diff options
author | yvt <i@yvt.jp> | 2021-07-10 17:24:27 +0900 |
---|---|---|
committer | yvt <i@yvt.jp> | 2021-07-10 17:55:42 +0900 |
commit | 01a7de50ab1843d85295f9dccad7f4c099e7208c (patch) | |
tree | ee53fb6e8889deb7b880ee969e6c662e6128d210 /components/script/dom | |
parent | ff8d2cdbbfc7a9dc7f38b7dd47cb350fde39388f (diff) | |
parent | 94b613fbdaa2b98f2179fc0bbda13c64e6fa0d38 (diff) | |
download | servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.tar.gz servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.zip |
Merge remote-tracking branch 'upstream/master' into feat-cow-infra
`tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html`
was reverted to the upstream version.
Diffstat (limited to 'components/script/dom')
1024 files changed, 110957 insertions, 40058 deletions
diff --git a/components/script/dom/abstractworker.rs b/components/script/dom/abstractworker.rs index 8d92680fbed..36ff18fe01b 100644 --- a/components/script/dom/abstractworker.rs +++ b/components/script/dom/abstractworker.rs @@ -1,20 +1,22 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::structuredclone::StructuredCloneData; -use js::jsapi::{JSRuntime, JS_RequestInterruptCallback}; -use js::rust::Runtime; -use script_runtime::CommonScriptMsg; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::script_runtime::CommonScriptMsg; +use script_traits::StructuredSerializedData; +use servo_url::ImmutableOrigin; /// Messages used to control the worker event loops pub enum WorkerScriptMsg { /// Common variants associated with the script messages Common(CommonScriptMsg), /// Message sent through Worker.postMessage - DOMMessage(StructuredCloneData) + DOMMessage { + origin: ImmutableOrigin, + data: StructuredSerializedData, + }, } pub struct SimpleWorkerErrorHandler<T: DomObject> { @@ -23,30 +25,6 @@ pub struct SimpleWorkerErrorHandler<T: DomObject> { impl<T: DomObject> SimpleWorkerErrorHandler<T> { pub fn new(addr: Trusted<T>) -> SimpleWorkerErrorHandler<T> { - SimpleWorkerErrorHandler { - addr: addr - } + SimpleWorkerErrorHandler { addr: addr } } } - -#[derive(Copy, Clone)] -pub struct SharedRt { - rt: *mut JSRuntime -} - -impl SharedRt { - pub fn new(rt: &Runtime) -> SharedRt { - SharedRt { - rt: rt.rt() - } - } - - #[allow(unsafe_code)] - pub fn request_interrupt(&self) { - unsafe { - JS_RequestInterruptCallback(self.rt); - } - } -} -#[allow(unsafe_code)] -unsafe impl Send for SharedRt {} diff --git a/components/script/dom/abstractworkerglobalscope.rs b/components/script/dom/abstractworkerglobalscope.rs index 7c79a919165..ca994c1c694 100644 --- a/components/script/dom/abstractworkerglobalscope.rs +++ b/components/script/dom/abstractworkerglobalscope.rs @@ -1,66 +1,159 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::abstractworker::WorkerScriptMsg; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::trace::JSTraceable; -use script_runtime::{ScriptChan, CommonScriptMsg, ScriptPort}; -use std::sync::mpsc::{Receiver, Sender}; +use crate::dom::abstractworker::WorkerScriptMsg; +use crate::dom::bindings::conversions::DerivedFrom; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::dedicatedworkerglobalscope::{AutoWorkerReset, DedicatedWorkerScriptMsg}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::worker::TrustedWorkerAddress; +use crate::dom::workerglobalscope::WorkerGlobalScope; +use crate::script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort}; +use crate::task_queue::{QueuedTaskConversion, TaskQueue}; +use crossbeam_channel::{Receiver, Sender}; +use devtools_traits::DevtoolScriptControlMsg; /// A ScriptChan that can be cloned freely and will silently send a TrustedWorkerAddress with /// common event loop messages. While this SendableWorkerScriptChan is alive, the associated /// Worker object will remain alive. -#[derive(JSTraceable, Clone)] -pub struct SendableWorkerScriptChan<T: DomObject> { - pub sender: Sender<(Trusted<T>, CommonScriptMsg)>, - pub worker: Trusted<T>, +#[derive(Clone, JSTraceable)] +pub struct SendableWorkerScriptChan { + pub sender: Sender<DedicatedWorkerScriptMsg>, + pub worker: TrustedWorkerAddress, } -impl<T: JSTraceable + DomObject + 'static> ScriptChan for SendableWorkerScriptChan<T> { +impl ScriptChan for SendableWorkerScriptChan { fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> { - self.sender.send((self.worker.clone(), msg)).map_err(|_| ()) + let msg = DedicatedWorkerScriptMsg::CommonWorker( + self.worker.clone(), + WorkerScriptMsg::Common(msg), + ); + self.sender.send(msg).map_err(|_| ()) } - fn clone(&self) -> Box<ScriptChan + Send> { - box SendableWorkerScriptChan { + fn clone(&self) -> Box<dyn ScriptChan + Send> { + Box::new(SendableWorkerScriptChan { sender: self.sender.clone(), worker: self.worker.clone(), - } + }) } } /// A ScriptChan that can be cloned freely and will silently send a TrustedWorkerAddress with /// worker event loop messages. While this SendableWorkerScriptChan is alive, the associated /// Worker object will remain alive. -#[derive(JSTraceable, Clone)] -pub struct WorkerThreadWorkerChan<T: DomObject> { - pub sender: Sender<(Trusted<T>, WorkerScriptMsg)>, - pub worker: Trusted<T>, +#[derive(Clone, JSTraceable)] +pub struct WorkerThreadWorkerChan { + pub sender: Sender<DedicatedWorkerScriptMsg>, + pub worker: TrustedWorkerAddress, } -impl<T: JSTraceable + DomObject + 'static> ScriptChan for WorkerThreadWorkerChan<T> { +impl ScriptChan for WorkerThreadWorkerChan { fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> { - self.sender - .send((self.worker.clone(), WorkerScriptMsg::Common(msg))) - .map_err(|_| ()) + let msg = DedicatedWorkerScriptMsg::CommonWorker( + self.worker.clone(), + WorkerScriptMsg::Common(msg), + ); + self.sender.send(msg).map_err(|_| ()) } - fn clone(&self) -> Box<ScriptChan + Send> { - box WorkerThreadWorkerChan { + fn clone(&self) -> Box<dyn ScriptChan + Send> { + Box::new(WorkerThreadWorkerChan { sender: self.sender.clone(), worker: self.worker.clone(), - } + }) } } -impl<T: DomObject> ScriptPort for Receiver<(Trusted<T>, WorkerScriptMsg)> { +impl ScriptPort for Receiver<DedicatedWorkerScriptMsg> { fn recv(&self) -> Result<CommonScriptMsg, ()> { - match self.recv().map(|(_, msg)| msg) { - Ok(WorkerScriptMsg::Common(script_msg)) => Ok(script_msg), - Ok(WorkerScriptMsg::DOMMessage(_)) => panic!("unexpected worker event message!"), - Err(_) => Err(()), + let common_msg = match self.recv() { + Ok(DedicatedWorkerScriptMsg::CommonWorker(_worker, common_msg)) => common_msg, + Err(_) => return Err(()), + Ok(DedicatedWorkerScriptMsg::WakeUp) => panic!("unexpected worker event message!"), + }; + match common_msg { + WorkerScriptMsg::Common(script_msg) => Ok(script_msg), + WorkerScriptMsg::DOMMessage { .. } => panic!("unexpected worker event message!"), + } + } +} + +pub trait WorkerEventLoopMethods { + type WorkerMsg: QueuedTaskConversion + Send; + type ControlMsg; + type Event; + fn task_queue(&self) -> &TaskQueue<Self::WorkerMsg>; + fn handle_event(&self, event: Self::Event) -> bool; + fn handle_worker_post_event(&self, worker: &TrustedWorkerAddress) -> Option<AutoWorkerReset>; + fn from_control_msg(&self, msg: Self::ControlMsg) -> Self::Event; + fn from_worker_msg(&self, msg: Self::WorkerMsg) -> Self::Event; + fn from_devtools_msg(&self, msg: DevtoolScriptControlMsg) -> Self::Event; + fn control_receiver(&self) -> &Receiver<Self::ControlMsg>; +} + +// https://html.spec.whatwg.org/multipage/#worker-event-loop +pub fn run_worker_event_loop<T, WorkerMsg, Event>( + worker_scope: &T, + worker: Option<&TrustedWorkerAddress>, +) where + WorkerMsg: QueuedTaskConversion + Send, + T: WorkerEventLoopMethods<WorkerMsg = WorkerMsg, Event = Event> + + DerivedFrom<WorkerGlobalScope> + + DerivedFrom<GlobalScope> + + DomObject, +{ + let scope = worker_scope.upcast::<WorkerGlobalScope>(); + let devtools_port = match scope.from_devtools_sender() { + Some(_) => Some(scope.from_devtools_receiver()), + None => None, + }; + let task_queue = worker_scope.task_queue(); + let event = select! { + recv(worker_scope.control_receiver()) -> msg => worker_scope.from_control_msg(msg.unwrap()), + recv(task_queue.select()) -> msg => { + task_queue.take_tasks(msg.unwrap()); + worker_scope.from_worker_msg(task_queue.recv().unwrap()) + }, + recv(devtools_port.unwrap_or(&crossbeam_channel::never())) -> msg => + worker_scope.from_devtools_msg(msg.unwrap()), + }; + let mut sequential = vec![]; + sequential.push(event); + // https://html.spec.whatwg.org/multipage/#worker-event-loop + // Once the WorkerGlobalScope's closing flag is set to true, + // the event loop's task queues must discard any further tasks + // that would be added to them + // (tasks already on the queue are unaffected except where otherwise specified). + while !scope.is_closing() { + // Batch all events that are ready. + // The task queue will throttle non-priority tasks if necessary. + match task_queue.try_recv() { + Err(_) => match devtools_port.map(|port| port.try_recv()) { + None => {}, + Some(Err(_)) => break, + Some(Ok(ev)) => sequential.push(worker_scope.from_devtools_msg(ev)), + }, + Ok(ev) => sequential.push(worker_scope.from_worker_msg(ev)), + } + } + // Step 3 + for event in sequential { + if !worker_scope.handle_event(event) { + // Shutdown + return; } + // Step 6 + let _ar = match worker { + Some(worker) => worker_scope.handle_worker_post_event(worker), + None => None, + }; + worker_scope + .upcast::<GlobalScope>() + .perform_a_microtask_checkpoint(); } + worker_scope + .upcast::<GlobalScope>() + .perform_a_dom_garbage_collection_checkpoint(); } diff --git a/components/script/dom/activation.rs b/components/script/dom/activation.rs index 7d2e710d8bd..4e6ac769885 100644 --- a/components/script/dom/activation.rs +++ b/components/script/dom/activation.rs @@ -1,18 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::str::DOMString; -use dom::element::Element; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::eventtarget::EventTarget; -use dom::mouseevent::MouseEvent; -use dom::node::window_from_node; -use dom::window::ReflowReason; -use script_layout_interface::message::ReflowQueryType; -use style::context::ReflowGoal; +use crate::dom::element::Element; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlinputelement::InputActivationState; +use crate::dom::node::window_from_node; +use crate::dom::window::ReflowReason; +use script_layout_interface::message::ReflowGoal; /// Trait for elements with defined activation behavior pub trait Activatable { @@ -21,100 +17,31 @@ pub trait Activatable { // Is this particular instance of the element activatable? fn is_instance_activatable(&self) -> bool; - // https://html.spec.whatwg.org/multipage/#run-pre-click-activation-steps - fn pre_click_activation(&self); + // https://dom.spec.whatwg.org/#eventtarget-legacy-pre-activation-behavior + fn legacy_pre_activation_behavior(&self) -> Option<InputActivationState> { + None + } - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self); + // https://dom.spec.whatwg.org/#eventtarget-legacy-canceled-activation-behavior + fn legacy_canceled_activation_behavior(&self, _state_before: Option<InputActivationState>) {} - // https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps + // https://dom.spec.whatwg.org/#eventtarget-activation-behavior + // event and target are used only by HTMLAnchorElement, in the case + // where the target is an <img ismap> so the href gets coordinates appended fn activation_behavior(&self, event: &Event, target: &EventTarget); - // https://html.spec.whatwg.org/multipage/#implicit-submission - fn implicit_submission(&self, ctrl_key: bool, shift_key: bool, alt_key: bool, meta_key: bool); - // https://html.spec.whatwg.org/multipage/#concept-selector-active fn enter_formal_activation_state(&self) { self.as_element().set_active_state(true); let win = window_from_node(self.as_element()); - win.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::ElementStateChanged); + win.reflow(ReflowGoal::Full, ReflowReason::ElementStateChanged); } fn exit_formal_activation_state(&self) { self.as_element().set_active_state(false); let win = window_from_node(self.as_element()); - win.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::ElementStateChanged); - } -} - -/// Whether an activation was initiated via the click() method -#[derive(PartialEq)] -pub enum ActivationSource { - FromClick, - NotFromClick, -} - -// https://html.spec.whatwg.org/multipage/#run-synthetic-click-activation-steps -pub fn synthetic_click_activation(element: &Element, - ctrl_key: bool, - shift_key: bool, - alt_key: bool, - meta_key: bool, - source: ActivationSource) { - // Step 1 - if element.click_in_progress() { - return; + win.reflow(ReflowGoal::Full, ReflowReason::ElementStateChanged); } - // Step 2 - element.set_click_in_progress(true); - // Step 3 - let activatable = element.as_maybe_activatable(); - if let Some(a) = activatable { - a.pre_click_activation(); - } - - // Step 4 - // https://html.spec.whatwg.org/multipage/#fire-a-synthetic-mouse-event - let win = window_from_node(element); - let target = element.upcast::<EventTarget>(); - let mouse = MouseEvent::new(&win, - DOMString::from("click"), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable, - Some(&win), - 1, - 0, - 0, - 0, - 0, - ctrl_key, - shift_key, - alt_key, - meta_key, - 0, - None); - let event = mouse.upcast::<Event>(); - if source == ActivationSource::FromClick { - event.set_trusted(false); - } - target.dispatch_event(event); - - // Step 5 - if let Some(a) = activatable { - if event.DefaultPrevented() { - a.canceled_activation(); - } else { - // post click activation - a.activation_behavior(event, target); - } - } - - // Step 6 - element.set_click_in_progress(false); } diff --git a/components/script/dom/analysernode.rs b/components/script/dom/analysernode.rs new file mode 100644 index 00000000000..d09dd61d9f3 --- /dev/null +++ b/components/script/dom/analysernode.rs @@ -0,0 +1,233 @@ +/* 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 crate::dom::audionode::AudioNode; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AnalyserNodeBinding::{ + AnalyserNodeMethods, AnalyserOptions, +}; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::window::Window; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use ipc_channel::ipc::{self, IpcReceiver}; +use ipc_channel::router::ROUTER; +use js::rust::CustomAutoRooterGuard; +use js::typedarray::{Float32Array, Uint8Array}; +use servo_media::audio::analyser_node::AnalysisEngine; +use servo_media::audio::block::Block; +use servo_media::audio::node::AudioNodeInit; + +#[dom_struct] +pub struct AnalyserNode { + node: AudioNode, + #[ignore_malloc_size_of = "Defined in servo-media"] + engine: DomRefCell<AnalysisEngine>, +} + +impl AnalyserNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + _: &Window, + context: &BaseAudioContext, + options: &AnalyserOptions, + ) -> Fallible<(AnalyserNode, IpcReceiver<Block>)> { + let node_options = + options + .parent + .unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers); + + if options.fftSize > 32768 || + options.fftSize < 32 || + (options.fftSize & (options.fftSize - 1) != 0) + { + return Err(Error::IndexSize); + } + + if *options.maxDecibels <= *options.minDecibels { + return Err(Error::IndexSize); + } + + if *options.smoothingTimeConstant < 0. || *options.smoothingTimeConstant > 1. { + return Err(Error::IndexSize); + } + + let (send, rcv) = ipc::channel().unwrap(); + let callback = move |block| { + send.send(block).unwrap(); + }; + + let node = AudioNode::new_inherited( + AudioNodeInit::AnalyserNode(Box::new(callback)), + context, + node_options, + 1, // inputs + 1, // outputs + )?; + + let engine = AnalysisEngine::new( + options.fftSize as usize, + *options.smoothingTimeConstant, + *options.minDecibels, + *options.maxDecibels, + ); + Ok(( + AnalyserNode { + node, + engine: DomRefCell::new(engine), + }, + rcv, + )) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &AnalyserOptions, + ) -> Fallible<DomRoot<AnalyserNode>> { + let (node, recv) = AnalyserNode::new_inherited(window, context, options)?; + let object = reflect_dom_object(Box::new(node), window); + let (source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let this = Trusted::new(&*object); + + ROUTER.add_route( + recv.to_opaque(), + Box::new(move |block| { + let this = this.clone(); + let _ = source.queue_with_canceller( + task!(append_analysis_block: move || { + let this = this.root(); + this.push_block(block.to().unwrap()) + }), + &canceller, + ); + }), + ); + Ok(object) + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-analysernode + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &AnalyserOptions, + ) -> Fallible<DomRoot<AnalyserNode>> { + AnalyserNode::new(window, context, options) + } + + pub fn push_block(&self, block: Block) { + self.engine.borrow_mut().push(block) + } +} + +impl AnalyserNodeMethods for AnalyserNode { + #[allow(unsafe_code)] + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-getfloatfrequencydata + fn GetFloatFrequencyData(&self, mut array: CustomAutoRooterGuard<Float32Array>) { + // Invariant to maintain: No JS code that may touch the array should + // run whilst we're writing to it + let dest = unsafe { array.as_mut_slice() }; + self.engine.borrow_mut().fill_frequency_data(dest); + } + + #[allow(unsafe_code)] + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-getbytefrequencydata + fn GetByteFrequencyData(&self, mut array: CustomAutoRooterGuard<Uint8Array>) { + // Invariant to maintain: No JS code that may touch the array should + // run whilst we're writing to it + let dest = unsafe { array.as_mut_slice() }; + self.engine.borrow_mut().fill_byte_frequency_data(dest); + } + + #[allow(unsafe_code)] + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-getfloattimedomaindata + fn GetFloatTimeDomainData(&self, mut array: CustomAutoRooterGuard<Float32Array>) { + // Invariant to maintain: No JS code that may touch the array should + // run whilst we're writing to it + let dest = unsafe { array.as_mut_slice() }; + self.engine.borrow().fill_time_domain_data(dest); + } + + #[allow(unsafe_code)] + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-getbytetimedomaindata + fn GetByteTimeDomainData(&self, mut array: CustomAutoRooterGuard<Uint8Array>) { + // Invariant to maintain: No JS code that may touch the array should + // run whilst we're writing to it + let dest = unsafe { array.as_mut_slice() }; + self.engine.borrow().fill_byte_time_domain_data(dest); + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-fftsize + fn SetFftSize(&self, value: u32) -> Fallible<()> { + if value > 32768 || value < 32 || (value & (value - 1) != 0) { + return Err(Error::IndexSize); + } + self.engine.borrow_mut().set_fft_size(value as usize); + Ok(()) + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-fftsize + fn FftSize(&self) -> u32 { + self.engine.borrow().get_fft_size() as u32 + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-frequencybincount + fn FrequencyBinCount(&self) -> u32 { + self.FftSize() / 2 + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-mindecibels + fn MinDecibels(&self) -> Finite<f64> { + Finite::wrap(self.engine.borrow().get_min_decibels()) + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-mindecibels + fn SetMinDecibels(&self, value: Finite<f64>) -> Fallible<()> { + if *value >= self.engine.borrow().get_max_decibels() { + return Err(Error::IndexSize); + } + self.engine.borrow_mut().set_min_decibels(*value); + Ok(()) + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-maxdecibels + fn MaxDecibels(&self) -> Finite<f64> { + Finite::wrap(self.engine.borrow().get_max_decibels()) + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-maxdecibels + fn SetMaxDecibels(&self, value: Finite<f64>) -> Fallible<()> { + if *value <= self.engine.borrow().get_min_decibels() { + return Err(Error::IndexSize); + } + self.engine.borrow_mut().set_max_decibels(*value); + Ok(()) + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-smoothingtimeconstant + fn SmoothingTimeConstant(&self) -> Finite<f64> { + Finite::wrap(self.engine.borrow().get_smoothing_constant()) + } + + /// https://webaudio.github.io/web-audio-api/#dom-analysernode-smoothingtimeconstant + fn SetSmoothingTimeConstant(&self, value: Finite<f64>) -> Fallible<()> { + if *value < 0. || *value > 1. { + return Err(Error::IndexSize); + } + self.engine.borrow_mut().set_smoothing_constant(*value); + Ok(()) + } +} diff --git a/components/script/dom/animationevent.rs b/components/script/dom/animationevent.rs new file mode 100644 index 00000000000..191b460228d --- /dev/null +++ b/components/script/dom/animationevent.rs @@ -0,0 +1,76 @@ +/* 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 crate::dom::bindings::codegen::Bindings::AnimationEventBinding::{ + AnimationEventInit, AnimationEventMethods, +}; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct AnimationEvent { + event: Event, + animation_name: Atom, + elapsed_time: Finite<f32>, + pseudo_element: DOMString, +} + +impl AnimationEvent { + fn new_inherited(init: &AnimationEventInit) -> AnimationEvent { + AnimationEvent { + event: Event::new_inherited(), + animation_name: Atom::from(init.animationName.clone()), + elapsed_time: init.elapsedTime.clone(), + pseudo_element: init.pseudoElement.clone(), + } + } + + pub fn new(window: &Window, type_: Atom, init: &AnimationEventInit) -> DomRoot<AnimationEvent> { + let ev = reflect_dom_object(Box::new(AnimationEvent::new_inherited(init)), window); + { + let event = ev.upcast::<Event>(); + event.init_event(type_, init.parent.bubbles, init.parent.cancelable); + } + ev + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &AnimationEventInit, + ) -> DomRoot<AnimationEvent> { + AnimationEvent::new(window, Atom::from(type_), init) + } +} + +impl AnimationEventMethods for AnimationEvent { + // https://drafts.csswg.org/css-animations/#interface-animationevent-attributes + fn AnimationName(&self) -> DOMString { + DOMString::from(&*self.animation_name) + } + + // https://drafts.csswg.org/css-animations/#interface-animationevent-attributes + fn ElapsedTime(&self) -> Finite<f32> { + self.elapsed_time.clone() + } + + // https://drafts.csswg.org/css-animations/#interface-animationevent-attributes + fn PseudoElement(&self) -> DOMString { + self.pseudo_element.clone() + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.upcast::<Event>().IsTrusted() + } +} diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs index 55f2adcfdb6..9c4b221f6f3 100644 --- a/components/script/dom/attr.rs +++ b/components/script/dom/attr.rs @@ -1,88 +1,92 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::customelementregistry::CallbackReaction; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::mutationobserver::{Mutation, MutationObserver}; +use crate::dom::node::Node; +use crate::dom::virtualmethods::vtable_for; +use crate::script_thread::ScriptThread; use devtools_traits::AttrInfo; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::AttrBinding::{self, AttrMethods}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, MutNullableJS, Root, RootedReference}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::element::{AttributeMutation, Element}; -use dom::virtualmethods::vtable_for; -use dom::window::Window; use dom_struct::dom_struct; -use html5ever_atoms::{Prefix, LocalName, Namespace}; +use html5ever::{LocalName, Namespace, Prefix}; use servo_atoms::Atom; use std::borrow::ToOwned; -use std::cell::Ref; use std::mem; use style::attr::{AttrIdentifier, AttrValue}; +use style::values::GenericAtomIdent; // https://dom.spec.whatwg.org/#interface-attr #[dom_struct] pub struct Attr { - reflector_: Reflector, + node_: Node, identifier: AttrIdentifier, - value: DOMRefCell<AttrValue>, + value: DomRefCell<AttrValue>, /// the element that owns this attribute. - owner: MutNullableJS<Element>, + owner: MutNullableDom<Element>, } impl Attr { - fn new_inherited(local_name: LocalName, - value: AttrValue, - name: LocalName, - namespace: Namespace, - prefix: Option<Prefix>, - owner: Option<&Element>) - -> Attr { + fn new_inherited( + document: &Document, + local_name: LocalName, + value: AttrValue, + name: LocalName, + namespace: Namespace, + prefix: Option<Prefix>, + owner: Option<&Element>, + ) -> Attr { Attr { - reflector_: Reflector::new(), + node_: Node::new_inherited(document), identifier: AttrIdentifier { - local_name: local_name, - name: name, - namespace: namespace, - prefix: prefix, + local_name: GenericAtomIdent(local_name), + name: GenericAtomIdent(name), + namespace: GenericAtomIdent(namespace), + prefix: prefix.map(GenericAtomIdent), }, - value: DOMRefCell::new(value), - owner: MutNullableJS::new(owner), + value: DomRefCell::new(value), + owner: MutNullableDom::new(owner), } } - pub fn new(window: &Window, - local_name: LocalName, - value: AttrValue, - name: LocalName, - namespace: Namespace, - prefix: Option<Prefix>, - owner: Option<&Element>) - -> Root<Attr> { - reflect_dom_object(box Attr::new_inherited(local_name, - value, - name, - namespace, - prefix, - owner), - window, - AttrBinding::Wrap) + pub fn new( + document: &Document, + local_name: LocalName, + value: AttrValue, + name: LocalName, + namespace: Namespace, + prefix: Option<Prefix>, + owner: Option<&Element>, + ) -> DomRoot<Attr> { + Node::reflect_node( + Box::new(Attr::new_inherited( + document, local_name, value, name, namespace, prefix, owner, + )), + document, + ) } #[inline] pub fn name(&self) -> &LocalName { - &self.identifier.name + &self.identifier.name.0 } #[inline] pub fn namespace(&self) -> &Namespace { - &self.identifier.namespace + &self.identifier.namespace.0 } #[inline] pub fn prefix(&self) -> Option<&Prefix> { - self.identifier.prefix.as_ref() + Some(&self.identifier.prefix.as_ref()?.0) } } @@ -102,49 +106,22 @@ impl AttrMethods for Attr { // https://dom.spec.whatwg.org/#dom-attr-value fn SetValue(&self, value: DOMString) { if let Some(owner) = self.owner() { - let value = owner.parse_attribute(&self.identifier.namespace, - self.local_name(), - value); + let value = owner.parse_attribute(self.namespace(), self.local_name(), value); self.set_value(value, &owner); } else { *self.value.borrow_mut() = AttrValue::String(value.into()); } } - // https://dom.spec.whatwg.org/#dom-attr-textcontent - fn TextContent(&self) -> DOMString { - self.Value() - } - - // https://dom.spec.whatwg.org/#dom-attr-textcontent - fn SetTextContent(&self, value: DOMString) { - self.SetValue(value) - } - - // https://dom.spec.whatwg.org/#dom-attr-nodevalue - fn NodeValue(&self) -> DOMString { - self.Value() - } - - // https://dom.spec.whatwg.org/#dom-attr-nodevalue - fn SetNodeValue(&self, value: DOMString) { - self.SetValue(value) - } - // https://dom.spec.whatwg.org/#dom-attr-name fn Name(&self) -> DOMString { // FIXME(ajeffrey): convert directly from LocalName to DOMString - DOMString::from(&*self.identifier.name) - } - - // https://dom.spec.whatwg.org/#dom-attr-nodename - fn NodeName(&self) -> DOMString { - self.Name() + DOMString::from(&**self.name()) } // https://dom.spec.whatwg.org/#dom-attr-namespaceuri fn GetNamespaceURI(&self) -> Option<DOMString> { - match self.identifier.namespace { + match *self.namespace() { ns!() => None, ref url => Some(DOMString::from(&**url)), } @@ -157,7 +134,7 @@ impl AttrMethods for Attr { } // https://dom.spec.whatwg.org/#dom-attr-ownerelement - fn GetOwnerElement(&self) -> Option<Root<Element>> { + fn GetOwnerElement(&self) -> Option<DomRoot<Element>> { self.owner() } @@ -167,13 +144,34 @@ impl AttrMethods for Attr { } } - impl Attr { pub fn set_value(&self, mut value: AttrValue, owner: &Element) { - assert!(Some(owner) == self.owner().r()); + let name = self.local_name().clone(); + let namespace = self.namespace().clone(); + let old_value = DOMString::from(&**self.value()); + let new_value = DOMString::from(&*value); + let mutation = Mutation::Attribute { + name: name.clone(), + namespace: namespace.clone(), + old_value: Some(old_value.clone()), + }; + + MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation); + + if owner.get_custom_element_definition().is_some() { + let reaction = CallbackReaction::AttributeChanged( + name, + Some(old_value), + Some(new_value), + namespace, + ); + ScriptThread::enqueue_callback_reaction(owner, reaction, None); + } + + assert_eq!(Some(owner), self.owner().as_deref()); owner.will_mutate_attr(self); self.swap_value(&mut value); - if self.identifier.namespace == ns!() { + if *self.namespace() == ns!() { vtable_for(owner.upcast()) .attribute_mutated(self, AttributeMutation::Set(Some(&value))); } @@ -199,79 +197,78 @@ impl Attr { /// Sets the owner element. Should be called after the attribute is added /// or removed from its older parent. pub fn set_owner(&self, owner: Option<&Element>) { - let ns = &self.identifier.namespace; + let ns = self.namespace(); match (self.owner(), owner) { (Some(old), None) => { // Already gone from the list of attributes of old owner. - assert!(old.get_attribute(&ns, &self.identifier.local_name).r() != Some(self)) - } - (Some(old), Some(new)) => assert!(&*old == new), + assert!( + old.get_attribute(&ns, &self.identifier.local_name) + .as_deref() != + Some(self) + ) + }, + (Some(old), Some(new)) => assert_eq!(&*old, new), _ => {}, } self.owner.set(owner); } - pub fn owner(&self) -> Option<Root<Element>> { + pub fn owner(&self) -> Option<DomRoot<Element>> { self.owner.get() } pub fn summarize(&self) -> AttrInfo { AttrInfo { - namespace: (*self.identifier.namespace).to_owned(), + namespace: (**self.namespace()).to_owned(), name: String::from(self.Name()), value: String::from(self.Value()), } } + + pub fn qualified_name(&self) -> DOMString { + match self.prefix() { + Some(ref prefix) => DOMString::from(format!("{}:{}", prefix, &**self.local_name())), + None => DOMString::from(&**self.local_name()), + } + } } #[allow(unsafe_code)] -pub trait AttrHelpersForLayout { - unsafe fn value_forever(&self) -> &'static AttrValue; - unsafe fn value_ref_forever(&self) -> &'static str; - unsafe fn value_atom_forever(&self) -> Option<Atom>; - unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]>; - unsafe fn local_name_atom_forever(&self) -> LocalName; - unsafe fn value_for_layout(&self) -> &AttrValue; +pub trait AttrHelpersForLayout<'dom> { + fn value(self) -> &'dom AttrValue; + fn as_str(self) -> &'dom str; + fn as_tokens(self) -> Option<&'dom [Atom]>; + fn local_name(self) -> &'dom LocalName; + fn namespace(self) -> &'dom Namespace; } #[allow(unsafe_code)] -impl AttrHelpersForLayout for LayoutJS<Attr> { +impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> { #[inline] - unsafe fn value_forever(&self) -> &'static AttrValue { - // This transmute is used to cheat the lifetime restriction. - mem::transmute::<&AttrValue, &AttrValue>((*self.unsafe_get()).value.borrow_for_layout()) + fn value(self) -> &'dom AttrValue { + unsafe { self.unsafe_get().value.borrow_for_layout() } } #[inline] - unsafe fn value_ref_forever(&self) -> &'static str { - &**self.value_forever() - } - - #[inline] - unsafe fn value_atom_forever(&self) -> Option<Atom> { - let value = (*self.unsafe_get()).value.borrow_for_layout(); - match *value { - AttrValue::Atom(ref val) => Some(val.clone()), - _ => None, - } + fn as_str(self) -> &'dom str { + &**self.value() } #[inline] - unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]> { - // This transmute is used to cheat the lifetime restriction. - match *self.value_forever() { + fn as_tokens(self) -> Option<&'dom [Atom]> { + match *self.value() { AttrValue::TokenList(_, ref tokens) => Some(tokens), _ => None, } } #[inline] - unsafe fn local_name_atom_forever(&self) -> LocalName { - (*self.unsafe_get()).identifier.local_name.clone() + fn local_name(self) -> &'dom LocalName { + unsafe { &self.unsafe_get().identifier.local_name.0 } } #[inline] - unsafe fn value_for_layout(&self) -> &AttrValue { - (*self.unsafe_get()).value.borrow_for_layout() + fn namespace(self) -> &'dom Namespace { + unsafe { &self.unsafe_get().identifier.namespace.0 } } } diff --git a/components/script/dom/audiobuffer.rs b/components/script/dom/audiobuffer.rs new file mode 100644 index 00000000000..ade645827a6 --- /dev/null +++ b/components/script/dom/audiobuffer.rs @@ -0,0 +1,342 @@ +/* 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 crate::dom::audionode::MAX_CHANNEL_COUNT; +use crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::AudioBufferBinding::{ + AudioBufferMethods, AudioBufferOptions, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::window::Window; +use crate::realms::enter_realm; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use js::jsapi::JS_GetArrayBufferViewBuffer; +use js::jsapi::{Heap, JSObject}; +use js::rust::wrappers::DetachArrayBuffer; +use js::rust::CustomAutoRooterGuard; +use js::typedarray::{CreateWith, Float32Array}; +use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer; +use std::cmp::min; +use std::ptr::{self, NonNull}; + +// Spec mandates at least [8000, 96000], we use [8000, 192000] to match Firefox +// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer +pub const MIN_SAMPLE_RATE: f32 = 8000.; +pub const MAX_SAMPLE_RATE: f32 = 192000.; + +type JSAudioChannel = Heap<*mut JSObject>; + +/// The AudioBuffer keeps its data either in js_channels +/// or in shared_channels if js_channels buffers are detached. +/// +/// js_channels buffers are (re)attached right before calling GetChannelData +/// and remain attached until its contents are needed by some other API +/// implementation. Follow https://webaudio.github.io/web-audio-api/#acquire-the-content +/// to know in which situations js_channels buffers must be detached. +/// +#[dom_struct] +pub struct AudioBuffer { + reflector_: Reflector, + /// Float32Arrays returned by calls to GetChannelData. + #[ignore_malloc_size_of = "mozjs"] + js_channels: DomRefCell<Vec<JSAudioChannel>>, + /// Aggregates the data from js_channels. + /// This is Some<T> iff the buffers in js_channels are detached. + #[ignore_malloc_size_of = "servo_media"] + shared_channels: DomRefCell<Option<ServoMediaAudioBuffer>>, + /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate + sample_rate: f32, + /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length + length: u32, + /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration + duration: f64, + /// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels + number_of_channels: u32, +} + +impl AudioBuffer { + #[allow(unrooted_must_root)] + #[allow(unsafe_code)] + pub fn new_inherited(number_of_channels: u32, length: u32, sample_rate: f32) -> AudioBuffer { + let vec = (0..number_of_channels).map(|_| Heap::default()).collect(); + AudioBuffer { + reflector_: Reflector::new(), + js_channels: DomRefCell::new(vec), + shared_channels: DomRefCell::new(None), + sample_rate, + length, + duration: length as f64 / sample_rate as f64, + number_of_channels, + } + } + + #[allow(unrooted_must_root)] + pub fn new( + global: &Window, + number_of_channels: u32, + length: u32, + sample_rate: f32, + initial_data: Option<&[Vec<f32>]>, + ) -> DomRoot<AudioBuffer> { + let buffer = AudioBuffer::new_inherited(number_of_channels, length, sample_rate); + let buffer = reflect_dom_object(Box::new(buffer), global); + buffer.set_initial_data(initial_data); + buffer + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-audiobuffer + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + options: &AudioBufferOptions, + ) -> Fallible<DomRoot<AudioBuffer>> { + if options.length <= 0 || + options.numberOfChannels <= 0 || + options.numberOfChannels > MAX_CHANNEL_COUNT || + *options.sampleRate < MIN_SAMPLE_RATE || + *options.sampleRate > MAX_SAMPLE_RATE + { + return Err(Error::NotSupported); + } + Ok(AudioBuffer::new( + window, + options.numberOfChannels, + options.length, + *options.sampleRate, + None, + )) + } + + // Initialize the underlying channels data with initial data provided by + // the user or silence otherwise. + fn set_initial_data(&self, initial_data: Option<&[Vec<f32>]>) { + let mut channels = ServoMediaAudioBuffer::new( + self.number_of_channels as u8, + self.length as usize, + self.sample_rate, + ); + for channel in 0..self.number_of_channels { + channels.buffers[channel as usize] = match initial_data { + Some(data) => data[channel as usize].clone(), + None => vec![0.; self.length as usize], + }; + } + *self.shared_channels.borrow_mut() = Some(channels); + } + + #[allow(unsafe_code)] + fn restore_js_channel_data(&self, cx: JSContext) -> bool { + let _ac = enter_realm(&*self); + for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() { + if !channel.get().is_null() { + // Already have data in JS array. + continue; + } + + rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); + if let Some(ref shared_channels) = *self.shared_channels.borrow() { + // Step 4. of + // https://webaudio.github.io/web-audio-api/#acquire-the-content + // "Attach ArrayBuffers containing copies of the data to the AudioBuffer, + // to be returned by the next call to getChannelData()". + unsafe { + if Float32Array::create( + *cx, + CreateWith::Slice(&shared_channels.buffers[i]), + array.handle_mut(), + ) + .is_err() + { + return false; + } + } + } + channel.set(array.get()); + } + + *self.shared_channels.borrow_mut() = None; + + true + } + + // https://webaudio.github.io/web-audio-api/#acquire-the-content + #[allow(unsafe_code)] + fn acquire_contents(&self) -> Option<ServoMediaAudioBuffer> { + let mut result = ServoMediaAudioBuffer::new( + self.number_of_channels as u8, + self.length as usize, + self.sample_rate, + ); + let cx = self.global().get_cx(); + for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() { + // Step 1. + if channel.get().is_null() { + return None; + } + + // Step 2. + let channel_data = unsafe { + typedarray!(in(*cx) let array: Float32Array = channel.get()); + if let Ok(array) = array { + let data = array.to_vec(); + let mut is_shared = false; + rooted!(in (*cx) let view_buffer = + JS_GetArrayBufferViewBuffer(*cx, channel.handle(), &mut is_shared)); + // This buffer is always created unshared + debug_assert!(!is_shared); + let _ = DetachArrayBuffer(*cx, view_buffer.handle()); + data + } else { + return None; + } + }; + + channel.set(ptr::null_mut()); + + // Step 3. + result.buffers[i] = channel_data; + } + + Some(result) + } + + pub fn get_channels(&self) -> Ref<Option<ServoMediaAudioBuffer>> { + if self.shared_channels.borrow().is_none() { + let channels = self.acquire_contents(); + if channels.is_some() { + *self.shared_channels.borrow_mut() = channels; + } + } + return self.shared_channels.borrow(); + } +} + +impl AudioBufferMethods for AudioBuffer { + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate + fn SampleRate(&self) -> Finite<f32> { + Finite::wrap(self.sample_rate) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length + fn Length(&self) -> u32 { + self.length + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration + fn Duration(&self) -> Finite<f64> { + Finite::wrap(self.duration) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels + fn NumberOfChannels(&self) -> u32 { + self.number_of_channels + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata + #[allow(unsafe_code)] + fn GetChannelData(&self, cx: JSContext, channel: u32) -> Fallible<NonNull<JSObject>> { + if channel >= self.number_of_channels { + return Err(Error::IndexSize); + } + + if !self.restore_js_channel_data(cx) { + return Err(Error::JSFailed); + } + unsafe { + Ok(NonNull::new_unchecked( + self.js_channels.borrow()[channel as usize].get(), + )) + } + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel + #[allow(unsafe_code)] + fn CopyFromChannel( + &self, + mut destination: CustomAutoRooterGuard<Float32Array>, + channel_number: u32, + start_in_channel: u32, + ) -> Fallible<()> { + if destination.is_shared() { + return Err(Error::Type("Cannot copy to shared buffer".to_owned())); + } + + if channel_number >= self.number_of_channels || start_in_channel >= self.length { + return Err(Error::IndexSize); + } + + let bytes_to_copy = min(self.length - start_in_channel, destination.len() as u32) as usize; + let cx = self.global().get_cx(); + let channel_number = channel_number as usize; + let offset = start_in_channel as usize; + let mut dest = Vec::with_capacity(destination.len()); + + // We either copy form js_channels or shared_channels. + let js_channel = self.js_channels.borrow()[channel_number].get(); + if !js_channel.is_null() { + typedarray!(in(*cx) let array: Float32Array = js_channel); + if let Ok(array) = array { + let data = unsafe { array.as_slice() }; + dest.extend_from_slice(&data[offset..offset + bytes_to_copy]); + } + } else if let Some(ref shared_channels) = *self.shared_channels.borrow() { + if let Some(shared_channel) = shared_channels.buffers.get(channel_number) { + dest.extend_from_slice(&shared_channel.as_slice()[offset..offset + bytes_to_copy]); + } + } + + unsafe { + destination.update(&dest); + } + + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel + #[allow(unsafe_code)] + fn CopyToChannel( + &self, + source: CustomAutoRooterGuard<Float32Array>, + channel_number: u32, + start_in_channel: u32, + ) -> Fallible<()> { + if source.is_shared() { + return Err(Error::Type("Cannot copy from shared buffer".to_owned())); + } + + if channel_number >= self.number_of_channels || start_in_channel > (source.len() as u32) { + return Err(Error::IndexSize); + } + + let cx = self.global().get_cx(); + if !self.restore_js_channel_data(cx) { + return Err(Error::JSFailed); + } + + let js_channel = self.js_channels.borrow()[channel_number as usize].get(); + if js_channel.is_null() { + // The array buffer was detached. + return Err(Error::IndexSize); + } + + typedarray!(in(*cx) let js_channel: Float32Array = js_channel); + if let Ok(mut js_channel) = js_channel { + let bytes_to_copy = min(self.length - start_in_channel, source.len() as u32) as usize; + let js_channel_data = unsafe { js_channel.as_mut_slice() }; + let (_, js_channel_data) = js_channel_data.split_at_mut(start_in_channel as usize); + unsafe { + js_channel_data[0..bytes_to_copy] + .copy_from_slice(&source.as_slice()[0..bytes_to_copy]) + }; + } else { + return Err(Error::IndexSize); + } + + Ok(()) + } +} diff --git a/components/script/dom/audiobuffersourcenode.rs b/components/script/dom/audiobuffersourcenode.rs new file mode 100644 index 00000000000..683f32f4ec0 --- /dev/null +++ b/components/script/dom/audiobuffersourcenode.rs @@ -0,0 +1,271 @@ +/* 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 crate::dom::audiobuffer::AudioBuffer; +use crate::dom::audioparam::AudioParam; +use crate::dom::audioscheduledsourcenode::AudioScheduledSourceNode; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioBufferSourceNodeBinding::AudioBufferSourceNodeMethods; +use crate::dom::bindings::codegen::Bindings::AudioBufferSourceNodeBinding::AudioBufferSourceOptions; +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate; +use crate::dom::bindings::codegen::Bindings::AudioScheduledSourceNodeBinding::AudioScheduledSourceNodeMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::buffer_source_node::AudioBufferSourceNodeMessage; +use servo_media::audio::buffer_source_node::AudioBufferSourceNodeOptions; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage}; +use servo_media::audio::param::ParamType; +use std::cell::Cell; +use std::f32; + +#[dom_struct] +pub struct AudioBufferSourceNode { + source_node: AudioScheduledSourceNode, + buffer: MutNullableDom<AudioBuffer>, + buffer_set: Cell<bool>, + playback_rate: Dom<AudioParam>, + detune: Dom<AudioParam>, + loop_enabled: Cell<bool>, + loop_start: Cell<f64>, + loop_end: Cell<f64>, +} + +impl AudioBufferSourceNode { + #[allow(unrooted_must_root)] + fn new_inherited( + window: &Window, + context: &BaseAudioContext, + options: &AudioBufferSourceOptions, + ) -> Fallible<AudioBufferSourceNode> { + let node_options = Default::default(); + let source_node = AudioScheduledSourceNode::new_inherited( + AudioNodeInit::AudioBufferSourceNode(options.into()), + context, + node_options, + 0, /* inputs */ + 1, /* outputs */ + )?; + let node_id = source_node.node().node_id(); + let playback_rate = AudioParam::new( + &window, + context, + node_id, + ParamType::PlaybackRate, + AutomationRate::K_rate, + *options.playbackRate, + f32::MIN, + f32::MAX, + ); + let detune = AudioParam::new( + &window, + context, + node_id, + ParamType::Detune, + AutomationRate::K_rate, + *options.detune, + f32::MIN, + f32::MAX, + ); + let node = AudioBufferSourceNode { + source_node, + buffer: Default::default(), + buffer_set: Cell::new(false), + playback_rate: Dom::from_ref(&playback_rate), + detune: Dom::from_ref(&detune), + loop_enabled: Cell::new(options.loop_), + loop_start: Cell::new(*options.loopStart), + loop_end: Cell::new(*options.loopEnd), + }; + if let Some(ref buffer) = options.buffer { + if let Some(ref buffer) = buffer { + if let Err(err) = node.SetBuffer(Some(&**buffer)) { + return Err(err); + } + } + } + Ok(node) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &AudioBufferSourceOptions, + ) -> Fallible<DomRoot<AudioBufferSourceNode>> { + let node = AudioBufferSourceNode::new_inherited(window, context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &AudioBufferSourceOptions, + ) -> Fallible<DomRoot<AudioBufferSourceNode>> { + AudioBufferSourceNode::new(window, context, options) + } +} + +impl AudioBufferSourceNodeMethods for AudioBufferSourceNode { + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-buffer + fn GetBuffer(&self) -> Fallible<Option<DomRoot<AudioBuffer>>> { + Ok(self.buffer.get()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-buffer + fn SetBuffer(&self, new_buffer: Option<&AudioBuffer>) -> Fallible<()> { + if new_buffer.is_some() { + if self.buffer_set.get() { + // Step 2. + return Err(Error::InvalidState); + } + // Step 3. + self.buffer_set.set(true); + } + + // Step 4. + self.buffer.set(new_buffer); + + // Step 5. + if self.source_node.has_start() { + if let Some(buffer) = self.buffer.get() { + let buffer = buffer.get_channels(); + if buffer.is_some() { + self.source_node + .node() + .message(AudioNodeMessage::AudioBufferSourceNode( + AudioBufferSourceNodeMessage::SetBuffer((*buffer).clone()), + )); + } + } + } + + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-playbackrate + fn PlaybackRate(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.playback_rate) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-detune + fn Detune(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.detune) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loop + fn Loop(&self) -> bool { + self.loop_enabled.get() + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loop + fn SetLoop(&self, should_loop: bool) { + self.loop_enabled.set(should_loop); + let msg = AudioNodeMessage::AudioBufferSourceNode( + AudioBufferSourceNodeMessage::SetLoopEnabled(should_loop), + ); + self.source_node.node().message(msg); + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopstart + fn LoopStart(&self) -> Finite<f64> { + Finite::wrap(self.loop_start.get()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopstart + fn SetLoopStart(&self, loop_start: Finite<f64>) { + self.loop_start.set(*loop_start); + let msg = AudioNodeMessage::AudioBufferSourceNode( + AudioBufferSourceNodeMessage::SetLoopStart(*loop_start), + ); + self.source_node.node().message(msg); + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopend + fn LoopEnd(&self) -> Finite<f64> { + Finite::wrap(self.loop_end.get()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopend + fn SetLoopEnd(&self, loop_end: Finite<f64>) { + self.loop_end.set(*loop_end); + let msg = AudioNodeMessage::AudioBufferSourceNode( + AudioBufferSourceNodeMessage::SetLoopEnd(*loop_end), + ); + self.source_node.node().message(msg); + } + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-start + fn Start( + &self, + when: Finite<f64>, + offset: Option<Finite<f64>>, + duration: Option<Finite<f64>>, + ) -> Fallible<()> { + if let Some(offset) = offset { + if *offset < 0. { + return Err(Error::Range("'offset' must be a positive value".to_owned())); + } + } + + if let Some(duration) = duration { + if *duration < 0. { + return Err(Error::Range( + "'duration' must be a positive value".to_owned(), + )); + } + } + + if let Some(buffer) = self.buffer.get() { + let buffer = buffer.get_channels(); + if buffer.is_some() { + self.source_node + .node() + .message(AudioNodeMessage::AudioBufferSourceNode( + AudioBufferSourceNodeMessage::SetBuffer((*buffer).clone()), + )); + } + } + + self.source_node + .node() + .message(AudioNodeMessage::AudioBufferSourceNode( + AudioBufferSourceNodeMessage::SetStartParams( + *when, + offset.map(|f| *f), + duration.map(|f| *f), + ), + )); + + self.source_node + .upcast::<AudioScheduledSourceNode>() + .Start(when) + } +} + +impl<'a> From<&'a AudioBufferSourceOptions> for AudioBufferSourceNodeOptions { + fn from(options: &'a AudioBufferSourceOptions) -> Self { + Self { + buffer: if let Some(ref buffer) = options.buffer { + if let Some(ref buffer) = buffer { + (*buffer.get_channels()).clone() + } else { + None + } + } else { + None + }, + detune: *options.detune, + loop_enabled: options.loop_, + loop_end: Some(*options.loopEnd), + loop_start: Some(*options.loopStart), + playback_rate: *options.playbackRate, + } + } +} diff --git a/components/script/dom/audiocontext.rs b/components/script/dom/audiocontext.rs new file mode 100644 index 00000000000..9a5160773f9 --- /dev/null +++ b/components/script/dom/audiocontext.rs @@ -0,0 +1,314 @@ +/* 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 crate::dom::baseaudiocontext::{BaseAudioContext, BaseAudioContextOptions}; +use crate::dom::bindings::codegen::Bindings::AudioContextBinding::{ + AudioContextLatencyCategory, AudioContextMethods, +}; +use crate::dom::bindings::codegen::Bindings::AudioContextBinding::{ + AudioContextOptions, AudioTimestamp, +}; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::AudioNodeOptions; +use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::AudioContextState; +use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContextBinding::BaseAudioContextMethods; +use crate::dom::bindings::codegen::UnionTypes::AudioContextLatencyCategoryOrDouble; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::mediaelementaudiosourcenode::MediaElementAudioSourceNode; +use crate::dom::mediastream::MediaStream; +use crate::dom::mediastreamaudiodestinationnode::MediaStreamAudioDestinationNode; +use crate::dom::mediastreamaudiosourcenode::MediaStreamAudioSourceNode; +use crate::dom::mediastreamtrack::MediaStreamTrack; +use crate::dom::mediastreamtrackaudiosourcenode::MediaStreamTrackAudioSourceNode; +use crate::dom::promise::Promise; +use crate::dom::window::Window; +use crate::realms::InRealm; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use msg::constellation_msg::PipelineId; +use servo_media::audio::context::{LatencyCategory, ProcessingState, RealTimeAudioContextOptions}; +use std::rc::Rc; + +#[dom_struct] +pub struct AudioContext { + context: BaseAudioContext, + latency_hint: AudioContextLatencyCategory, + /// https://webaudio.github.io/web-audio-api/#dom-audiocontext-baselatency + base_latency: f64, + /// https://webaudio.github.io/web-audio-api/#dom-audiocontext-outputlatency + output_latency: f64, +} + +impl AudioContext { + #[allow(unrooted_must_root)] + // https://webaudio.github.io/web-audio-api/#AudioContext-constructors + fn new_inherited(options: &AudioContextOptions, pipeline_id: PipelineId) -> AudioContext { + // Steps 1-3. + let context = BaseAudioContext::new_inherited( + BaseAudioContextOptions::AudioContext(options.into()), + pipeline_id, + ); + + // Step 4.1. + let latency_hint = match options.latencyHint { + AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => category, + AudioContextLatencyCategoryOrDouble::Double(_) => { + AudioContextLatencyCategory::Interactive + }, // TODO + }; + + // Step 4.2. The sample rate is set during the creation of the BaseAudioContext. + // servo-media takes care of setting the default sample rate of the output device + // and of resampling the audio output if needed. + + // Steps 5 and 6 of the construction algorithm will happen in `resume`, + // after reflecting dom object. + + AudioContext { + context, + latency_hint, + base_latency: 0., // TODO + output_latency: 0., // TODO + } + } + + #[allow(unrooted_must_root)] + pub fn new(window: &Window, options: &AudioContextOptions) -> DomRoot<AudioContext> { + let pipeline_id = window.pipeline_id(); + let context = AudioContext::new_inherited(options, pipeline_id); + let context = reflect_dom_object(Box::new(context), window); + context.resume(); + context + } + + // https://webaudio.github.io/web-audio-api/#AudioContext-constructors + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + options: &AudioContextOptions, + ) -> Fallible<DomRoot<AudioContext>> { + Ok(AudioContext::new(window, options)) + } + + fn resume(&self) { + // Step 5. + if self.context.is_allowed_to_start() { + // Step 6. + self.context.resume(); + } + } + + pub fn base(&self) -> DomRoot<BaseAudioContext> { + DomRoot::from_ref(&self.context) + } +} + +impl AudioContextMethods for AudioContext { + // https://webaudio.github.io/web-audio-api/#dom-audiocontext-baselatency + fn BaseLatency(&self) -> Finite<f64> { + Finite::wrap(self.base_latency) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiocontext-outputlatency + fn OutputLatency(&self) -> Finite<f64> { + Finite::wrap(self.output_latency) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiocontext-outputlatency + fn GetOutputTimestamp(&self) -> AudioTimestamp { + // TODO + AudioTimestamp { + contextTime: Some(Finite::wrap(0.)), + performanceTime: Some(Finite::wrap(0.)), + } + } + + // https://webaudio.github.io/web-audio-api/#dom-audiocontext-suspend + fn Suspend(&self, comp: InRealm) -> Rc<Promise> { + // Step 1. + let promise = Promise::new_in_current_realm(&self.global(), comp); + + // Step 2. + if self.context.control_thread_state() == ProcessingState::Closed { + promise.reject_error(Error::InvalidState); + return promise; + } + + // Step 3. + if self.context.State() == AudioContextState::Suspended { + promise.resolve_native(&()); + return promise; + } + + // Steps 4 and 5. + let window = DomRoot::downcast::<Window>(self.global()).unwrap(); + let task_source = window.task_manager().dom_manipulation_task_source(); + let trusted_promise = TrustedPromise::new(promise.clone()); + match self.context.audio_context_impl().lock().unwrap().suspend() { + Ok(_) => { + let base_context = Trusted::new(&self.context); + let context = Trusted::new(self); + let _ = task_source.queue( + task!(suspend_ok: move || { + let base_context = base_context.root(); + let context = context.root(); + let promise = trusted_promise.root(); + promise.resolve_native(&()); + if base_context.State() != AudioContextState::Suspended { + base_context.set_state_attribute(AudioContextState::Suspended); + let window = DomRoot::downcast::<Window>(context.global()).unwrap(); + window.task_manager().dom_manipulation_task_source().queue_simple_event( + context.upcast(), + atom!("statechange"), + &window + ); + } + }), + window.upcast(), + ); + }, + Err(_) => { + // The spec does not define the error case and `suspend` should + // never fail, but we handle the case here for completion. + let _ = task_source.queue( + task!(suspend_error: move || { + let promise = trusted_promise.root(); + promise.reject_error(Error::Type("Something went wrong".to_owned())); + }), + window.upcast(), + ); + }, + }; + + // Step 6. + promise + } + + // https://webaudio.github.io/web-audio-api/#dom-audiocontext-close + fn Close(&self, comp: InRealm) -> Rc<Promise> { + // Step 1. + let promise = Promise::new_in_current_realm(&self.global(), comp); + + // Step 2. + if self.context.control_thread_state() == ProcessingState::Closed { + promise.reject_error(Error::InvalidState); + return promise; + } + + // Step 3. + if self.context.State() == AudioContextState::Closed { + promise.resolve_native(&()); + return promise; + } + + // Steps 4 and 5. + let window = DomRoot::downcast::<Window>(self.global()).unwrap(); + let task_source = window.task_manager().dom_manipulation_task_source(); + let trusted_promise = TrustedPromise::new(promise.clone()); + match self.context.audio_context_impl().lock().unwrap().close() { + Ok(_) => { + let base_context = Trusted::new(&self.context); + let context = Trusted::new(self); + let _ = task_source.queue( + task!(suspend_ok: move || { + let base_context = base_context.root(); + let context = context.root(); + let promise = trusted_promise.root(); + promise.resolve_native(&()); + if base_context.State() != AudioContextState::Closed { + base_context.set_state_attribute(AudioContextState::Closed); + let window = DomRoot::downcast::<Window>(context.global()).unwrap(); + window.task_manager().dom_manipulation_task_source().queue_simple_event( + context.upcast(), + atom!("statechange"), + &window + ); + } + }), + window.upcast(), + ); + }, + Err(_) => { + // The spec does not define the error case and `suspend` should + // never fail, but we handle the case here for completion. + let _ = task_source.queue( + task!(suspend_error: move || { + let promise = trusted_promise.root(); + promise.reject_error(Error::Type("Something went wrong".to_owned())); + }), + window.upcast(), + ); + }, + }; + + // Step 6. + promise + } + + /// https://webaudio.github.io/web-audio-api/#dom-audiocontext-createmediaelementsource + fn CreateMediaElementSource( + &self, + media_element: &HTMLMediaElement, + ) -> Fallible<DomRoot<MediaElementAudioSourceNode>> { + let global = self.global(); + let window = global.as_window(); + MediaElementAudioSourceNode::new(window, self, media_element) + } + + /// https://webaudio.github.io/web-audio-api/#dom-audiocontext-createmediastreamsource + fn CreateMediaStreamSource( + &self, + stream: &MediaStream, + ) -> Fallible<DomRoot<MediaStreamAudioSourceNode>> { + let global = self.global(); + let window = global.as_window(); + MediaStreamAudioSourceNode::new(window, self, stream) + } + + /// https://webaudio.github.io/web-audio-api/#dom-audiocontext-createmediastreamtracksource + fn CreateMediaStreamTrackSource( + &self, + track: &MediaStreamTrack, + ) -> Fallible<DomRoot<MediaStreamTrackAudioSourceNode>> { + let global = self.global(); + let window = global.as_window(); + MediaStreamTrackAudioSourceNode::new(window, self, track) + } + + /// https://webaudio.github.io/web-audio-api/#dom-audiocontext-createmediastreamdestination + fn CreateMediaStreamDestination(&self) -> Fallible<DomRoot<MediaStreamAudioDestinationNode>> { + let global = self.global(); + let window = global.as_window(); + MediaStreamAudioDestinationNode::new(window, self, &AudioNodeOptions::empty()) + } +} + +impl From<AudioContextLatencyCategory> for LatencyCategory { + fn from(category: AudioContextLatencyCategory) -> Self { + match category { + AudioContextLatencyCategory::Balanced => LatencyCategory::Balanced, + AudioContextLatencyCategory::Interactive => LatencyCategory::Interactive, + AudioContextLatencyCategory::Playback => LatencyCategory::Playback, + } + } +} + +impl<'a> From<&'a AudioContextOptions> for RealTimeAudioContextOptions { + fn from(options: &AudioContextOptions) -> Self { + Self { + sample_rate: *options.sampleRate.unwrap_or(Finite::wrap(44100.)), + latency_hint: match options.latencyHint { + AudioContextLatencyCategoryOrDouble::AudioContextLatencyCategory(category) => { + category.into() + }, + AudioContextLatencyCategoryOrDouble::Double(_) => LatencyCategory::Interactive, // TODO + }, + } + } +} diff --git a/components/script/dom/audiodestinationnode.rs b/components/script/dom/audiodestinationnode.rs new file mode 100644 index 00000000000..7e820240b63 --- /dev/null +++ b/components/script/dom/audiodestinationnode.rs @@ -0,0 +1,56 @@ +/* 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 crate::dom::audionode::{AudioNode, MAX_CHANNEL_COUNT}; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioDestinationNodeBinding::AudioDestinationNodeMethods; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::AudioNodeOptions; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct AudioDestinationNode { + node: AudioNode, +} + +impl AudioDestinationNode { + fn new_inherited( + context: &BaseAudioContext, + options: &AudioNodeOptions, + ) -> AudioDestinationNode { + let node_options = + options.unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers); + AudioDestinationNode { + node: AudioNode::new_inherited_for_id( + context.destination_node(), + context, + node_options, + 1, + 1, + ), + } + } + + #[allow(unrooted_must_root)] + pub fn new( + global: &GlobalScope, + context: &BaseAudioContext, + options: &AudioNodeOptions, + ) -> DomRoot<AudioDestinationNode> { + let node = AudioDestinationNode::new_inherited(context, options); + reflect_dom_object(Box::new(node), global) + } +} + +impl AudioDestinationNodeMethods for AudioDestinationNode { + // https://webaudio.github.io/web-audio-api/#dom-audiodestinationnode-maxchannelcount + fn MaxChannelCount(&self) -> u32 { + MAX_CHANNEL_COUNT + } +} diff --git a/components/script/dom/audiolistener.rs b/components/script/dom/audiolistener.rs new file mode 100644 index 00000000000..2256efaf558 --- /dev/null +++ b/components/script/dom/audiolistener.rs @@ -0,0 +1,222 @@ +/* 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 crate::dom::audioparam::AudioParam; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioListenerBinding::AudioListenerMethods; +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::window::Window; + +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AudioParamMethods; +use dom_struct::dom_struct; +use servo_media::audio::param::{ParamDir, ParamType}; +use std::f32; + +#[dom_struct] +pub struct AudioListener { + reflector_: Reflector, + position_x: Dom<AudioParam>, + position_y: Dom<AudioParam>, + position_z: Dom<AudioParam>, + forward_x: Dom<AudioParam>, + forward_y: Dom<AudioParam>, + forward_z: Dom<AudioParam>, + up_x: Dom<AudioParam>, + up_y: Dom<AudioParam>, + up_z: Dom<AudioParam>, +} + +impl AudioListener { + fn new_inherited(window: &Window, context: &BaseAudioContext) -> AudioListener { + let node = context.listener(); + + let position_x = AudioParam::new( + window, + context, + node, + ParamType::Position(ParamDir::X), + AutomationRate::A_rate, + 0., // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let position_y = AudioParam::new( + window, + context, + node, + ParamType::Position(ParamDir::Y), + AutomationRate::A_rate, + 0., // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let position_z = AudioParam::new( + window, + context, + node, + ParamType::Position(ParamDir::Z), + AutomationRate::A_rate, + 0., // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let forward_x = AudioParam::new( + window, + context, + node, + ParamType::Forward(ParamDir::X), + AutomationRate::A_rate, + 0., // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let forward_y = AudioParam::new( + window, + context, + node, + ParamType::Forward(ParamDir::Y), + AutomationRate::A_rate, + 0., // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let forward_z = AudioParam::new( + window, + context, + node, + ParamType::Forward(ParamDir::Z), + AutomationRate::A_rate, + -1., // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let up_x = AudioParam::new( + window, + context, + node, + ParamType::Up(ParamDir::X), + AutomationRate::A_rate, + 0., // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let up_y = AudioParam::new( + window, + context, + node, + ParamType::Up(ParamDir::Y), + AutomationRate::A_rate, + 1., // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let up_z = AudioParam::new( + window, + context, + node, + ParamType::Up(ParamDir::Z), + AutomationRate::A_rate, + 0., // default value + f32::MIN, // min value + f32::MAX, // max value + ); + + AudioListener { + reflector_: Reflector::new(), + position_x: Dom::from_ref(&position_x), + position_y: Dom::from_ref(&position_y), + position_z: Dom::from_ref(&position_z), + forward_x: Dom::from_ref(&forward_x), + forward_y: Dom::from_ref(&forward_y), + forward_z: Dom::from_ref(&forward_z), + up_x: Dom::from_ref(&up_x), + up_y: Dom::from_ref(&up_y), + up_z: Dom::from_ref(&up_z), + } + } + + #[allow(unrooted_must_root)] + pub fn new(window: &Window, context: &BaseAudioContext) -> DomRoot<AudioListener> { + let node = AudioListener::new_inherited(window, context); + reflect_dom_object(Box::new(node), window) + } +} + +#[allow(non_snake_case)] +impl AudioListenerMethods for AudioListener { + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-positionx + fn PositionX(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.position_x) + } + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-positiony + fn PositionY(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.position_y) + } + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-positionz + fn PositionZ(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.position_z) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-forwardx + fn ForwardX(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.forward_x) + } + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-forwardy + fn ForwardY(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.forward_y) + } + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-forwardz + fn ForwardZ(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.forward_z) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-upx + fn UpX(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.up_x) + } + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-upy + fn UpY(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.up_y) + } + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-upz + fn UpZ(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.up_z) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-setorientation + fn SetOrientation( + &self, + x: Finite<f32>, + y: Finite<f32>, + z: Finite<f32>, + xUp: Finite<f32>, + yUp: Finite<f32>, + zUp: Finite<f32>, + ) -> Fallible<DomRoot<AudioListener>> { + self.forward_x.SetValue(x); + self.forward_y.SetValue(y); + self.forward_z.SetValue(z); + self.up_x.SetValue(xUp); + self.up_y.SetValue(yUp); + self.up_z.SetValue(zUp); + Ok(DomRoot::from_ref(self)) + } + + // https://webaudio.github.io/web-audio-api/#dom-audiolistener-setposition + fn SetPosition( + &self, + x: Finite<f32>, + y: Finite<f32>, + z: Finite<f32>, + ) -> Fallible<DomRoot<AudioListener>> { + self.position_x.SetValue(x); + self.position_y.SetValue(y); + self.position_z.SetValue(z); + Ok(DomRoot::from_ref(self)) + } +} diff --git a/components/script/dom/audionode.rs b/components/script/dom/audionode.rs new file mode 100644 index 00000000000..841b99d2d8f --- /dev/null +++ b/components/script/dom/audionode.rs @@ -0,0 +1,419 @@ +/* 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 crate::dom::audioparam::AudioParam; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + AudioNodeMethods, AudioNodeOptions, +}; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::InheritTypes::{ + AudioNodeTypeId, AudioScheduledSourceNodeTypeId, EventTargetTypeId, +}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::eventtarget::EventTarget; +use dom_struct::dom_struct; +use servo_media::audio::graph::NodeId; +use servo_media::audio::node::ChannelCountMode as ServoMediaChannelCountMode; +use servo_media::audio::node::ChannelInterpretation as ServoMediaChannelInterpretation; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage, ChannelInfo}; +use std::cell::Cell; + +// 32 is the minimum required by the spec for createBuffer() and the deprecated +// createScriptProcessor() and matches what is used by Blink and Gecko. +// The limit protects against large memory allocations. +pub const MAX_CHANNEL_COUNT: u32 = 32; + +#[dom_struct] +pub struct AudioNode { + eventtarget: EventTarget, + #[ignore_malloc_size_of = "servo_media"] + node_id: NodeId, + context: Dom<BaseAudioContext>, + number_of_inputs: u32, + number_of_outputs: u32, + channel_count: Cell<u32>, + channel_count_mode: Cell<ChannelCountMode>, + channel_interpretation: Cell<ChannelInterpretation>, +} + +impl AudioNode { + pub fn new_inherited( + node_type: AudioNodeInit, + context: &BaseAudioContext, + options: UnwrappedAudioNodeOptions, + number_of_inputs: u32, + number_of_outputs: u32, + ) -> Fallible<AudioNode> { + if options.count == 0 || options.count > MAX_CHANNEL_COUNT { + return Err(Error::NotSupported); + } + let ch = ChannelInfo { + count: options.count as u8, + mode: options.mode.into(), + interpretation: options.interpretation.into(), + }; + let node_id = context + .audio_context_impl() + .lock() + .unwrap() + .create_node(node_type, ch); + Ok(AudioNode::new_inherited_for_id( + node_id, + context, + options, + number_of_inputs, + number_of_outputs, + )) + } + + pub fn new_inherited_for_id( + node_id: NodeId, + context: &BaseAudioContext, + options: UnwrappedAudioNodeOptions, + number_of_inputs: u32, + number_of_outputs: u32, + ) -> AudioNode { + AudioNode { + eventtarget: EventTarget::new_inherited(), + node_id, + context: Dom::from_ref(context), + number_of_inputs, + number_of_outputs, + channel_count: Cell::new(options.count), + channel_count_mode: Cell::new(options.mode), + channel_interpretation: Cell::new(options.interpretation), + } + } + + pub fn message(&self, message: AudioNodeMessage) { + self.context + .audio_context_impl() + .lock() + .unwrap() + .message_node(self.node_id, message); + } + + pub fn node_id(&self) -> NodeId { + self.node_id + } +} + +impl AudioNodeMethods for AudioNode { + // https://webaudio.github.io/web-audio-api/#dom-audionode-connect + fn Connect( + &self, + destination: &AudioNode, + output: u32, + input: u32, + ) -> Fallible<DomRoot<AudioNode>> { + if self.context != destination.context { + return Err(Error::InvalidAccess); + } + + if output >= self.NumberOfOutputs() || input >= destination.NumberOfInputs() { + return Err(Error::IndexSize); + } + + // servo-media takes care of ignoring duplicated connections. + + self.context + .audio_context_impl() + .lock() + .unwrap() + .connect_ports( + self.node_id().output(output), + destination.node_id().input(input), + ); + + Ok(DomRoot::from_ref(destination)) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-connect-destinationparam-output + fn Connect_(&self, dest: &AudioParam, output: u32) -> Fallible<()> { + if self.context != dest.context() { + return Err(Error::InvalidAccess); + } + + if output >= self.NumberOfOutputs() { + return Err(Error::IndexSize); + } + + // servo-media takes care of ignoring duplicated connections. + + self.context + .audio_context_impl() + .lock() + .unwrap() + .connect_ports( + self.node_id().output(output), + dest.node_id().param(dest.param_type()), + ); + + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect + fn Disconnect(&self) -> ErrorResult { + self.context + .audio_context_impl() + .lock() + .unwrap() + .disconnect_all_from(self.node_id()); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-output + fn Disconnect_(&self, out: u32) -> ErrorResult { + self.context + .audio_context_impl() + .lock() + .unwrap() + .disconnect_output(self.node_id().output(out)); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode + fn Disconnect__(&self, to: &AudioNode) -> ErrorResult { + self.context + .audio_context_impl() + .lock() + .unwrap() + .disconnect_between(self.node_id(), to.node_id()); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode-output + fn Disconnect___(&self, to: &AudioNode, out: u32) -> ErrorResult { + self.context + .audio_context_impl() + .lock() + .unwrap() + .disconnect_output_between(self.node_id().output(out), to.node_id()); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode-output-input + fn Disconnect____(&self, to: &AudioNode, out: u32, inp: u32) -> ErrorResult { + self.context + .audio_context_impl() + .lock() + .unwrap() + .disconnect_output_between_to(self.node_id().output(out), to.node_id().input(inp)); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect + fn Disconnect_____(&self, param: &AudioParam) -> ErrorResult { + self.context + .audio_context_impl() + .lock() + .unwrap() + .disconnect_to(self.node_id(), param.node_id().param(param.param_type())); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect + fn Disconnect______(&self, param: &AudioParam, out: u32) -> ErrorResult { + self.context + .audio_context_impl() + .lock() + .unwrap() + .disconnect_output_between_to( + self.node_id().output(out), + param.node_id().param(param.param_type()), + ); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-context + fn Context(&self) -> DomRoot<BaseAudioContext> { + DomRoot::from_ref(&self.context) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-numberofinputs + fn NumberOfInputs(&self) -> u32 { + self.number_of_inputs + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-numberofoutputs + fn NumberOfOutputs(&self) -> u32 { + self.number_of_outputs + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-channelcount + fn ChannelCount(&self) -> u32 { + self.channel_count.get() + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-channelcount + fn SetChannelCount(&self, value: u32) -> ErrorResult { + match self.upcast::<EventTarget>().type_id() { + EventTargetTypeId::AudioNode(AudioNodeTypeId::AudioDestinationNode) => { + if self.context.is_offline() { + return Err(Error::InvalidState); + } else if value < 1 || value > MAX_CHANNEL_COUNT { + return Err(Error::IndexSize); + } + }, + EventTargetTypeId::AudioNode(AudioNodeTypeId::PannerNode) => { + if value > 2 { + return Err(Error::NotSupported); + } + }, + EventTargetTypeId::AudioNode(AudioNodeTypeId::AudioScheduledSourceNode( + AudioScheduledSourceNodeTypeId::StereoPannerNode, + )) => { + if value > 2 { + return Err(Error::NotSupported); + } + }, + EventTargetTypeId::AudioNode(AudioNodeTypeId::ChannelMergerNode) => { + return Err(Error::InvalidState); + }, + EventTargetTypeId::AudioNode(AudioNodeTypeId::ChannelSplitterNode) => { + return Err(Error::InvalidState); + }, + // XXX We do not support any of the other AudioNodes with + // constraints yet. Add more cases here as we add support + // for new AudioNodes. + _ => (), + }; + + if value == 0 || value > MAX_CHANNEL_COUNT { + return Err(Error::NotSupported); + } + + self.channel_count.set(value); + self.message(AudioNodeMessage::SetChannelCount(value as u8)); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-channelcountmode + fn ChannelCountMode(&self) -> ChannelCountMode { + self.channel_count_mode.get() + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-channelcountmode + fn SetChannelCountMode(&self, value: ChannelCountMode) -> ErrorResult { + // Channel count mode has no effect for nodes with no inputs. + if self.number_of_inputs == 0 { + return Ok(()); + } + + match self.upcast::<EventTarget>().type_id() { + EventTargetTypeId::AudioNode(AudioNodeTypeId::AudioDestinationNode) => { + if self.context.is_offline() { + return Err(Error::InvalidState); + } + }, + EventTargetTypeId::AudioNode(AudioNodeTypeId::PannerNode) => { + if value == ChannelCountMode::Max { + return Err(Error::NotSupported); + } + }, + EventTargetTypeId::AudioNode(AudioNodeTypeId::AudioScheduledSourceNode( + AudioScheduledSourceNodeTypeId::StereoPannerNode, + )) => { + if value == ChannelCountMode::Max { + return Err(Error::NotSupported); + } + }, + EventTargetTypeId::AudioNode(AudioNodeTypeId::ChannelMergerNode) => { + return Err(Error::InvalidState); + }, + EventTargetTypeId::AudioNode(AudioNodeTypeId::ChannelSplitterNode) => { + return Err(Error::InvalidState); + }, + // XXX We do not support any of the other AudioNodes with + // constraints yet. Add more cases here as we add support + // for new AudioNodes. + _ => (), + }; + + self.channel_count_mode.set(value); + self.message(AudioNodeMessage::SetChannelMode(value.into())); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-channelinterpretation + fn ChannelInterpretation(&self) -> ChannelInterpretation { + self.channel_interpretation.get() + } + + // https://webaudio.github.io/web-audio-api/#dom-audionode-channelinterpretation + fn SetChannelInterpretation(&self, value: ChannelInterpretation) -> ErrorResult { + // Channel interpretation mode has no effect for nodes with no inputs. + if self.number_of_inputs == 0 { + return Ok(()); + } + + match self.upcast::<EventTarget>().type_id() { + EventTargetTypeId::AudioNode(AudioNodeTypeId::ChannelSplitterNode) => { + return Err(Error::InvalidState); + }, + _ => (), + }; + + self.channel_interpretation.set(value); + self.message(AudioNodeMessage::SetChannelInterpretation(value.into())); + Ok(()) + } +} + +impl From<ChannelCountMode> for ServoMediaChannelCountMode { + fn from(mode: ChannelCountMode) -> Self { + match mode { + ChannelCountMode::Max => ServoMediaChannelCountMode::Max, + ChannelCountMode::Clamped_max => ServoMediaChannelCountMode::ClampedMax, + ChannelCountMode::Explicit => ServoMediaChannelCountMode::Explicit, + } + } +} + +impl From<ChannelInterpretation> for ServoMediaChannelInterpretation { + fn from(interpretation: ChannelInterpretation) -> Self { + match interpretation { + ChannelInterpretation::Discrete => ServoMediaChannelInterpretation::Discrete, + ChannelInterpretation::Speakers => ServoMediaChannelInterpretation::Speakers, + } + } +} + +impl AudioNodeOptions { + pub fn unwrap_or( + &self, + count: u32, + mode: ChannelCountMode, + interpretation: ChannelInterpretation, + ) -> UnwrappedAudioNodeOptions { + UnwrappedAudioNodeOptions { + count: self.channelCount.unwrap_or(count), + mode: self.channelCountMode.unwrap_or(mode), + interpretation: self.channelInterpretation.unwrap_or(interpretation), + } + } +} + +/// Each node has a set of defaults, so this lets us work with them +/// easily without having to deal with the Options +pub struct UnwrappedAudioNodeOptions { + pub count: u32, + pub mode: ChannelCountMode, + pub interpretation: ChannelInterpretation, +} + +impl Default for UnwrappedAudioNodeOptions { + fn default() -> Self { + UnwrappedAudioNodeOptions { + count: 2, + mode: ChannelCountMode::Max, + interpretation: ChannelInterpretation::Speakers, + } + } +} diff --git a/components/script/dom/audioparam.rs b/components/script/dom/audioparam.rs new file mode 100644 index 00000000000..dd0e72f7bcf --- /dev/null +++ b/components/script/dom/audioparam.rs @@ -0,0 +1,308 @@ +/* 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 crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::{ + AudioParamMethods, AutomationRate, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::graph::NodeId; +use servo_media::audio::node::AudioNodeMessage; +use servo_media::audio::param::{ParamRate, ParamType, RampKind, UserAutomationEvent}; +use std::cell::Cell; +use std::sync::mpsc; + +#[dom_struct] +pub struct AudioParam { + reflector_: Reflector, + context: Dom<BaseAudioContext>, + #[ignore_malloc_size_of = "servo_media"] + node: NodeId, + #[ignore_malloc_size_of = "servo_media"] + param: ParamType, + automation_rate: Cell<AutomationRate>, + default_value: f32, + min_value: f32, + max_value: f32, +} + +impl AudioParam { + pub fn new_inherited( + context: &BaseAudioContext, + node: NodeId, + param: ParamType, + automation_rate: AutomationRate, + default_value: f32, + min_value: f32, + max_value: f32, + ) -> AudioParam { + AudioParam { + reflector_: Reflector::new(), + context: Dom::from_ref(context), + node, + param, + automation_rate: Cell::new(automation_rate), + default_value, + min_value, + max_value, + } + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + node: NodeId, + param: ParamType, + automation_rate: AutomationRate, + default_value: f32, + min_value: f32, + max_value: f32, + ) -> DomRoot<AudioParam> { + let audio_param = AudioParam::new_inherited( + context, + node, + param, + automation_rate, + default_value, + min_value, + max_value, + ); + reflect_dom_object(Box::new(audio_param), window) + } + + fn message_node(&self, message: AudioNodeMessage) { + self.context + .audio_context_impl() + .lock() + .unwrap() + .message_node(self.node, message); + } + + pub fn context(&self) -> &BaseAudioContext { + &self.context + } + + pub fn node_id(&self) -> NodeId { + self.node + } + + pub fn param_type(&self) -> ParamType { + self.param + } +} + +impl AudioParamMethods for AudioParam { + // https://webaudio.github.io/web-audio-api/#dom-audioparam-automationrate + fn AutomationRate(&self) -> AutomationRate { + self.automation_rate.get() + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-automationrate + fn SetAutomationRate(&self, automation_rate: AutomationRate) { + self.automation_rate.set(automation_rate); + self.message_node(AudioNodeMessage::SetParamRate( + self.param, + automation_rate.into(), + )); + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-value + fn Value(&self) -> Finite<f32> { + let (tx, rx) = mpsc::channel(); + self.message_node(AudioNodeMessage::GetParamValue(self.param, tx)); + Finite::wrap(rx.recv().unwrap()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-value + fn SetValue(&self, value: Finite<f32>) { + self.message_node(AudioNodeMessage::SetParam( + self.param, + UserAutomationEvent::SetValue(*value), + )); + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-defaultvalue + fn DefaultValue(&self) -> Finite<f32> { + Finite::wrap(self.default_value) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-minvalue + fn MinValue(&self) -> Finite<f32> { + Finite::wrap(self.min_value) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-maxvalue + fn MaxValue(&self) -> Finite<f32> { + Finite::wrap(self.max_value) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-setvalueattime + fn SetValueAtTime( + &self, + value: Finite<f32>, + start_time: Finite<f64>, + ) -> Fallible<DomRoot<AudioParam>> { + if *start_time < 0. { + return Err(Error::Range(format!( + "start time {} should not be negative", + *start_time + ))); + } + self.message_node(AudioNodeMessage::SetParam( + self.param, + UserAutomationEvent::SetValueAtTime(*value, *start_time), + )); + Ok(DomRoot::from_ref(self)) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-linearramptovalueattime + fn LinearRampToValueAtTime( + &self, + value: Finite<f32>, + end_time: Finite<f64>, + ) -> Fallible<DomRoot<AudioParam>> { + if *end_time < 0. { + return Err(Error::Range(format!( + "end time {} should not be negative", + *end_time + ))); + } + self.message_node(AudioNodeMessage::SetParam( + self.param, + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, *value, *end_time), + )); + Ok(DomRoot::from_ref(self)) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-exponentialramptovalueattime + fn ExponentialRampToValueAtTime( + &self, + value: Finite<f32>, + end_time: Finite<f64>, + ) -> Fallible<DomRoot<AudioParam>> { + if *end_time < 0. { + return Err(Error::Range(format!( + "end time {} should not be negative", + *end_time + ))); + } + if *value == 0. { + return Err(Error::Range(format!( + "target value {} should not be 0", + *value + ))); + } + self.message_node(AudioNodeMessage::SetParam( + self.param, + UserAutomationEvent::RampToValueAtTime(RampKind::Exponential, *value, *end_time), + )); + Ok(DomRoot::from_ref(self)) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-settargetattime + fn SetTargetAtTime( + &self, + target: Finite<f32>, + start_time: Finite<f64>, + time_constant: Finite<f32>, + ) -> Fallible<DomRoot<AudioParam>> { + if *start_time < 0. { + return Err(Error::Range(format!( + "start time {} should not be negative", + *start_time + ))); + } + if *time_constant < 0. { + return Err(Error::Range(format!( + "time constant {} should not be negative", + *time_constant + ))); + } + self.message_node(AudioNodeMessage::SetParam( + self.param, + UserAutomationEvent::SetTargetAtTime(*target, *start_time, (*time_constant).into()), + )); + Ok(DomRoot::from_ref(self)) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-setvaluecurveattime + fn SetValueCurveAtTime( + &self, + values: Vec<Finite<f32>>, + start_time: Finite<f64>, + end_time: Finite<f64>, + ) -> Fallible<DomRoot<AudioParam>> { + if *start_time < 0. { + return Err(Error::Range(format!( + "start time {} should not be negative", + *start_time + ))); + } + if values.len() < 2. as usize { + return Err(Error::InvalidState); + } + + if *end_time < 0. { + return Err(Error::Range(format!( + "end time {} should not be negative", + *end_time + ))); + } + self.message_node(AudioNodeMessage::SetParam( + self.param, + UserAutomationEvent::SetValueCurveAtTime( + values.into_iter().map(|v| *v).collect(), + *start_time, + *end_time, + ), + )); + Ok(DomRoot::from_ref(self)) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-cancelscheduledvalues + fn CancelScheduledValues(&self, cancel_time: Finite<f64>) -> Fallible<DomRoot<AudioParam>> { + if *cancel_time < 0. { + return Err(Error::Range(format!( + "cancel time {} should not be negative", + *cancel_time + ))); + } + self.message_node(AudioNodeMessage::SetParam( + self.param, + UserAutomationEvent::CancelScheduledValues(*cancel_time), + )); + Ok(DomRoot::from_ref(self)) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioparam-cancelandholdattime + fn CancelAndHoldAtTime(&self, cancel_time: Finite<f64>) -> Fallible<DomRoot<AudioParam>> { + if *cancel_time < 0. { + return Err(Error::Range(format!( + "cancel time {} should not be negative", + *cancel_time + ))); + } + self.message_node(AudioNodeMessage::SetParam( + self.param, + UserAutomationEvent::CancelAndHoldAtTime(*cancel_time), + )); + Ok(DomRoot::from_ref(self)) + } +} + +// https://webaudio.github.io/web-audio-api/#enumdef-automationrate +impl From<AutomationRate> for ParamRate { + fn from(rate: AutomationRate) -> Self { + match rate { + AutomationRate::A_rate => ParamRate::ARate, + AutomationRate::K_rate => ParamRate::KRate, + } + } +} diff --git a/components/script/dom/audioscheduledsourcenode.rs b/components/script/dom/audioscheduledsourcenode.rs new file mode 100644 index 00000000000..ab23b5660b7 --- /dev/null +++ b/components/script/dom/audioscheduledsourcenode.rs @@ -0,0 +1,122 @@ +/* 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 crate::dom::audionode::{AudioNode, UnwrappedAudioNodeOptions}; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioScheduledSourceNodeBinding::AudioScheduledSourceNodeMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use servo_media::audio::node::OnEndedCallback; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage, AudioScheduledSourceNodeMessage}; +use std::cell::Cell; + +#[dom_struct] +pub struct AudioScheduledSourceNode { + node: AudioNode, + has_start: Cell<bool>, + has_stop: Cell<bool>, +} + +impl AudioScheduledSourceNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + node_type: AudioNodeInit, + context: &BaseAudioContext, + options: UnwrappedAudioNodeOptions, + number_of_inputs: u32, + number_of_outputs: u32, + ) -> Fallible<AudioScheduledSourceNode> { + Ok(AudioScheduledSourceNode { + node: AudioNode::new_inherited( + node_type, + context, + options, + number_of_inputs, + number_of_outputs, + )?, + has_start: Cell::new(false), + has_stop: Cell::new(false), + }) + } + + pub fn node(&self) -> &AudioNode { + &self.node + } + + pub fn has_start(&self) -> bool { + self.has_start.get() + } +} + +impl AudioScheduledSourceNodeMethods for AudioScheduledSourceNode { + // https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-onended + event_handler!(ended, GetOnended, SetOnended); + + // https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-start + fn Start(&self, when: Finite<f64>) -> Fallible<()> { + if *when < 0. { + return Err(Error::Range("'when' must be a positive value".to_owned())); + } + + if self.has_start.get() || self.has_stop.get() { + return Err(Error::InvalidState); + } + + let this = Trusted::new(self); + let global = self.global(); + let window = global.as_window(); + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let callback = OnEndedCallback::new(move || { + let _ = task_source.queue_with_canceller( + task!(ended: move || { + let this = this.root(); + let global = this.global(); + let window = global.as_window(); + window.task_manager().dom_manipulation_task_source().queue_simple_event( + this.upcast(), + atom!("ended"), + &window + ); + }), + &canceller, + ); + }); + + self.node() + .message(AudioNodeMessage::AudioScheduledSourceNode( + AudioScheduledSourceNodeMessage::RegisterOnEndedCallback(callback), + )); + + self.has_start.set(true); + self.node + .message(AudioNodeMessage::AudioScheduledSourceNode( + AudioScheduledSourceNodeMessage::Start(*when), + )); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-stop + fn Stop(&self, when: Finite<f64>) -> Fallible<()> { + if *when < 0. { + return Err(Error::Range("'when' must be a positive value".to_owned())); + } + + if !self.has_start.get() { + return Err(Error::InvalidState); + } + self.has_stop.set(true); + self.node + .message(AudioNodeMessage::AudioScheduledSourceNode( + AudioScheduledSourceNodeMessage::Stop(*when), + )); + Ok(()) + } +} diff --git a/components/script/dom/audiotrack.rs b/components/script/dom/audiotrack.rs new file mode 100644 index 00000000000..19bc6d0a6cd --- /dev/null +++ b/components/script/dom/audiotrack.rs @@ -0,0 +1,121 @@ +/* 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 crate::dom::audiotracklist::AudioTrackList; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AudioTrackBinding::AudioTrackMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct AudioTrack { + reflector_: Reflector, + id: DOMString, + kind: DOMString, + label: DOMString, + language: DOMString, + enabled: Cell<bool>, + track_list: DomRefCell<Option<Dom<AudioTrackList>>>, +} + +impl AudioTrack { + pub fn new_inherited( + id: DOMString, + kind: DOMString, + label: DOMString, + language: DOMString, + track_list: Option<&AudioTrackList>, + ) -> AudioTrack { + AudioTrack { + reflector_: Reflector::new(), + id: id.into(), + kind: kind.into(), + label: label.into(), + language: language.into(), + enabled: Cell::new(false), + track_list: DomRefCell::new(track_list.map(|t| Dom::from_ref(t))), + } + } + + pub fn new( + window: &Window, + id: DOMString, + kind: DOMString, + label: DOMString, + language: DOMString, + track_list: Option<&AudioTrackList>, + ) -> DomRoot<AudioTrack> { + reflect_dom_object( + Box::new(AudioTrack::new_inherited( + id, kind, label, language, track_list, + )), + window, + ) + } + + pub fn id(&self) -> DOMString { + self.id.clone() + } + + pub fn kind(&self) -> DOMString { + self.kind.clone() + } + + pub fn enabled(&self) -> bool { + self.enabled.get() + } + + pub fn set_enabled(&self, value: bool) { + self.enabled.set(value); + } + + pub fn add_track_list(&self, track_list: &AudioTrackList) { + *self.track_list.borrow_mut() = Some(Dom::from_ref(track_list)); + } + + pub fn remove_track_list(&self) { + *self.track_list.borrow_mut() = None; + } +} + +impl AudioTrackMethods for AudioTrack { + // https://html.spec.whatwg.org/multipage/#dom-audiotrack-id + fn Id(&self) -> DOMString { + self.id() + } + + // https://html.spec.whatwg.org/multipage/#dom-audiotrack-kind + fn Kind(&self) -> DOMString { + self.kind() + } + + // https://html.spec.whatwg.org/multipage/#dom-audiotrack-label + fn Label(&self) -> DOMString { + self.label.clone() + } + + // https://html.spec.whatwg.org/multipage/#dom-audiotrack-language + fn Language(&self) -> DOMString { + self.language.clone() + } + + // https://html.spec.whatwg.org/multipage/#dom-audiotrack-enabled + fn Enabled(&self) -> bool { + self.enabled() + } + + // https://html.spec.whatwg.org/multipage/#dom-audiotrack-enabled + fn SetEnabled(&self, value: bool) { + if let Some(list) = self.track_list.borrow().as_ref() { + if let Some(idx) = list.find(self) { + list.set_enabled(idx, value); + } + } + self.set_enabled(value); + } +} diff --git a/components/script/dom/audiotracklist.rs b/components/script/dom/audiotracklist.rs new file mode 100644 index 00000000000..6a7fd3b4ebb --- /dev/null +++ b/components/script/dom/audiotracklist.rs @@ -0,0 +1,146 @@ +/* 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 crate::dom::audiotrack::AudioTrack; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AudioTrackListBinding::AudioTrackListMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::window::Window; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct AudioTrackList { + eventtarget: EventTarget, + tracks: DomRefCell<Vec<Dom<AudioTrack>>>, + media_element: Option<Dom<HTMLMediaElement>>, +} + +impl AudioTrackList { + pub fn new_inherited( + tracks: &[&AudioTrack], + media_element: Option<&HTMLMediaElement>, + ) -> AudioTrackList { + AudioTrackList { + eventtarget: EventTarget::new_inherited(), + tracks: DomRefCell::new(tracks.iter().map(|track| Dom::from_ref(&**track)).collect()), + media_element: media_element.map(|m| Dom::from_ref(m)), + } + } + + pub fn new( + window: &Window, + tracks: &[&AudioTrack], + media_element: Option<&HTMLMediaElement>, + ) -> DomRoot<AudioTrackList> { + reflect_dom_object( + Box::new(AudioTrackList::new_inherited(tracks, media_element)), + window, + ) + } + + pub fn len(&self) -> usize { + self.tracks.borrow().len() + } + + pub fn find(&self, track: &AudioTrack) -> Option<usize> { + self.tracks.borrow().iter().position(|t| &**t == track) + } + + pub fn item(&self, idx: usize) -> Option<DomRoot<AudioTrack>> { + self.tracks + .borrow() + .get(idx) + .map(|track| DomRoot::from_ref(&**track)) + } + + pub fn enabled_index(&self) -> Option<usize> { + self.tracks + .borrow() + .iter() + .position(|track| track.enabled()) + } + + pub fn set_enabled(&self, idx: usize, value: bool) { + let track = match self.item(idx) { + Some(t) => t, + None => return, + }; + + // If the chosen tracks enabled status is the same as the new status, return early. + if track.enabled() == value { + return; + } + // Set the tracks enabled status. + track.set_enabled(value); + if let Some(media_element) = self.media_element.as_ref() { + media_element.set_audio_track(idx, value); + } + + // Queue a task to fire an event named change. + let global = &self.global(); + let this = Trusted::new(self); + let (source, canceller) = global + .as_window() + .task_manager() + .media_element_task_source_with_canceller(); + + let _ = source.queue_with_canceller( + task!(media_track_change: move || { + let this = this.root(); + this.upcast::<EventTarget>().fire_event(atom!("change")); + }), + &canceller, + ); + } + + pub fn add(&self, track: &AudioTrack) { + self.tracks.borrow_mut().push(Dom::from_ref(track)); + track.add_track_list(self); + } + + pub fn clear(&self) { + self.tracks + .borrow() + .iter() + .for_each(|t| t.remove_track_list()); + self.tracks.borrow_mut().clear(); + } +} + +impl AudioTrackListMethods for AudioTrackList { + // https://html.spec.whatwg.org/multipage/#dom-audiotracklist-length + fn Length(&self) -> u32 { + self.len() as u32 + } + + // https://html.spec.whatwg.org/multipage/#dom-tracklist-item + fn IndexedGetter(&self, idx: u32) -> Option<DomRoot<AudioTrack>> { + self.item(idx as usize) + } + + // https://html.spec.whatwg.org/multipage/#dom-audiotracklist-gettrackbyid + fn GetTrackById(&self, id: DOMString) -> Option<DomRoot<AudioTrack>> { + self.tracks + .borrow() + .iter() + .find(|track| track.id() == id) + .map(|track| DomRoot::from_ref(&**track)) + } + + // https://html.spec.whatwg.org/multipage/#handler-tracklist-onchange + event_handler!(change, GetOnchange, SetOnchange); + + // https://html.spec.whatwg.org/multipage/#handler-tracklist-onaddtrack + event_handler!(addtrack, GetOnaddtrack, SetOnaddtrack); + + // https://html.spec.whatwg.org/multipage/#handler-tracklist-onremovetrack + event_handler!(removetrack, GetOnremovetrack, SetOnremovetrack); +} diff --git a/components/script/dom/baseaudiocontext.rs b/components/script/dom/baseaudiocontext.rs new file mode 100644 index 00000000000..42836dd61f0 --- /dev/null +++ b/components/script/dom/baseaudiocontext.rs @@ -0,0 +1,578 @@ +/* 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 crate::dom::analysernode::AnalyserNode; +use crate::dom::audiobuffer::AudioBuffer; +use crate::dom::audiobuffersourcenode::AudioBufferSourceNode; +use crate::dom::audiodestinationnode::AudioDestinationNode; +use crate::dom::audiolistener::AudioListener; +use crate::dom::audionode::MAX_CHANNEL_COUNT; +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AnalyserNodeBinding::AnalyserOptions; +use crate::dom::bindings::codegen::Bindings::AudioBufferSourceNodeBinding::AudioBufferSourceOptions; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::AudioNodeOptions; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::AudioContextState; +use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContextMethods; +use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::DecodeErrorCallback; +use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::DecodeSuccessCallback; +use crate::dom::bindings::codegen::Bindings::BiquadFilterNodeBinding::BiquadFilterOptions; +use crate::dom::bindings::codegen::Bindings::ChannelMergerNodeBinding::ChannelMergerOptions; +use crate::dom::bindings::codegen::Bindings::ChannelSplitterNodeBinding::ChannelSplitterOptions; +use crate::dom::bindings::codegen::Bindings::ConstantSourceNodeBinding::ConstantSourceOptions; +use crate::dom::bindings::codegen::Bindings::GainNodeBinding::GainOptions; +use crate::dom::bindings::codegen::Bindings::OscillatorNodeBinding::OscillatorOptions; +use crate::dom::bindings::codegen::Bindings::PannerNodeBinding::PannerOptions; +use crate::dom::bindings::codegen::Bindings::StereoPannerNodeBinding::StereoPannerOptions; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::biquadfilternode::BiquadFilterNode; +use crate::dom::channelmergernode::ChannelMergerNode; +use crate::dom::channelsplitternode::ChannelSplitterNode; +use crate::dom::constantsourcenode::ConstantSourceNode; +use crate::dom::domexception::{DOMErrorName, DOMException}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::gainnode::GainNode; +use crate::dom::oscillatornode::OscillatorNode; +use crate::dom::pannernode::PannerNode; +use crate::dom::promise::Promise; +use crate::dom::stereopannernode::StereoPannerNode; +use crate::dom::window::Window; +use crate::realms::InRealm; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use js::rust::CustomAutoRooterGuard; +use js::typedarray::ArrayBuffer; +use msg::constellation_msg::PipelineId; +use servo_media::audio::context::{AudioContext, AudioContextOptions, ProcessingState}; +use servo_media::audio::context::{OfflineAudioContextOptions, RealTimeAudioContextOptions}; +use servo_media::audio::decoder::AudioDecoderCallbacks; +use servo_media::audio::graph::NodeId; +use servo_media::{ClientContextId, ServoMedia}; +use std::cell::Cell; +use std::collections::hash_map::Entry; +use std::collections::{HashMap, VecDeque}; +use std::mem; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use uuid::Uuid; + +#[allow(dead_code)] +pub enum BaseAudioContextOptions { + AudioContext(RealTimeAudioContextOptions), + OfflineAudioContext(OfflineAudioContextOptions), +} + +#[derive(JSTraceable)] +struct DecodeResolver { + pub promise: Rc<Promise>, + pub success_callback: Option<Rc<DecodeSuccessCallback>>, + pub error_callback: Option<Rc<DecodeErrorCallback>>, +} + +#[dom_struct] +pub struct BaseAudioContext { + eventtarget: EventTarget, + #[ignore_malloc_size_of = "servo_media"] + audio_context_impl: Arc<Mutex<AudioContext>>, + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-destination + destination: MutNullableDom<AudioDestinationNode>, + listener: MutNullableDom<AudioListener>, + /// Resume promises which are soon to be fulfilled by a queued task. + #[ignore_malloc_size_of = "promises are hard"] + in_flight_resume_promises_queue: DomRefCell<VecDeque<(Box<[Rc<Promise>]>, ErrorResult)>>, + /// https://webaudio.github.io/web-audio-api/#pendingresumepromises + #[ignore_malloc_size_of = "promises are hard"] + pending_resume_promises: DomRefCell<Vec<Rc<Promise>>>, + #[ignore_malloc_size_of = "promises are hard"] + decode_resolvers: DomRefCell<HashMap<String, DecodeResolver>>, + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate + sample_rate: f32, + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state + /// Although servo-media already keeps track of the control thread state, + /// we keep a state flag here as well. This is so that we can synchronously + /// throw when trying to do things on the context when the context has just + /// been "closed()". + state: Cell<AudioContextState>, + channel_count: u32, +} + +impl BaseAudioContext { + #[allow(unrooted_must_root)] + pub fn new_inherited( + options: BaseAudioContextOptions, + pipeline_id: PipelineId, + ) -> BaseAudioContext { + let (sample_rate, channel_count) = match options { + BaseAudioContextOptions::AudioContext(ref opt) => (opt.sample_rate, 2), + BaseAudioContextOptions::OfflineAudioContext(ref opt) => { + (opt.sample_rate, opt.channels) + }, + }; + + let client_context_id = + ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get()); + let context = BaseAudioContext { + eventtarget: EventTarget::new_inherited(), + audio_context_impl: ServoMedia::get() + .unwrap() + .create_audio_context(&client_context_id, options.into()), + destination: Default::default(), + listener: Default::default(), + in_flight_resume_promises_queue: Default::default(), + pending_resume_promises: Default::default(), + decode_resolvers: Default::default(), + sample_rate, + state: Cell::new(AudioContextState::Suspended), + channel_count: channel_count.into(), + }; + + context + } + + /// Tells whether this is an OfflineAudioContext or not. + pub fn is_offline(&self) -> bool { + false + } + + pub fn audio_context_impl(&self) -> Arc<Mutex<AudioContext>> { + self.audio_context_impl.clone() + } + + pub fn destination_node(&self) -> NodeId { + self.audio_context_impl.lock().unwrap().dest_node() + } + + pub fn listener(&self) -> NodeId { + self.audio_context_impl.lock().unwrap().listener() + } + + // https://webaudio.github.io/web-audio-api/#allowed-to-start + pub fn is_allowed_to_start(&self) -> bool { + self.state.get() == AudioContextState::Suspended + } + + fn push_pending_resume_promise(&self, promise: &Rc<Promise>) { + self.pending_resume_promises + .borrow_mut() + .push(promise.clone()); + } + + /// Takes the pending resume promises. + /// + /// The result with which these promises will be fulfilled is passed here + /// and this method returns nothing because we actually just move the + /// current list of pending resume promises to the + /// `in_flight_resume_promises_queue` field. + /// + /// Each call to this method must be followed by a call to + /// `fulfill_in_flight_resume_promises`, to actually fulfill the promises + /// which were taken and moved to the in-flight queue. + fn take_pending_resume_promises(&self, result: ErrorResult) { + let pending_resume_promises = + mem::replace(&mut *self.pending_resume_promises.borrow_mut(), vec![]); + self.in_flight_resume_promises_queue + .borrow_mut() + .push_back((pending_resume_promises.into(), result)); + } + + /// Fulfills the next in-flight resume promises queue after running a closure. + /// + /// See the comment on `take_pending_resume_promises` for why this method + /// does not take a list of promises to fulfill. Callers cannot just pop + /// the front list off of `in_flight_resume_promises_queue` and later fulfill + /// the promises because that would mean putting + /// `#[allow(unrooted_must_root)]` on even more functions, potentially + /// hiding actual safety bugs. + #[allow(unrooted_must_root)] + fn fulfill_in_flight_resume_promises<F>(&self, f: F) + where + F: FnOnce(), + { + let (promises, result) = self + .in_flight_resume_promises_queue + .borrow_mut() + .pop_front() + .expect("there should be at least one list of in flight resume promises"); + f(); + for promise in &*promises { + match result { + Ok(ref value) => promise.resolve_native(value), + Err(ref error) => promise.reject_error(error.clone()), + } + } + } + + /// Control thread processing state + pub fn control_thread_state(&self) -> ProcessingState { + self.audio_context_impl.lock().unwrap().state() + } + + /// Set audio context state + pub fn set_state_attribute(&self, state: AudioContextState) { + self.state.set(state); + } + + pub fn resume(&self) { + let global = self.global(); + let window = global.as_window(); + let task_source = window.task_manager().dom_manipulation_task_source(); + let this = Trusted::new(self); + // Set the rendering thread state to 'running' and start + // rendering the audio graph. + match self.audio_context_impl.lock().unwrap().resume() { + Ok(()) => { + self.take_pending_resume_promises(Ok(())); + let _ = task_source.queue( + task!(resume_success: move || { + let this = this.root(); + this.fulfill_in_flight_resume_promises(|| { + if this.state.get() != AudioContextState::Running { + this.state.set(AudioContextState::Running); + let window = DomRoot::downcast::<Window>(this.global()).unwrap(); + window.task_manager().dom_manipulation_task_source().queue_simple_event( + this.upcast(), + atom!("statechange"), + &window + ); + } + }); + }), + window.upcast(), + ); + }, + Err(()) => { + self.take_pending_resume_promises(Err(Error::Type( + "Something went wrong".to_owned(), + ))); + let _ = task_source.queue( + task!(resume_error: move || { + this.root().fulfill_in_flight_resume_promises(|| {}) + }), + window.upcast(), + ); + }, + } + } +} + +impl BaseAudioContextMethods for BaseAudioContext { + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate + fn SampleRate(&self) -> Finite<f32> { + Finite::wrap(self.sample_rate) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-currenttime + fn CurrentTime(&self) -> Finite<f64> { + let current_time = self.audio_context_impl.lock().unwrap().current_time(); + Finite::wrap(current_time) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state + fn State(&self) -> AudioContextState { + self.state.get() + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-resume + fn Resume(&self, comp: InRealm) -> Rc<Promise> { + // Step 1. + let promise = Promise::new_in_current_realm(&self.global(), comp); + + // Step 2. + if self.audio_context_impl.lock().unwrap().state() == ProcessingState::Closed { + promise.reject_error(Error::InvalidState); + return promise; + } + + // Step 3. + if self.state.get() == AudioContextState::Running { + promise.resolve_native(&()); + return promise; + } + + self.push_pending_resume_promise(&promise); + + // Step 4. + if !self.is_allowed_to_start() { + return promise; + } + + // Steps 5 and 6. + self.resume(); + + // Step 7. + promise + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-destination + fn Destination(&self) -> DomRoot<AudioDestinationNode> { + let global = self.global(); + self.destination.or_init(|| { + let mut options = AudioNodeOptions::empty(); + options.channelCount = Some(self.channel_count); + options.channelCountMode = Some(ChannelCountMode::Explicit); + options.channelInterpretation = Some(ChannelInterpretation::Speakers); + AudioDestinationNode::new(&global, self, &options) + }) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-listener + fn Listener(&self) -> DomRoot<AudioListener> { + let global = self.global(); + let window = global.as_window(); + self.listener.or_init(|| AudioListener::new(&window, self)) + } + + // https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-onstatechange + event_handler!(statechange, GetOnstatechange, SetOnstatechange); + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createoscillator + fn CreateOscillator(&self) -> Fallible<DomRoot<OscillatorNode>> { + OscillatorNode::new( + &self.global().as_window(), + &self, + &OscillatorOptions::empty(), + ) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-creategain + fn CreateGain(&self) -> Fallible<DomRoot<GainNode>> { + GainNode::new(&self.global().as_window(), &self, &GainOptions::empty()) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createpanner + fn CreatePanner(&self) -> Fallible<DomRoot<PannerNode>> { + PannerNode::new(&self.global().as_window(), &self, &PannerOptions::empty()) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createanalyser + fn CreateAnalyser(&self) -> Fallible<DomRoot<AnalyserNode>> { + AnalyserNode::new(&self.global().as_window(), &self, &AnalyserOptions::empty()) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbiquadfilter + fn CreateBiquadFilter(&self) -> Fallible<DomRoot<BiquadFilterNode>> { + BiquadFilterNode::new( + &self.global().as_window(), + &self, + &BiquadFilterOptions::empty(), + ) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createstereopanner + fn CreateStereoPanner(&self) -> Fallible<DomRoot<StereoPannerNode>> { + StereoPannerNode::new( + &self.global().as_window(), + &self, + &StereoPannerOptions::empty(), + ) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createconstantsource + fn CreateConstantSource(&self) -> Fallible<DomRoot<ConstantSourceNode>> { + ConstantSourceNode::new( + &self.global().as_window(), + &self, + &ConstantSourceOptions::empty(), + ) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelmerger + fn CreateChannelMerger(&self, count: u32) -> Fallible<DomRoot<ChannelMergerNode>> { + let mut opts = ChannelMergerOptions::empty(); + opts.numberOfInputs = count; + ChannelMergerNode::new(&self.global().as_window(), &self, &opts) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelsplitter + fn CreateChannelSplitter(&self, count: u32) -> Fallible<DomRoot<ChannelSplitterNode>> { + let mut opts = ChannelSplitterOptions::empty(); + opts.numberOfOutputs = count; + ChannelSplitterNode::new(&self.global().as_window(), &self, &opts) + } + + /// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer + fn CreateBuffer( + &self, + number_of_channels: u32, + length: u32, + sample_rate: Finite<f32>, + ) -> Fallible<DomRoot<AudioBuffer>> { + if number_of_channels <= 0 || + number_of_channels > MAX_CHANNEL_COUNT || + length <= 0 || + *sample_rate <= 0. + { + return Err(Error::NotSupported); + } + Ok(AudioBuffer::new( + &self.global().as_window(), + number_of_channels, + length, + *sample_rate, + None, + )) + } + + // https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffersource + fn CreateBufferSource(&self) -> Fallible<DomRoot<AudioBufferSourceNode>> { + AudioBufferSourceNode::new( + &self.global().as_window(), + &self, + &AudioBufferSourceOptions::empty(), + ) + } + + // https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata + fn DecodeAudioData( + &self, + audio_data: CustomAutoRooterGuard<ArrayBuffer>, + decode_success_callback: Option<Rc<DecodeSuccessCallback>>, + decode_error_callback: Option<Rc<DecodeErrorCallback>>, + comp: InRealm, + ) -> Rc<Promise> { + // Step 1. + let promise = Promise::new_in_current_realm(&self.global(), comp); + let global = self.global(); + let window = global.as_window(); + + if audio_data.len() > 0 { + // Step 2. + // XXX detach array buffer. + let uuid = Uuid::new_v4().to_simple().to_string(); + let uuid_ = uuid.clone(); + self.decode_resolvers.borrow_mut().insert( + uuid.clone(), + DecodeResolver { + promise: promise.clone(), + success_callback: decode_success_callback, + error_callback: decode_error_callback, + }, + ); + let audio_data = audio_data.to_vec(); + let decoded_audio = Arc::new(Mutex::new(Vec::new())); + let decoded_audio_ = decoded_audio.clone(); + let decoded_audio__ = decoded_audio.clone(); + // servo-media returns an audio channel position along + // with the AudioDecoderCallback progress callback, which + // may not be the same as the index of the decoded_audio + // Vec. + let channels = Arc::new(Mutex::new(HashMap::new())); + let this = Trusted::new(self); + let this_ = this.clone(); + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let (task_source_, canceller_) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let callbacks = AudioDecoderCallbacks::new() + .ready(move |channel_count| { + decoded_audio + .lock() + .unwrap() + .resize(channel_count as usize, Vec::new()); + }) + .progress(move |buffer, channel_pos_mask| { + let mut decoded_audio = decoded_audio_.lock().unwrap(); + let mut channels = channels.lock().unwrap(); + let channel = match channels.entry(channel_pos_mask) { + Entry::Occupied(entry) => *entry.get(), + Entry::Vacant(entry) => { + let x = (channel_pos_mask as f32).log2() as usize; + *entry.insert(x) + }, + }; + decoded_audio[channel].extend_from_slice((*buffer).as_ref()); + }) + .eos(move || { + let _ = task_source.queue_with_canceller( + task!(audio_decode_eos: move || { + let this = this.root(); + let decoded_audio = decoded_audio__.lock().unwrap(); + let length = if decoded_audio.len() >= 1 { + decoded_audio[0].len() + } else { + 0 + }; + let buffer = AudioBuffer::new( + &this.global().as_window(), + decoded_audio.len() as u32 /* number of channels */, + length as u32, + this.sample_rate, + Some(decoded_audio.as_slice())); + let mut resolvers = this.decode_resolvers.borrow_mut(); + assert!(resolvers.contains_key(&uuid_)); + let resolver = resolvers.remove(&uuid_).unwrap(); + if let Some(callback) = resolver.success_callback { + let _ = callback.Call__(&buffer, ExceptionHandling::Report); + } + resolver.promise.resolve_native(&buffer); + }), + &canceller, + ); + }) + .error(move |error| { + let _ = task_source_.queue_with_canceller( + task!(audio_decode_eos: move || { + let this = this_.root(); + let mut resolvers = this.decode_resolvers.borrow_mut(); + assert!(resolvers.contains_key(&uuid)); + let resolver = resolvers.remove(&uuid).unwrap(); + if let Some(callback) = resolver.error_callback { + let _ = callback.Call__( + &DOMException::new(&this.global(), DOMErrorName::DataCloneError), + ExceptionHandling::Report); + } + let error = format!("Audio decode error {:?}", error); + resolver.promise.reject_error(Error::Type(error)); + }), + &canceller_, + ); + }) + .build(); + self.audio_context_impl + .lock() + .unwrap() + .decode_audio_data(audio_data, callbacks); + } else { + // Step 3. + promise.reject_error(Error::DataClone); + return promise; + } + + // Step 4. + promise + } +} + +impl From<BaseAudioContextOptions> for AudioContextOptions { + fn from(options: BaseAudioContextOptions) -> Self { + match options { + BaseAudioContextOptions::AudioContext(options) => { + AudioContextOptions::RealTimeAudioContext(options) + }, + BaseAudioContextOptions::OfflineAudioContext(options) => { + AudioContextOptions::OfflineAudioContext(options) + }, + } + } +} + +impl From<ProcessingState> for AudioContextState { + fn from(state: ProcessingState) -> Self { + match state { + ProcessingState::Suspended => AudioContextState::Suspended, + ProcessingState::Running => AudioContextState::Running, + ProcessingState::Closed => AudioContextState::Closed, + } + } +} diff --git a/components/script/dom/beforeunloadevent.rs b/components/script/dom/beforeunloadevent.rs index f0b152e0fdd..d4e27e94b32 100644 --- a/components/script/dom/beforeunloadevent.rs +++ b/components/script/dom/beforeunloadevent.rs @@ -1,19 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #![allow(dead_code)] -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BeforeUnloadEventBinding; -use dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEventMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::window::Window; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEventMethods; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; @@ -21,32 +20,31 @@ use servo_atoms::Atom; #[dom_struct] pub struct BeforeUnloadEvent { event: Event, - return_value: DOMRefCell<DOMString>, + return_value: DomRefCell<DOMString>, } impl BeforeUnloadEvent { fn new_inherited() -> BeforeUnloadEvent { BeforeUnloadEvent { event: Event::new_inherited(), - return_value: DOMRefCell::new(DOMString::new()), + return_value: DomRefCell::new(DOMString::new()), } } - pub fn new_uninitialized(window: &Window) -> Root<BeforeUnloadEvent> { - reflect_dom_object(box BeforeUnloadEvent::new_inherited(), - window, - BeforeUnloadEventBinding::Wrap) + pub fn new_uninitialized(window: &Window) -> DomRoot<BeforeUnloadEvent> { + reflect_dom_object(Box::new(BeforeUnloadEvent::new_inherited()), window) } - pub fn new(window: &Window, - type_: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable) -> Root<BeforeUnloadEvent> { + pub fn new( + window: &Window, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + ) -> DomRoot<BeforeUnloadEvent> { let ev = BeforeUnloadEvent::new_uninitialized(window); { let event = ev.upcast::<Event>(); - event.init_event(type_, bool::from(bubbles), - bool::from(cancelable)); + event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); } ev } diff --git a/components/script/dom/bindings/callback.rs b/components/script/dom/bindings/callback.rs index e8a9683a4b2..c9130cfd50a 100644 --- a/components/script/dom/bindings/callback.rs +++ b/components/script/dom/bindings/callback.rs @@ -1,22 +1,26 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Base classes to work with IDL callbacks. -use dom::bindings::error::{Error, Fallible, report_pending_exception}; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::DomObject; -use dom::bindings::settings_stack::{AutoEntryScript, AutoIncumbentScript}; -use dom::bindings::utils::AsCCharPtrPtr; -use dom::globalscope::GlobalScope; -use js::jsapi::{Heap, MutableHandleObject}; -use js::jsapi::{IsCallable, JSContext, JSObject, JS_WrapObject, AddRawValueRoot}; -use js::jsapi::{JSCompartment, JS_EnterCompartment, JS_LeaveCompartment, RemoveRawValueRoot}; -use js::jsapi::JSAutoCompartment; -use js::jsapi::JS_GetProperty; -use js::jsval::{JSVal, UndefinedValue, ObjectValue}; -use js::rust::Runtime; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{report_pending_exception, Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::settings_stack::{AutoEntryScript, AutoIncumbentScript}; +use crate::dom::bindings::utils::AsCCharPtrPtr; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use crate::realms::{enter_realm, InRealm}; +use crate::script_runtime::JSContext; +use js::jsapi::Heap; +use js::jsapi::{AddRawValueRoot, IsCallable, JSObject}; +use js::jsapi::{EnterRealm, LeaveRealm, Realm, RemoveRawValueRoot}; +use js::jsval::{JSVal, ObjectValue, UndefinedValue}; +use js::rust::wrappers::{JS_GetProperty, JS_WrapObject}; +use js::rust::{MutableHandleObject, Runtime}; use std::default::Default; use std::ffi::CString; use std::mem::drop; @@ -25,7 +29,7 @@ use std::ptr; use std::rc::Rc; /// The exception handling used for a call. -#[derive(Copy, Clone, PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub enum ExceptionHandling { /// Report any exception and don't throw it to the caller code. Report, @@ -36,7 +40,7 @@ pub enum ExceptionHandling { /// A common base class for representing IDL callback function and /// callback interface types. #[derive(JSTraceable)] -#[must_root] +#[unrooted_must_root_lint::must_root] pub struct CallbackObject { /// The underlying `JSObject`. callback: Heap<*mut JSObject>, @@ -53,7 +57,7 @@ pub struct CallbackObject { /// /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context /// [sometimes]: https://github.com/whatwg/html/issues/2248 - incumbent: Option<JS<GlobalScope>> + incumbent: Option<Dom<GlobalScope>>, } impl Default for CallbackObject { @@ -69,7 +73,7 @@ impl CallbackObject { CallbackObject { callback: Heap::default(), permanent_js_root: Heap::default(), - incumbent: GlobalScope::incumbent().map(|i| JS::from_ref(&*i)), + incumbent: GlobalScope::incumbent().map(|i| Dom::from_ref(&*i)), } } @@ -78,11 +82,14 @@ impl CallbackObject { } #[allow(unsafe_code)] - unsafe fn init(&mut self, cx: *mut JSContext, callback: *mut JSObject) { + unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) { self.callback.set(callback); self.permanent_js_root.set(ObjectValue(callback)); - assert!(AddRawValueRoot(cx, self.permanent_js_root.get_unsafe(), - b"CallbackObject::root\n".as_c_char_ptr())); + assert!(AddRawValueRoot( + *cx, + self.permanent_js_root.get_unsafe(), + b"CallbackObject::root\n".as_c_char_ptr() + )); } } @@ -94,7 +101,6 @@ impl Drop for CallbackObject { RemoveRawValueRoot(cx, self.permanent_js_root.get_unsafe()); } } - } impl PartialEq for CallbackObject { @@ -103,12 +109,11 @@ impl PartialEq for CallbackObject { } } - /// A trait to be implemented by concrete IDL callback function and /// callback interface types. pub trait CallbackContainer { /// Create a new CallbackContainer object for the given `JSObject`. - unsafe fn new(cx: *mut JSContext, callback: *mut JSObject) -> Rc<Self>; + unsafe fn new(cx: JSContext, callback: *mut JSObject) -> Rc<Self>; /// Returns the underlying `CallbackObject`. fn callback_holder(&self) -> &CallbackObject; /// Returns the underlying `JSObject`. @@ -120,14 +125,13 @@ pub trait CallbackContainer { /// /// ["callback context"]: https://heycam.github.io/webidl/#dfn-callback-context fn incumbent(&self) -> Option<&GlobalScope> { - self.callback_holder().incumbent.as_ref().map(JS::deref) + self.callback_holder().incumbent.as_ref().map(Dom::deref) } } - /// A common base class for representing IDL callback function types. #[derive(JSTraceable, PartialEq)] -#[must_root] +#[unrooted_must_root_lint::must_root] pub struct CallbackFunction { object: CallbackObject, } @@ -148,17 +152,14 @@ impl CallbackFunction { /// Initialize the callback function with a value. /// Should be called once this object is done moving. - pub unsafe fn init(&mut self, cx: *mut JSContext, callback: *mut JSObject) { + pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) { self.object.init(cx, callback); } } - - - /// A common base class for representing IDL callback interface types. #[derive(JSTraceable, PartialEq)] -#[must_root] +#[unrooted_must_root_lint::must_root] pub struct CallbackInterface { object: CallbackObject, } @@ -178,62 +179,60 @@ impl CallbackInterface { /// Initialize the callback function with a value. /// Should be called once this object is done moving. - pub unsafe fn init(&mut self, cx: *mut JSContext, callback: *mut JSObject) { + pub unsafe fn init(&mut self, cx: JSContext, callback: *mut JSObject) { self.object.init(cx, callback); } /// Returns the property with the given `name`, if it is a callable object, /// or an error otherwise. - pub fn get_callable_property(&self, cx: *mut JSContext, name: &str) -> Fallible<JSVal> { - rooted!(in(cx) let mut callable = UndefinedValue()); - rooted!(in(cx) let obj = self.callback_holder().get()); + pub fn get_callable_property(&self, cx: JSContext, name: &str) -> Fallible<JSVal> { + rooted!(in(*cx) let mut callable = UndefinedValue()); + rooted!(in(*cx) let obj = self.callback_holder().get()); unsafe { let c_name = CString::new(name).unwrap(); - if !JS_GetProperty(cx, obj.handle(), c_name.as_ptr(), callable.handle_mut()) { + if !JS_GetProperty(*cx, obj.handle(), c_name.as_ptr(), callable.handle_mut()) { return Err(Error::JSFailed); } if !callable.is_object() || !IsCallable(callable.to_object()) { - return Err(Error::Type(format!("The value of the {} property is not callable", - name))); + return Err(Error::Type(format!( + "The value of the {} property is not callable", + name + ))); } } Ok(callable.get()) } } - -/// Wraps the reflector for `p` into the compartment of `cx`. -pub fn wrap_call_this_object<T: DomObject>(cx: *mut JSContext, - p: &T, - rval: MutableHandleObject) { +/// Wraps the reflector for `p` into the realm of `cx`. +pub fn wrap_call_this_object<T: DomObject>(cx: JSContext, p: &T, mut rval: MutableHandleObject) { rval.set(p.reflector().get_jsobject().get()); assert!(!rval.get().is_null()); unsafe { - if !JS_WrapObject(cx, rval) { + if !JS_WrapObject(*cx, rval) { rval.set(ptr::null_mut()); } } } - /// A class that performs whatever setup we need to safely make a call while /// this class is on the stack. After `new` returns, the call is safe to make. pub struct CallSetup { /// The global for reporting exceptions. This is the global object of the /// (possibly wrapped) callback object. - exception_global: Root<GlobalScope>, + exception_global: DomRoot<GlobalScope>, /// The `JSContext` used for the call. - cx: *mut JSContext, - /// The compartment we were in before the call. - old_compartment: *mut JSCompartment, + cx: JSContext, + /// The realm we were in before the call. + old_realm: *mut Realm, /// The exception handling used for the call. handling: ExceptionHandling, - /// https://heycam.github.io/webidl/#es-invoking-callback-functions + /// <https://heycam.github.io/webidl/#es-invoking-callback-functions> /// steps 8 and 18.2. entry_script: Option<AutoEntryScript>, - /// https://heycam.github.io/webidl/#es-invoking-callback-functions + /// <https://heycam.github.io/webidl/#es-invoking-callback-functions> /// steps 9 and 18.1. incumbent_script: Option<AutoIncumbentScript>, } @@ -241,10 +240,11 @@ pub struct CallSetup { impl CallSetup { /// Performs the setup needed to make a call. #[allow(unrooted_must_root)] - pub fn new<T: CallbackContainer>(callback: &T, - handling: ExceptionHandling) - -> CallSetup { + pub fn new<T: CallbackContainer>(callback: &T, handling: ExceptionHandling) -> CallSetup { let global = unsafe { GlobalScope::from_object(callback.callback()) }; + if let Some(window) = global.downcast::<Window>() { + window.Document().ensure_safe_to_run_script_or_layout(); + } let cx = global.get_cx(); let aes = AutoEntryScript::new(&global); @@ -252,7 +252,7 @@ impl CallSetup { CallSetup { exception_global: global, cx: cx, - old_compartment: unsafe { JS_EnterCompartment(cx, callback.callback()) }, + old_realm: unsafe { EnterRealm(*cx, callback.callback()) }, handling: handling, entry_script: Some(aes), incumbent_script: ais, @@ -260,7 +260,7 @@ impl CallSetup { } /// Returns the `JSContext` used for the call. - pub fn get_context(&self) -> *mut JSContext { + pub fn get_context(&self) -> JSContext { self.cx } } @@ -268,11 +268,10 @@ impl CallSetup { impl Drop for CallSetup { fn drop(&mut self) { unsafe { - JS_LeaveCompartment(self.cx, self.old_compartment); + LeaveRealm(*self.cx, self.old_realm); if self.handling == ExceptionHandling::Report { - let _ac = JSAutoCompartment::new(self.cx, - self.exception_global.reflector().get_jsobject().get()); - report_pending_exception(self.cx, true); + let ar = enter_realm(&*self.exception_global); + report_pending_exception(*self.cx, true, InRealm::Entered(&ar)); } drop(self.incumbent_script.take()); drop(self.entry_script.take().unwrap()); diff --git a/components/script/dom/bindings/cell.rs b/components/script/dom/bindings/cell.rs index 8e82de94381..7d567e04847 100644 --- a/components/script/dom/bindings/cell.rs +++ b/components/script/dom/bindings/cell.rs @@ -1,73 +1,69 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! A shareable mutable container for the DOM. -use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut}; -use style::thread_state; +use crate::dom::bindings::root::{assert_in_layout, assert_in_script}; +#[cfg(feature = "refcell_backtrace")] +pub use accountable_refcell::{ref_filter_map, Ref, RefCell, RefMut}; +#[cfg(not(feature = "refcell_backtrace"))] +pub use ref_filter_map::ref_filter_map; +use std::cell::{BorrowError, BorrowMutError}; +#[cfg(not(feature = "refcell_backtrace"))] +pub use std::cell::{Ref, RefCell, RefMut}; /// A mutable field in the DOM. /// -/// This extends the API of `core::cell::RefCell` to allow unsafe access in +/// This extends the API of `std::cell::RefCell` to allow unsafe access in /// certain situations, with dynamic checking in debug builds. -#[derive(Clone, Debug, Default, HeapSizeOf, PartialEq)] -pub struct DOMRefCell<T> { +#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)] +pub struct DomRefCell<T> { value: RefCell<T>, } -// Functionality specific to Servo's `DOMRefCell` type +// Functionality specific to Servo's `DomRefCell` type // =================================================== -impl<T> DOMRefCell<T> { +impl<T> DomRefCell<T> { /// Return a reference to the contents. /// /// For use in the layout thread only. #[allow(unsafe_code)] pub unsafe fn borrow_for_layout(&self) -> &T { - debug_assert!(thread_state::get().is_layout()); - &*self.value.as_ptr() - } - - /// Borrow the contents for the purpose of GC tracing. - /// - /// This succeeds even if the object is mutably borrowed, - /// so you have to be careful in trace code! - #[allow(unsafe_code)] - pub unsafe fn borrow_for_gc_trace(&self) -> &T { - // FIXME: IN_GC isn't reliable enough - doesn't catch minor GCs - // https://github.com/servo/servo/issues/6389 - // debug_assert!(thread_state::get().contains(SCRIPT | IN_GC)); - &*self.value.as_ptr() + assert_in_layout(); + self.value + .try_borrow_unguarded() + .expect("cell is mutably borrowed") } /// Borrow the contents for the purpose of script deallocation. /// #[allow(unsafe_code)] pub unsafe fn borrow_for_script_deallocation(&self) -> &mut T { - debug_assert!(thread_state::get().contains(thread_state::SCRIPT)); + assert_in_script(); &mut *self.value.as_ptr() } - /// Version of the above that we use during restyle while the script thread - /// is blocked. - pub fn borrow_mut_for_layout(&self) -> RefMut<T> { - debug_assert!(thread_state::get().is_layout()); - self.value.borrow_mut() + /// Mutably borrow a cell for layout. Ideally this would use + /// `RefCell::try_borrow_mut_unguarded` but that doesn't exist yet. + #[allow(unsafe_code)] + pub unsafe fn borrow_mut_for_layout(&self) -> &mut T { + assert_in_layout(); + &mut *self.value.as_ptr() } } -// Functionality duplicated with `core::cell::RefCell` +// Functionality duplicated with `std::cell::RefCell` // =================================================== -impl<T> DOMRefCell<T> { - /// Create a new `DOMRefCell` containing `value`. - pub fn new(value: T) -> DOMRefCell<T> { - DOMRefCell { +impl<T> DomRefCell<T> { + /// Create a new `DomRefCell` containing `value`. + pub fn new(value: T) -> DomRefCell<T> { + DomRefCell { value: RefCell::new(value), } } - /// Immutably borrows the wrapped value. /// /// The borrow lasts until the returned `Ref` exits scope. Multiple @@ -79,7 +75,7 @@ impl<T> DOMRefCell<T> { /// /// Panics if the value is currently mutably borrowed. pub fn borrow(&self) -> Ref<T> { - self.try_borrow().expect("DOMRefCell<T> already mutably borrowed") + self.value.borrow() } /// Mutably borrows the wrapped value. @@ -93,7 +89,7 @@ impl<T> DOMRefCell<T> { /// /// Panics if the value is currently borrowed. pub fn borrow_mut(&self) -> RefMut<T> { - self.try_borrow_mut().expect("DOMRefCell<T> already borrowed") + self.value.borrow_mut() } /// Attempts to immutably borrow the wrapped value. @@ -107,7 +103,7 @@ impl<T> DOMRefCell<T> { /// /// Panics if this is called off the script thread. pub fn try_borrow(&self) -> Result<Ref<T>, BorrowError> { - debug_assert!(thread_state::get().is_script()); + assert_in_script(); self.value.try_borrow() } @@ -122,7 +118,7 @@ impl<T> DOMRefCell<T> { /// /// Panics if this is called off the script thread. pub fn try_borrow_mut(&self) -> Result<RefMut<T>, BorrowMutError> { - debug_assert!(thread_state::get().is_script()); + assert_in_script(); self.value.try_borrow_mut() } } diff --git a/components/script/dom/bindings/codegen/BindingGen.py b/components/script/dom/bindings/codegen/BindingGen.py deleted file mode 100644 index 79576b67e58..00000000000 --- a/components/script/dom/bindings/codegen/BindingGen.py +++ /dev/null @@ -1,54 +0,0 @@ -# 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 http://mozilla.org/MPL/2.0/. - -import sys -import os -sys.path.append(os.path.join(".", "parser")) -sys.path.append(os.path.join(".", "ply")) -import cPickle -from Configuration import Configuration -from CodegenRust import CGBindingRoot, replaceFileIfChanged - - -def generate_binding_rs(config, outputprefix, webidlfile): - """ - |config| Is the configuration object. - |outputprefix| is a prefix to use for the header guards and filename. - """ - - filename = outputprefix + ".rs" - module = CGBindingRoot(config, outputprefix, webidlfile).define() - if not module: - print "Skipping empty module: %s" % (filename) - elif replaceFileIfChanged(filename, module): - print "Generating binding implementation: %s" % (filename) - - -def main(): - # Parse arguments. - from optparse import OptionParser - usagestring = "usage: %prog configFile outputdir outputPrefix webIDLFile" - o = OptionParser(usage=usagestring) - (options, args) = o.parse_args() - - if len(args) != 4: - o.error(usagestring) - configFile = os.path.normpath(args[0]) - outputdir = args[1] - outputPrefix = args[2] - webIDLFile = os.path.normpath(args[3]) - - # Load the parsing results - resultsPath = os.path.join(outputdir, 'ParserResults.pkl') - with open(resultsPath, 'rb') as f: - parserData = cPickle.load(f) - - # Create the configuration data. - config = Configuration(configFile, parserData) - - # Generate the prototype classes. - generate_binding_rs(config, outputPrefix, webIDLFile) - -if __name__ == '__main__': - main() diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf index 5647679f446..6c6e0ed6618 100644 --- a/components/script/dom/bindings/codegen/Bindings.conf +++ b/components/script/dom/bindings/codegen/Bindings.conf @@ -1,6 +1,6 @@ # 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 http://mozilla.org/MPL/2.0/. +# file, You can obtain one at https://mozilla.org/MPL/2.0/. # DOM Bindings Configuration. # @@ -14,6 +14,14 @@ DOMInterfaces = { +'Blob': { + 'weakReferenceable': True, +}, + +'File': { + 'weakReferenceable': True, +}, + 'MediaQueryList': { 'weakReferenceable': True, }, @@ -26,17 +34,134 @@ DOMInterfaces = { 'weakReferenceable': True, }, +'EventSource': { + 'weakReferenceable': True, +}, + +'MessagePort': { + 'weakReferenceable': True, +}, + #FIXME(jdm): This should be 'register': False, but then we don't generate enum types -'TestBinding': {}, +'TestBinding': { + 'inRealms': ['PromiseAttribute', 'PromiseNativeHandler'], +}, + +'DynamicModuleOwner': { + 'inRealms': ['PromiseAttribute'], +}, 'URL': { 'weakReferenceable': True, }, 'WindowProxy' : { - 'nativeType': 'BrowsingContext', - 'path': 'dom::browsingcontext::BrowsingContext', + 'path': 'crate::dom::windowproxy::WindowProxy', 'register': False, +}, + +'Window': { + 'inRealms': ['Fetch', 'Opener'], +}, + +'WorkerGlobalScope': { + 'inRealms': ['Fetch'], +}, + +'CustomElementRegistry': { + 'inRealms': ['WhenDefined'], +}, + +'AudioContext': { + 'inRealms': ['Suspend', 'Close'], +}, + +'NavigationPreloadManager': { + 'inRealms': ['Enable', 'Disable', 'SetHeaderValue', 'GetState'], +}, + +'HTMLMediaElement': { + 'inRealms': ['Play'], +}, + +'BluetoothRemoteGATTDescriptor': { + 'inRealms': ['ReadValue', 'WriteValue'], +}, + +'OfflineAudioContext': { + 'inRealms': ['StartRendering'], +}, + +'BluetoothRemoteGATTServer': { + 'inRealms': ['Connect'], +}, + +'ServiceWorkerContainer': { + 'inRealms': ['Register'], +}, + +'Navigator': { + 'inRealms': ['GetVRDisplays'], +}, + +'MediaDevices': { + 'inRealms': ['GetUserMedia'], +}, + +'XRSession': { + 'inRealms': ['UpdateRenderState', 'RequestReferenceSpace'], +}, + +'Bluetooth': { + 'inRealms': ['RequestDevice', 'GetAvailability'], +}, + +'BaseAudioContext': { + 'inRealms': ['Resume', 'DecodeAudioData'], +}, + +'RTCPeerConnection': { + 'inRealms': ['AddIceCandidate', 'CreateOffer', 'CreateAnswer', 'SetLocalDescription', 'SetRemoteDescription'], +}, + +'BluetoothRemoteGATTCharacteristic': { + 'inRealms': ['ReadValue', 'WriteValue', 'StartNotifications', 'StopNotifications'], +}, + +'VRDisplay': { + 'inRealms': ['RequestPresent', 'ExitPresent'], +}, + +'Worklet': { + 'inRealms': ['AddModule'], +}, + +'TestWorklet': { + 'inRealms': ['AddModule'], +}, + +'BluetoothDevice': { + 'inRealms': ['WatchAdvertisements'], +}, + +'XRSystem': { + 'inRealms': ['SupportsSessionMode', 'RequestSession'], +}, + +'GPU': { + 'inRealms': ['RequestAdapter'], +}, + +'GPUAdapter': { + 'inRealms': ['RequestDevice'], +}, + +'GPUBuffer': { + 'inRealms': ['MapAsync'], +}, + +'GPUDevice': { + 'inRealms': ['PopErrorScope', 'Lost'], } } diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 09dc19ce411..4a102aacb45 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -1,6 +1,6 @@ # 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 http://mozilla.org/MPL/2.0/. +# file, You can obtain one at https://mozilla.org/MPL/2.0/. # Common codegen classes. @@ -17,11 +17,14 @@ import functools from WebIDL import ( BuiltinTypes, IDLBuiltinType, - IDLNullValue, + IDLDefaultDictionaryValue, + IDLEmptySequenceValue, + IDLInterfaceMember, IDLNullableType, + IDLNullValue, IDLObject, + IDLPromiseType, IDLType, - IDLInterfaceMember, IDLUndefinedValue, IDLWrapperType, ) @@ -51,32 +54,6 @@ RUST_KEYWORDS = {"abstract", "alignof", "as", "become", "box", "break", "const", "use", "virtual", "where", "while", "yield"} -def replaceFileIfChanged(filename, newContents): - """ - Read a copy of the old file, so that we don't touch it if it hasn't changed. - Returns True if the file was updated, false otherwise. - """ - # XXXjdm This doesn't play well with make right now. - # Force the file to always be updated, or else changing CodegenRust.py - # will cause many autogenerated bindings to be regenerated perpetually - # until the result is actually different. - - # oldFileContents = "" - # try: - # with open(filename, 'rb') as oldFile: - # oldFileContents = ''.join(oldFile.readlines()) - # except: - # pass - - # if newContents == oldFileContents: - # return False - - with open(filename, 'wb') as f: - f.write(newContents) - - return True - - def toStringBool(arg): return str(not not arg).lower() @@ -94,20 +71,19 @@ def stripTrailingWhitespace(text): def innerContainerType(type): - assert type.isSequence() or type.isMozMap() + assert type.isSequence() or type.isRecord() return type.inner.inner if type.nullable() else type.inner def wrapInNativeContainerType(type, inner): if type.isSequence(): - containerType = "Vec" - elif type.isMozMap(): - containerType = "MozMap" + return CGWrapper(inner, pre="Vec<", post=">") + elif type.isRecord(): + key = type.inner.keyType if type.nullable() else type.keyType + return CGRecord(key, inner) else: raise TypeError("Unexpected container type %s", type) - return CGWrapper(inner, pre=containerType + "<", post=">") - builtinNames = { IDLType.Tags.bool: 'bool', @@ -320,7 +296,7 @@ class CGMethodCall(CGThing): if requiredArgs > 0: code = ( "if argc < %d {\n" - " throw_type_error(cx, \"Not enough arguments to %s.\");\n" + " throw_type_error(*cx, \"Not enough arguments to %s.\");\n" " return false;\n" "}" % (requiredArgs, methodName)) self.cgRoot.prepend( @@ -343,12 +319,17 @@ class CGMethodCall(CGThing): distinguishingIndex = method.distinguishingIndexForArgCount(argCount) - # We can't handle unions at the distinguishing index. + # We can't handle unions of non-object values at the distinguishing index. for (returnType, args) in possibleSignatures: - if args[distinguishingIndex].type.isUnion(): - raise TypeError("No support for unions as distinguishing " - "arguments yet: %s", - args[distinguishingIndex].location) + type = args[distinguishingIndex].type + if type.isUnion(): + if type.nullable(): + type = type.inner + for type in type.flatMemberTypes: + if not (type.isObject() or type.isNonCallbackInterface()): + raise TypeError("No support for unions with non-object variants " + "as distinguishing arguments yet: %s", + args[distinguishingIndex].location) # Convert all our arguments up to the distinguishing index. # Doesn't matter which of the possible signatures we use, since @@ -360,10 +341,10 @@ class CGMethodCall(CGThing): for i in range(0, distinguishingIndex)] # Select the right overload from our set. - distinguishingArg = "args.get(%d)" % distinguishingIndex + distinguishingArg = "HandleValue::from_raw(args.get(%d))" % distinguishingIndex def pickFirstSignature(condition, filterLambda): - sigs = filter(filterLambda, possibleSignatures) + sigs = list(filter(filterLambda, possibleSignatures)) assert len(sigs) < 2 if len(sigs) > 0: call = getPerSignatureCall(sigs[0], distinguishingIndex) @@ -378,16 +359,17 @@ class CGMethodCall(CGThing): # First check for null or undefined pickFirstSignature("%s.get().is_null_or_undefined()" % distinguishingArg, - lambda s: (s[1][distinguishingIndex].type.nullable() or - s[1][distinguishingIndex].type.isDictionary())) + lambda s: (s[1][distinguishingIndex].type.nullable() + or s[1][distinguishingIndex].type.isDictionary())) # Now check for distinguishingArg being an object that implements a # non-callback interface. That includes typed arrays and # arraybuffers. interfacesSigs = [ s for s in possibleSignatures - if (s[1][distinguishingIndex].type.isObject() or - s[1][distinguishingIndex].type.isNonCallbackInterface())] + if (s[1][distinguishingIndex].type.isObject() + or s[1][distinguishingIndex].type.isUnion() + or s[1][distinguishingIndex].type.isNonCallbackInterface())] # There might be more than one of these; we need to check # which ones we unwrap to. @@ -421,7 +403,8 @@ class CGMethodCall(CGThing): template, {"val": distinguishingArg}, declType, - "arg%d" % distinguishingIndex) + "arg%d" % distinguishingIndex, + needsAutoRoot=type_needs_auto_root(type)) # Indent by 4, since we need to indent further than our "do" statement caseBody.append(CGIndenter(testCode, 4)) @@ -438,38 +421,27 @@ class CGMethodCall(CGThing): # XXXbz Now we're supposed to check for distinguishingArg being # an array or a platform object that supports indexed # properties... skip that last for now. It's a bit of a pain. - pickFirstSignature("%s.get().is_object() && is_array_like(cx, %s)" % + pickFirstSignature("%s.get().is_object() && is_array_like(*cx, %s)" % (distinguishingArg, distinguishingArg), lambda s: - (s[1][distinguishingIndex].type.isSequence() or - s[1][distinguishingIndex].type.isObject())) - - # Check for Date objects - # XXXbz Do we need to worry about security wrappers around the Date? - pickFirstSignature("%s.get().is_object() && " - "{ rooted!(in(cx) let obj = %s.get().to_object()); " - "let mut is_date = false; " - "assert!(JS_ObjectIsDate(cx, obj.handle(), &mut is_date)); " - "is_date }" % - (distinguishingArg, distinguishingArg), - lambda s: (s[1][distinguishingIndex].type.isDate() or - s[1][distinguishingIndex].type.isObject())) + (s[1][distinguishingIndex].type.isSequence() + or s[1][distinguishingIndex].type.isObject())) # Check for vanilla JS objects # XXXbz Do we need to worry about security wrappers? - pickFirstSignature("%s.get().is_object() && !is_platform_object(%s.get().to_object())" % - (distinguishingArg, distinguishingArg), - lambda s: (s[1][distinguishingIndex].type.isCallback() or - s[1][distinguishingIndex].type.isCallbackInterface() or - s[1][distinguishingIndex].type.isDictionary() or - s[1][distinguishingIndex].type.isObject())) + pickFirstSignature("%s.get().is_object()" % + distinguishingArg, + lambda s: (s[1][distinguishingIndex].type.isCallback() + or s[1][distinguishingIndex].type.isCallbackInterface() + or s[1][distinguishingIndex].type.isDictionary() + or s[1][distinguishingIndex].type.isObject())) # The remaining cases are mutually exclusive. The # pickFirstSignature calls are what change caseBody # Check for strings or enums if pickFirstSignature(None, - lambda s: (s[1][distinguishingIndex].type.isString() or - s[1][distinguishingIndex].type.isEnum())): + lambda s: (s[1][distinguishingIndex].type.isString() + or s[1][distinguishingIndex].type.isEnum())): pass # Check for primitives elif pickFirstSignature(None, @@ -482,7 +454,8 @@ class CGMethodCall(CGThing): else: # Just throw; we have no idea what we're supposed to # do with this. - caseBody.append(CGGeneric("return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS);")) + caseBody.append(CGGeneric("throw_type_error(*cx, \"Could not convert JavaScript argument\");\n" + "return false;")) argCountCases.append(CGCase(str(argCount), CGList(caseBody, "\n"))) @@ -494,7 +467,7 @@ class CGMethodCall(CGThing): overloadCGThings.append( CGSwitch("argcount", argCountCases, - CGGeneric("throw_type_error(cx, \"Not enough arguments to %s.\");\n" + CGGeneric("throw_type_error(*cx, \"Not enough arguments to %s.\");\n" "return false;" % methodName))) # XXXjdm Avoid unreachable statement warnings # overloadCGThings.append( @@ -509,9 +482,9 @@ class CGMethodCall(CGThing): def dictionaryHasSequenceMember(dictionary): return (any(typeIsSequenceOrHasSequenceMember(m.type) for m in - dictionary.members) or - (dictionary.parent and - dictionaryHasSequenceMember(dictionary.parent))) + dictionary.members) + or (dictionary.parent + and dictionaryHasSequenceMember(dictionary.parent))) def typeIsSequenceOrHasSequenceMember(type): @@ -560,11 +533,9 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, isDefinitelyObject=False, isMember=False, isArgument=False, + isAutoRooted=False, invalidEnumValueFatal=True, defaultValue=None, - treatNullAs="Default", - isEnforceRange=False, - isClamp=False, exceptionCode=None, allowTreatNonObjectAsNull=False, isCallbackReturnValue=False, @@ -592,12 +563,6 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, If defaultValue is not None, it's the IDL default value for this conversion - If isEnforceRange is true, we're converting an integer and throwing if the - value is out of range. - - If isClamp is true, we're converting an integer and clamping if the - value is out of range. - If allowTreatNonObjectAsNull is true, then [TreatNonObjectAsNull] extended attributes on nullable callback functions will be honored. @@ -620,6 +585,13 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, # We should not have a defaultValue if we know we're an object assert not isDefinitelyObject or defaultValue is None + isEnforceRange = type.hasEnforceRange() + isClamp = type.hasClamp() + if type.treatNullAsEmpty: + treatNullAs = "EmptyString" + else: + treatNullAs = "Default" + # If exceptionCode is not set, we'll just rethrow the exception we got. # Note that we can't just set failureCode to exceptionCode, because setting # failureCode will prevent pending exceptions from being set in cases when @@ -628,7 +600,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, exceptionCode = "return false;\n" if failureCode is None: - failOrPropagate = "throw_type_error(cx, &error);\n%s" % exceptionCode + failOrPropagate = "throw_type_error(*cx, &error);\n%s" % exceptionCode else: failOrPropagate = failureCode @@ -646,34 +618,39 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, def onFailureNotAnObject(failureCode): return CGWrapper( CGGeneric( - failureCode or - ('throw_type_error(cx, "%s is not an object.");\n' - '%s' % (firstCap(sourceDescription), exceptionCode))), + failureCode + or ('throw_type_error(*cx, "%s is not an object.");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))), post="\n") def onFailureInvalidEnumValue(failureCode, passedVarName): return CGGeneric( - failureCode or - ('throw_type_error(cx, &format!("\'{}\' is not a valid enum value for enumeration \'%s\'.", %s)); %s' - % (type.name, passedVarName, exceptionCode))) + failureCode + or ('throw_type_error(*cx, &format!("\'{}\' is not a valid enum value for enumeration \'%s\'.", %s)); %s' + % (type.name, passedVarName, exceptionCode))) def onFailureNotCallable(failureCode): return CGGeneric( - failureCode or - ('throw_type_error(cx, \"%s is not callable.\");\n' - '%s' % (firstCap(sourceDescription), exceptionCode))) + failureCode + or ('throw_type_error(*cx, \"%s is not callable.\");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))) - # A helper function for handling null default values. Checks that the - # default value, if it exists, is null. - def handleDefaultNull(nullValue): + # A helper function for handling default values. + def handleDefault(nullValue): if defaultValue is None: return None - if not isinstance(defaultValue, IDLNullValue): - raise TypeError("Can't handle non-null default value here") + if isinstance(defaultValue, IDLNullValue): + assert type.nullable() + return nullValue + elif isinstance(defaultValue, IDLDefaultDictionaryValue): + assert type.isDictionary() + return nullValue + elif isinstance(defaultValue, IDLEmptySequenceValue): + assert type.isSequence() + return "Vec::new()" - assert type.nullable() or type.isDictionary() - return nullValue + raise TypeError("Can't handle non-null, non-empty sequence or non-empty dictionary default value here") # A helper function for wrapping up the template body for # possibly-nullable objecty stuff @@ -683,31 +660,32 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, # Handle the non-object cases by wrapping up the whole # thing in an if cascade. templateBody = ( - "if ${val}.get().is_object() {\n" + - CGIndenter(CGGeneric(templateBody)).define() + "\n") + "if ${val}.get().is_object() {\n" + + CGIndenter(CGGeneric(templateBody)).define() + "\n") if type.nullable(): templateBody += ( "} else if ${val}.get().is_null_or_undefined() {\n" " %s\n") % nullValue templateBody += ( - "} else {\n" + - CGIndenter(onFailureNotAnObject(failureCode)).define() + - "}") + "} else {\n" + + CGIndenter(onFailureNotAnObject(failureCode)).define() + + "}") return templateBody assert not (isEnforceRange and isClamp) # These are mutually exclusive - if type.isSequence() or type.isMozMap(): + if type.isSequence() or type.isRecord(): innerInfo = getJSToNativeConversionInfo(innerContainerType(type), descriptorProvider, - isMember=isMember) + isMember="Sequence", + isAutoRooted=isAutoRooted) declType = wrapInNativeContainerType(type, innerInfo.declType) config = getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs) if type.nullable(): declType = CGWrapper(declType, pre="Option<", post=" >") - templateBody = ("match FromJSValConvertible::from_jsval(cx, ${val}, %s) {\n" + templateBody = ("match FromJSValConvertible::from_jsval(*cx, ${val}, %s) {\n" " Ok(ConversionResult::Success(value)) => value,\n" " Ok(ConversionResult::Failure(error)) => {\n" "%s\n" @@ -715,17 +693,14 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, " _ => { %s },\n" "}" % (config, indent(failOrPropagate, 8), exceptionCode)) - return handleOptional(templateBody, declType, handleDefaultNull("None")) + return handleOptional(templateBody, declType, handleDefault("None")) if type.isUnion(): declType = CGGeneric(union_native_type(type)) if type.nullable(): declType = CGWrapper(declType, pre="Option<", post=" >") - if isMember != "Dictionary" and type_needs_tracing(type): - declType = CGTemplatedType("RootedTraceableBox", declType) - - templateBody = ("match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + templateBody = ("match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n" " Ok(ConversionResult::Success(value)) => value,\n" " Ok(ConversionResult::Failure(error)) => {\n" "%s\n" @@ -738,11 +713,32 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, for memberType in type.unroll().flatMemberTypes if memberType.isDictionary() ] - if dictionaries: + if (defaultValue + and not isinstance(defaultValue, IDLNullValue) + and not isinstance(defaultValue, IDLDefaultDictionaryValue)): + tag = defaultValue.type.tag() + if tag is IDLType.Tags.bool: + default = "%s::Boolean(%s)" % ( + union_native_type(type), + "true" if defaultValue.value else "false") + elif tag is IDLType.Tags.usvstring: + default = '%s::USVString(USVString("%s".to_owned()))' % ( + union_native_type(type), + defaultValue.value) + elif defaultValue.type.isEnum(): + enum = defaultValue.type.inner.identifier.name + default = "%s::%s(%s::%s)" % ( + union_native_type(type), + enum, + enum, + getEnumValueName(defaultValue.value)) + else: + raise("We don't currently support default values that aren't null, boolean or default dictionary") + elif dictionaries: if defaultValue: - assert isinstance(defaultValue, IDLNullValue) + assert isinstance(defaultValue, IDLDefaultDictionaryValue) dictionary, = dictionaries - default = "%s::%s(%s::%s::empty(cx))" % ( + default = "%s::%s(%s::%s::empty())" % ( union_native_type(type), dictionary.name, CGDictionary.makeModuleName(dictionary.inner), @@ -750,10 +746,60 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, else: default = None else: - default = handleDefaultNull("None") + default = handleDefault("None") return handleOptional(templateBody, declType, default) + if type.isPromise(): + assert not type.nullable() + # Per spec, what we're supposed to do is take the original + # Promise.resolve and call it with the original Promise as this + # value to make a Promise out of whatever value we actually have + # here. The question is which global we should use. There are + # a couple cases to consider: + # + # 1) Normal call to API with a Promise argument. This is a case the + # spec covers, and we should be using the current Realm's + # Promise. That means the current realm. + # 2) Promise return value from a callback or callback interface. + # This is in theory a case the spec covers but in practice it + # really doesn't define behavior here because it doesn't define + # what Realm we're in after the callback returns, which is when + # the argument conversion happens. We will use the current + # realm, which is the realm of the callable (which + # may itself be a cross-realm wrapper itself), which makes + # as much sense as anything else. In practice, such an API would + # once again be providing a Promise to signal completion of an + # operation, which would then not be exposed to anyone other than + # our own implementation code. + templateBody = fill( + """ + { // Scope for our JSAutoRealm. + + rooted!(in(*cx) let globalObj = CurrentGlobalOrNull(*cx)); + let promiseGlobal = GlobalScope::from_object_maybe_wrapped(globalObj.handle().get(), *cx); + + rooted!(in(*cx) let mut valueToResolve = $${val}.get()); + if !JS_WrapValue(*cx, valueToResolve.handle_mut()) { + $*{exceptionCode} + } + match Promise::new_resolved(&promiseGlobal, cx, valueToResolve.handle()) { + Ok(value) => value, + Err(error) => { + throw_dom_exception(cx, &promiseGlobal, error); + $*{exceptionCode} + } + } + } + """, + exceptionCode=exceptionCode) + + if isArgument: + declType = CGGeneric("&Promise") + else: + declType = CGGeneric("Rc<Promise>") + return handleOptional(templateBody, declType, handleDefault("None")) + if type.isGeckoInterface(): assert not isEnforceRange and not isClamp @@ -770,7 +816,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, isDefinitelyObject, type, failureCode) - return handleOptional(template, declType, handleDefaultNull("None")) + return handleOptional(template, declType, handleDefault("None")) conversionFunction = "root_from_handlevalue" descriptorType = descriptor.returnType @@ -779,99 +825,129 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, descriptorType = descriptor.nativeType elif isArgument: descriptorType = descriptor.argumentType + elif descriptor.interface.identifier.name == "WindowProxy": + conversionFunction = "windowproxy_from_handlevalue" + + if failureCode is None: + substitutions = { + "sourceDescription": sourceDescription, + "interface": descriptor.interface.identifier.name, + "exceptionCode": exceptionCode, + } + unwrapFailureCode = string.Template( + 'throw_type_error(*cx, "${sourceDescription} does not ' + 'implement interface ${interface}.");\n' + '${exceptionCode}').substitute(substitutions) + else: + unwrapFailureCode = failureCode - templateBody = "" - isPromise = descriptor.interface.identifier.name == "Promise" - if isPromise: - # Per spec, what we're supposed to do is take the original - # Promise.resolve and call it with the original Promise as this - # value to make a Promise out of whatever value we actually have - # here. The question is which global we should use. There are - # a couple cases to consider: - # - # 1) Normal call to API with a Promise argument. This is a case the - # spec covers, and we should be using the current Realm's - # Promise. That means the current compartment. - # 2) Promise return value from a callback or callback interface. - # This is in theory a case the spec covers but in practice it - # really doesn't define behavior here because it doesn't define - # what Realm we're in after the callback returns, which is when - # the argument conversion happens. We will use the current - # compartment, which is the compartment of the callable (which - # may itself be a cross-compartment wrapper itself), which makes - # as much sense as anything else. In practice, such an API would - # once again be providing a Promise to signal completion of an - # operation, which would then not be exposed to anyone other than - # our own implementation code. - templateBody = fill( - """ - { // Scope for our JSAutoCompartment. + templateBody = fill( + """ + match ${function}($${val}, *cx) { + Ok(val) => val, + Err(()) => { + $*{failureCode} + } + } + """, + failureCode=unwrapFailureCode + "\n", + function=conversionFunction) - rooted!(in(cx) let globalObj = CurrentGlobalOrNull(cx)); - let promiseGlobal = GlobalScope::from_object_maybe_wrapped(globalObj.handle().get()); + declType = CGGeneric(descriptorType) + if type.nullable(): + templateBody = "Some(%s)" % templateBody + declType = CGWrapper(declType, pre="Option<", post=">") - rooted!(in(cx) let mut valueToResolve = $${val}.get()); - if !JS_WrapValue(cx, valueToResolve.handle_mut()) { - $*{exceptionCode} - } - match Promise::Resolve(&promiseGlobal, cx, valueToResolve.handle()) { - Ok(value) => value, - Err(error) => { - throw_dom_exception(cx, &promiseGlobal, error); - $*{exceptionCode} - } - } - } - """, - exceptionCode=exceptionCode) + templateBody = wrapObjectTemplate(templateBody, "None", + isDefinitelyObject, type, failureCode) + + return handleOptional(templateBody, declType, handleDefault("None")) + + if is_typed_array(type): + if failureCode is None: + substitutions = { + "sourceDescription": sourceDescription, + "exceptionCode": exceptionCode, + } + unwrapFailureCode = string.Template( + 'throw_type_error(*cx, "${sourceDescription} is not a typed array.");\n' + '${exceptionCode}').substitute(substitutions) else: - if descriptor.interface.isConsequential(): - raise TypeError("Consequential interface %s being used as an " - "argument" % descriptor.interface.identifier.name) - - if failureCode is None: - substitutions = { - "sourceDescription": sourceDescription, - "interface": descriptor.interface.identifier.name, - "exceptionCode": exceptionCode, + unwrapFailureCode = failureCode + + typeName = type.unroll().name # unroll because it may be nullable + + if isMember == "Union": + typeName = "Heap" + typeName + + templateBody = fill( + """ + match typedarray::${ty}::from($${val}.get().to_object()) { + Ok(val) => val, + Err(()) => { + $*{failureCode} } - unwrapFailureCode = string.Template( - 'throw_type_error(cx, "${sourceDescription} does not ' - 'implement interface ${interface}.");\n' - '${exceptionCode}').substitute(substitutions) - else: - unwrapFailureCode = failureCode + } + """, + ty=typeName, + failureCode=unwrapFailureCode + "\n", + ) - templateBody = fill( - """ - match ${function}($${val}) { + if isMember == "Union": + templateBody = "RootedTraceableBox::new(%s)" % templateBody + + declType = CGGeneric("typedarray::%s" % typeName) + if type.nullable(): + templateBody = "Some(%s)" % templateBody + declType = CGWrapper(declType, pre="Option<", post=">") + + templateBody = wrapObjectTemplate(templateBody, "None", + isDefinitelyObject, type, failureCode) + + return handleOptional(templateBody, declType, handleDefault("None")) + + if type.isReadableStream(): + assert not isEnforceRange and not isClamp + + if failureCode is None: + unwrapFailureCode = '''throw_type_error(*cx, "This object is not \ + an instance of ReadableStream.");\n''' + else: + unwrapFailureCode = failureCode + + templateBody = fill( + """ + { + use crate::realms::{AlreadyInRealm, InRealm}; + let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); + match ReadableStream::from_js(cx, $${val}.get().to_object(), InRealm::Already(&in_realm_proof)) { Ok(val) => val, Err(()) => { - $*{failureCode} + $*{failureCode} } } - """, - failureCode=unwrapFailureCode + "\n", - function=conversionFunction) - declType = CGGeneric(descriptorType) - if type.nullable(): - templateBody = "Some(%s)" % templateBody - declType = CGWrapper(declType, pre="Option<", post=">") + } + """, + failureCode=unwrapFailureCode + "\n", + ) templateBody = wrapObjectTemplate(templateBody, "None", isDefinitelyObject, type, failureCode) - return handleOptional(templateBody, declType, handleDefaultNull("None")) + declType = CGGeneric("DomRoot<ReadableStream>") + + return handleOptional(templateBody, declType, + handleDefault("None")) - if type.isSpiderMonkeyInterface(): - raise TypeError("Can't handle SpiderMonkey interface arguments yet") + elif type.isSpiderMonkeyInterface(): + raise TypeError("Can't handle SpiderMonkey interface arguments other than typed arrays yet") if type.isDOMString(): nullBehavior = getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs) conversionCode = ( - "match FromJSValConvertible::from_jsval(cx, ${val}, %s) {\n" + "match FromJSValConvertible::from_jsval(*cx, ${val}, %s) {\n" " Ok(ConversionResult::Success(strval)) => strval,\n" " Ok(ConversionResult::Failure(error)) => {\n" "%s\n" @@ -900,7 +976,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, assert not isEnforceRange and not isClamp conversionCode = ( - "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + "match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n" " Ok(ConversionResult::Success(strval)) => strval,\n" " Ok(ConversionResult::Failure(error)) => {\n" "%s\n" @@ -929,7 +1005,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, assert not isEnforceRange and not isClamp conversionCode = ( - "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + "match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n" " Ok(ConversionResult::Success(strval)) => strval,\n" " Ok(ConversionResult::Failure(error)) => {\n" "%s\n" @@ -962,17 +1038,16 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, "yet") enum = type.inner.identifier.name if invalidEnumValueFatal: - handleInvalidEnumValueCode = onFailureInvalidEnumValue(failureCode, 'search').define() + handleInvalidEnumValueCode = failureCode or "throw_type_error(*cx, &error); %s" % exceptionCode else: handleInvalidEnumValueCode = "return true;" template = ( - "match find_enum_value(cx, ${val}, %(pairs)s) {\n" + "match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {" " Err(_) => { %(exceptionCode)s },\n" - " Ok((None, search)) => { %(handleInvalidEnumValueCode)s },\n" - " Ok((Some(&value), _)) => value,\n" - "}" % {"pairs": enum + "Values::pairs", - "exceptionCode": exceptionCode, + " Ok(ConversionResult::Success(v)) => v,\n" + " Ok(ConversionResult::Failure(error)) => { %(handleInvalidEnumValueCode)s },\n" + "}" % {"exceptionCode": exceptionCode, "handleInvalidEnumValueCode": handleInvalidEnumValueCode}) if defaultValue is not None: @@ -1034,20 +1109,28 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, assert not isEnforceRange and not isClamp assert isMember != "Union" - if isMember == "Dictionary": - # TODO: Need to properly root dictionaries - # https://github.com/servo/servo/issues/6381 - declType = CGGeneric("Heap<JSVal>") + if isMember in ("Dictionary", "Sequence") or isAutoRooted: + templateBody = "${val}.get()" if defaultValue is None: default = None elif isinstance(defaultValue, IDLNullValue): - default = "Heap::new(NullValue())" + default = "NullValue()" elif isinstance(defaultValue, IDLUndefinedValue): - default = "Heap::new(UndefinedValue())" + default = "UndefinedValue()" else: raise TypeError("Can't handle non-null, non-undefined default value here") - return handleOptional("Heap::new(${val}.get())", declType, default) + + if not isAutoRooted: + templateBody = "RootedTraceableBox::from_box(Heap::boxed(%s))" % templateBody + if default is not None: + default = "RootedTraceableBox::from_box(Heap::boxed(%s))" % default + declType = CGGeneric("RootedTraceableBox<Heap<JSVal>>") + # AutoRooter can trace properly inner raw GC thing pointers + else: + declType = CGGeneric("JSVal") + + return handleOptional(templateBody, declType, default) declType = CGGeneric("HandleValue") @@ -1065,39 +1148,37 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type.isObject(): assert not isEnforceRange and not isClamp - # TODO: Need to root somehow - # https://github.com/servo/servo/issues/6382 + templateBody = "${val}.get().to_object()" default = "ptr::null_mut()" - templateBody = wrapObjectTemplate("${val}.get().to_object()", - default, - isDefinitelyObject, type, failureCode) - if isMember in ("Dictionary", "Union"): - declType = CGGeneric("Heap<*mut JSObject>") - templateBody = "Heap::new(%s)" % templateBody - default = "Heap::new(%s)" % default + if isMember in ("Dictionary", "Union", "Sequence") and not isAutoRooted: + templateBody = "RootedTraceableBox::from_box(Heap::boxed(%s))" % templateBody + default = "RootedTraceableBox::new(Heap::default())" + declType = CGGeneric("RootedTraceableBox<Heap<*mut JSObject>>") else: # TODO: Need to root somehow # https://github.com/servo/servo/issues/6382 declType = CGGeneric("*mut JSObject") + templateBody = wrapObjectTemplate(templateBody, default, + isDefinitelyObject, type, failureCode) + return handleOptional(templateBody, declType, - handleDefaultNull(default)) + handleDefault(default)) if type.isDictionary(): # There are no nullable dictionaries - assert not type.nullable() + assert not type.nullable() or (isMember and isMember != "Dictionary") typeName = "%s::%s" % (CGDictionary.makeModuleName(type.inner), CGDictionary.makeDictionaryName(type.inner)) declType = CGGeneric(typeName) - empty = "%s::empty(cx)" % typeName + empty = "%s::empty()" % typeName - if isMember != "Dictionary" and type_needs_tracing(type): + if type_needs_tracing(type): declType = CGTemplatedType("RootedTraceableBox", declType) - empty = "RootedTraceableBox::new(%s)" % empty - template = ("match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + template = ("match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n" " Ok(ConversionResult::Success(dictionary)) => dictionary,\n" " Ok(ConversionResult::Failure(error)) => {\n" "%s\n" @@ -1105,7 +1186,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, " _ => { %s },\n" "}" % (indent(failOrPropagate, 8), exceptionCode)) - return handleOptional(template, declType, handleDefaultNull(empty)) + return handleOptional(template, declType, handleDefault(empty)) if type.isVoid(): # This one only happens for return values, and its easy: Just @@ -1125,7 +1206,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, declType = CGWrapper(declType, pre="Option<", post=">") template = ( - "match FromJSValConvertible::from_jsval(cx, ${val}, %s) {\n" + "match FromJSValConvertible::from_jsval(*cx, ${val}, %s) {\n" " Ok(ConversionResult::Success(v)) => v,\n" " Ok(ConversionResult::Failure(error)) => {\n" "%s\n" @@ -1156,7 +1237,8 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, def instantiateJSToNativeConversionTemplate(templateBody, replacements, - declType, declName): + declType, declName, + needsAutoRoot=False): """ Take the templateBody and declType as returned by getJSToNativeConversionInfo, a set of replacements as required by the @@ -1181,6 +1263,8 @@ def instantiateJSToNativeConversionTemplate(templateBody, replacements, else: result.append(conversion) + if needsAutoRoot: + result.append(CGGeneric("auto_root!(in(*cx) let %s = %s);" % (declName, declName))) # Add an empty CGGeneric to get an extra newline after the argument # conversion. result.append(CGGeneric("")) @@ -1198,12 +1282,12 @@ def convertConstIDLValueToJSVal(value): if tag == IDLType.Tags.uint32: return "ConstantVal::UintVal(%s)" % (value.value) if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]: - return "ConstantVal::DoubleVal(%s)" % (value.value) + return "ConstantVal::DoubleVal(%s as f64)" % (value.value) if tag == IDLType.Tags.bool: return "ConstantVal::BoolVal(true)" if value.value else "ConstantVal::BoolVal(false)" if tag in [IDLType.Tags.unrestricted_float, IDLType.Tags.float, IDLType.Tags.unrestricted_double, IDLType.Tags.double]: - return "ConstantVal::DoubleVal(%s)" % (value.value) + return "ConstantVal::DoubleVal(%s as f64)" % (value.value) raise TypeError("Const value of unhandled type: " + value.type) @@ -1225,7 +1309,7 @@ class CGArgumentConverter(CGThing): } replacementVariables = { - "val": string.Template("${args}.get(${index})").substitute(replacer), + "val": string.Template("HandleValue::from_raw(${args}.get(${index}))").substitute(replacer), } info = getJSToNativeConversionInfo( @@ -1233,10 +1317,8 @@ class CGArgumentConverter(CGThing): descriptorProvider, invalidEnumValueFatal=invalidEnumValueFatal, defaultValue=argument.defaultValue, - treatNullAs=argument.treatNullAs, - isEnforceRange=argument.enforceRange, - isClamp=argument.clamp, isMember="Variadic" if argument.variadic else False, + isAutoRooted=type_needs_auto_root(argument.type), allowTreatNonObjectAsNull=argument.allowTreatNonCallableAsNull()) template = info.template default = info.default @@ -1259,12 +1341,16 @@ class CGArgumentConverter(CGThing): else: assert not default + arg = "arg%d" % index + self.converter = instantiateJSToNativeConversionTemplate( - template, replacementVariables, declType, "arg%d" % index) + template, replacementVariables, declType, arg, + needsAutoRoot=type_needs_auto_root(argument.type)) + else: assert argument.optional variadicConversion = { - "val": string.Template("${args}.get(variadicArg)").substitute(replacer), + "val": string.Template("HandleValue::from_raw(${args}.get(variadicArg))").substitute(replacer), } innerConverter = [instantiateJSToNativeConversionTemplate( template, variadicConversion, declType, "slot")] @@ -1272,7 +1358,7 @@ class CGArgumentConverter(CGThing): arg = "arg%d" % index if argument.type.isGeckoInterface(): init = "rooted_vec!(let mut %s)" % arg - innerConverter.append(CGGeneric("%s.push(JS::from_ref(&*slot));" % arg)) + innerConverter.append(CGGeneric("%s.push(Dom::from_ref(&*slot));" % arg)) else: init = "let mut %s = vec![]" % arg innerConverter.append(CGGeneric("%s.push(slot);" % arg)) @@ -1301,7 +1387,7 @@ def wrapForType(jsvalRef, result='result', successCode='return true;', pre=''): * 'successCode': the code to run once we have done the conversion. * 'pre': code to run before the conversion if rooting is necessary """ - wrap = "%s\n(%s).to_jsval(cx, %s);" % (pre, result, jsvalRef) + wrap = "%s\n(%s).to_jsval(*cx, %s);" % (pre, result, jsvalRef) if successCode: wrap += "\n%s" % successCode return wrap @@ -1323,7 +1409,7 @@ def typeNeedsCx(type, retVal=False): # Returns a conversion behavior suitable for a type def getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs): - if type.isSequence() or type.isMozMap(): + if type.isSequence() or type.isRecord(): return getConversionConfigForType(innerContainerType(type), isEnforceRange, isClamp, treatNullAs) if type.isDOMString(): assert not isEnforceRange and not isClamp @@ -1381,6 +1467,9 @@ def getRetvalDeclarationForType(returnType, descriptorProvider): if returnType.nullable(): result = CGWrapper(result, pre="Option<", post=">") return result + if returnType.isPromise(): + assert not returnType.nullable() + return CGGeneric("Rc<Promise>") if returnType.isGeckoInterface(): descriptor = descriptorProvider.getDescriptor( returnType.unroll().inner.identifier.name) @@ -1404,11 +1493,11 @@ def getRetvalDeclarationForType(returnType, descriptorProvider): if returnType.isAny(): return CGGeneric("JSVal") if returnType.isObject() or returnType.isSpiderMonkeyInterface(): - result = CGGeneric("NonZero<*mut JSObject>") + result = CGGeneric("NonNull<JSObject>") if returnType.nullable(): result = CGWrapper(result, pre="Option<", post=">") return result - if returnType.isSequence() or returnType.isMozMap(): + if returnType.isSequence() or returnType.isRecord(): result = getRetvalDeclarationForType(innerContainerType(returnType), descriptorProvider) result = wrapInNativeContainerType(returnType, result) if returnType.nullable(): @@ -1418,6 +1507,8 @@ def getRetvalDeclarationForType(returnType, descriptorProvider): nullable = returnType.nullable() dictName = returnType.inner.name if nullable else returnType.name result = CGGeneric(dictName) + if type_needs_tracing(returnType): + result = CGWrapper(result, pre="RootedTraceableBox<", post=">") if nullable: result = CGWrapper(result, pre="Option<", post=">") return result @@ -1426,7 +1517,7 @@ def getRetvalDeclarationForType(returnType, descriptorProvider): returnType) -def MemberCondition(pref, func): +def MemberCondition(pref, func, exposed, secure): """ A string representing the condition for a member to actually be exposed. Any of the arguments can be None. If not None, they should have the @@ -1434,14 +1525,21 @@ def MemberCondition(pref, func): pref: The name of the preference. func: The name of the function. + exposed: One or more names of an exposed global. + secure: Requires secure context. """ assert pref is None or isinstance(pref, str) assert func is None or isinstance(func, str) - assert func is None or pref is None + assert exposed is None or isinstance(exposed, set) + assert func is None or pref is None or exposed is None or secure is None + if secure: + return 'Condition::SecureContext()' if pref: return 'Condition::Pref("%s")' % pref if func: return 'Condition::Func(%s)' % func + if exposed: + return ["Condition::Exposed(InterfaceObjectMap::Globals::%s)" % camel_to_upper_snake(i) for i in exposed] return "Condition::Satisfied" @@ -1484,7 +1582,9 @@ class PropertyDefiner: PropertyDefiner.getStringAttr(interfaceMember, "Pref"), PropertyDefiner.getStringAttr(interfaceMember, - "Func")) + "Func"), + interfaceMember.exposureSet, + interfaceMember.getExtendedAttribute("SecureContext")) def generateGuardedArray(self, array, name, specTemplate, specTerminator, specType, getCondition, getDataTuple): @@ -1516,22 +1616,30 @@ class PropertyDefiner: specs = [] prefableSpecs = [] prefableTemplate = ' Guard::new(%s, %s[%d])' + origTemplate = specTemplate + if isinstance(specTemplate, str): + specTemplate = lambda _: origTemplate # noqa for cond, members in groupby(array, lambda m: getCondition(m, self.descriptor)): - currentSpecs = [specTemplate % getDataTuple(m) for m in members] + currentSpecs = [specTemplate(m) % getDataTuple(m) for m in members] if specTerminator: currentSpecs.append(specTerminator) specs.append("&[\n" + ",\n".join(currentSpecs) + "]\n") - prefableSpecs.append( - prefableTemplate % (cond, name + "_specs", len(specs) - 1)) + if isinstance(cond, list): + for i in cond: + prefableSpecs.append( + prefableTemplate % (i, name + "_specs", len(specs) - 1)) + else: + prefableSpecs.append( + prefableTemplate % (cond, name + "_specs", len(specs) - 1)) - specsArray = ("const %s_specs: &'static [&'static[%s]] = &[\n" + - ",\n".join(specs) + "\n" + - "];\n") % (name, specType) + specsArray = ("const %s_specs: &'static [&'static[%s]] = &[\n" + + ",\n".join(specs) + "\n" + + "];\n") % (name, specType) - prefArray = ("const %s: &'static [Guard<&'static [%s]>] = &[\n" + - ",\n".join(prefableSpecs) + "\n" + - "];\n") % (name, specType) + prefArray = ("const %s: &'static [Guard<&'static [%s]>] = &[\n" + + ",\n".join(prefableSpecs) + "\n" + + "];\n") % (name, specType) return specsArray + prefArray @@ -1559,45 +1667,70 @@ class MethodDefiner(PropertyDefiner): # Ignore non-static methods for callback interfaces if not descriptor.interface.isCallback() or static: methods = [m for m in descriptor.interface.members if - m.isMethod() and m.isStatic() == static and - not m.isIdentifierLess() and - MemberIsUnforgeable(m, descriptor) == unforgeable] + m.isMethod() and m.isStatic() == static + and not m.isIdentifierLess() + and MemberIsUnforgeable(m, descriptor) == unforgeable] else: methods = [] self.regular = [{"name": m.identifier.name, "methodInfo": not m.isStatic(), "length": methodLength(m), + "flags": "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(m, descriptor)} for m in methods] - # FIXME Check for an existing iterator on the interface first. - if any(m.isGetter() and m.isIndexed() for m in methods): + # TODO: Once iterable is implemented, use tiebreak rules instead of + # failing. Also, may be more tiebreak rules to implement once spec bug + # is resolved. + # https://www.w3.org/Bugs/Public/show_bug.cgi?id=28592 + def hasIterator(methods, regular): + return (any("@@iterator" in m.aliases for m in methods) + or any("@@iterator" == r["name"] for r in regular)) + + # Check whether we need to output an @@iterator due to having an indexed + # getter. We only do this while outputting non-static and + # non-unforgeable methods, since the @@iterator function will be + # neither. + if (not static + and not unforgeable + and descriptor.supportsIndexedProperties()): # noqa + if hasIterator(methods, self.regular): # noqa + raise TypeError("Cannot have indexed getter/attr on " + "interface %s with other members " + "that generate @@iterator, such as " + "maplike/setlike or aliased functions." % + self.descriptor.interface.identifier.name) self.regular.append({"name": '@@iterator', "methodInfo": False, - "selfHostedName": "ArrayValues", + "selfHostedName": "$ArrayValues", "length": 0, + "flags": "0", # Not enumerable, per spec. "condition": "Condition::Satisfied"}) # Generate the keys/values/entries aliases for value iterables. maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable - if (not static and not unforgeable and - (maplikeOrSetlikeOrIterable and - maplikeOrSetlikeOrIterable.isIterable() and - maplikeOrSetlikeOrIterable.isValueIterator())): + if (not static and not unforgeable + and maplikeOrSetlikeOrIterable + and maplikeOrSetlikeOrIterable.isIterable() + and maplikeOrSetlikeOrIterable.isValueIterator()): + m = maplikeOrSetlikeOrIterable + # Add our keys/values/entries/forEach self.regular.append({ "name": "keys", "methodInfo": False, "selfHostedName": "ArrayKeys", "length": 0, + "flags": "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(m, descriptor) }) self.regular.append({ "name": "values", "methodInfo": False, - "selfHostedName": "ArrayValues", + "selfHostedName": "$ArrayValues", "length": 0, + "flags": "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(m, descriptor) }) @@ -1606,6 +1739,7 @@ class MethodDefiner(PropertyDefiner): "methodInfo": False, "selfHostedName": "ArrayEntries", "length": 0, + "flags": "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(m, descriptor) }) @@ -1614,6 +1748,7 @@ class MethodDefiner(PropertyDefiner): "methodInfo": False, "selfHostedName": "ArrayForEach", "length": 1, + "flags": "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(m, descriptor) }) @@ -1626,6 +1761,7 @@ class MethodDefiner(PropertyDefiner): "name": "toString", "nativeName": stringifier.identifier.name, "length": 0, + "flags": "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(stringifier, descriptor) }) self.unforgeable = unforgeable @@ -1637,18 +1773,15 @@ class MethodDefiner(PropertyDefiner): def condition(m, d): return m["condition"] - flags = "JSPROP_ENUMERATE" - if self.unforgeable: - flags += " | JSPROP_PERMANENT | JSPROP_READONLY" - def specData(m): - # TODO: Use something like JS_FNSPEC - # https://github.com/servo/servo/issues/6391 + flags = m["flags"] + if self.unforgeable: + flags += " | JSPROP_PERMANENT | JSPROP_READONLY" if "selfHostedName" in m: selfHostedName = '%s as *const u8 as *const libc::c_char' % str_to_const_array(m["selfHostedName"]) assert not m.get("methodInfo", True) accessor = "None" - jitinfo = "0 as *const JSJitInfo" + jitinfo = "ptr::null()" else: selfHostedName = "0 as *const libc::c_char" if m.get("methodInfo", True): @@ -1660,28 +1793,30 @@ class MethodDefiner(PropertyDefiner): jitinfo = "&%s_methodinfo as *const _ as *const JSJitInfo" % identifier accessor = "Some(generic_method)" else: - jitinfo = "0 as *const JSJitInfo" + jitinfo = "ptr::null()" accessor = 'Some(%s)' % m.get("nativeName", m["name"]) if m["name"].startswith("@@"): - return ('(SymbolCode::%s as i32 + 1)' - % m["name"][2:], accessor, jitinfo, m["length"], flags, selfHostedName) - return (str_to_const_array(m["name"]), accessor, jitinfo, m["length"], flags, selfHostedName) + name = 'JSPropertySpec_Name { symbol_: SymbolCode::%s as usize + 1 }' % m["name"][2:] + else: + name = ('JSPropertySpec_Name { string_: %s as *const u8 as *const libc::c_char }' + % str_to_const_array(m["name"])) + return (name, accessor, jitinfo, m["length"], flags, selfHostedName) return self.generateGuardedArray( array, name, ' JSFunctionSpec {\n' - ' name: %s as *const u8 as *const libc::c_char,\n' + ' name: %s,\n' ' call: JSNativeWrapper { op: %s, info: %s },\n' ' nargs: %s,\n' ' flags: (%s) as u16,\n' ' selfHostedName: %s\n' ' }', ' JSFunctionSpec {\n' - ' name: 0 as *const libc::c_char,\n' - ' call: JSNativeWrapper { op: None, info: 0 as *const JSJitInfo },\n' + ' name: JSPropertySpec_Name { string_: ptr::null() },\n' + ' call: JSNativeWrapper { op: None, info: ptr::null() },\n' ' nargs: 0,\n' ' flags: 0,\n' - ' selfHostedName: 0 as *const libc::c_char\n' + ' selfHostedName: ptr::null()\n' ' }', 'JSFunctionSpec', condition, specData) @@ -1694,23 +1829,35 @@ class AttrDefiner(PropertyDefiner): self.name = name self.descriptor = descriptor self.regular = [ - m + { + "name": m.identifier.name, + "attr": m, + "flags": "JSPROP_ENUMERATE", + "is_accessor": "true", + } for m in descriptor.interface.members if - m.isAttr() and m.isStatic() == static and - MemberIsUnforgeable(m, descriptor) == unforgeable + m.isAttr() and m.isStatic() == static + and MemberIsUnforgeable(m, descriptor) == unforgeable ] self.static = static self.unforgeable = unforgeable + if not static and not unforgeable and not ( + descriptor.interface.isNamespace() or descriptor.interface.isCallback() + ): + self.regular.append({ + "name": "@@toStringTag", + "attr": None, + "flags": "JSPROP_READONLY", + "is_accessor": "false", + }) + def generateArray(self, array, name): if len(array) == 0: return "" - flags = "JSPROP_ENUMERATE | JSPROP_SHARED" - if self.unforgeable: - flags += " | JSPROP_PERMANENT" - def getter(attr): + attr = attr['attr'] if self.static: accessor = 'get_' + self.descriptor.internalNameFor(attr.identifier.name) jitinfo = "0 as *const JSJitInfo" @@ -1726,6 +1873,7 @@ class AttrDefiner(PropertyDefiner): "native": accessor}) def setter(attr): + attr = attr['attr'] if (attr.readonly and not attr.getExtendedAttribute("PutForwards") and not attr.getExtendedAttribute("Replaceable")): return "JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }" @@ -1744,26 +1892,61 @@ class AttrDefiner(PropertyDefiner): % {"info": jitinfo, "native": accessor}) + def condition(m, d): + if m["name"] == "@@toStringTag": + return MemberCondition(pref=None, func=None, exposed=None, secure=None) + return PropertyDefiner.getControllingCondition(m["attr"], d) + def specData(attr): - return (str_to_const_array(attr.identifier.name), flags, getter(attr), + if attr["name"] == "@@toStringTag": + return (attr["name"][2:], attr["flags"], attr["is_accessor"], + str_to_const_array(self.descriptor.interface.getClassName())) + + flags = attr["flags"] + if self.unforgeable: + flags += " | JSPROP_PERMANENT" + return (str_to_const_array(attr["attr"].identifier.name), flags, attr["is_accessor"], getter(attr), setter(attr)) + def template(m): + if m["name"] == "@@toStringTag": + return """ JSPropertySpec { + name: JSPropertySpec_Name { symbol_: SymbolCode::%s as usize + 1 }, + attributes_: (%s) as u8, + isAccessor_: (%s), + u: JSPropertySpec_AccessorsOrValue { + value: JSPropertySpec_ValueWrapper { + type_: JSPropertySpec_ValueWrapper_Type::String, + __bindgen_anon_1: JSPropertySpec_ValueWrapper__bindgen_ty_1 { + string: %s as *const u8 as *const libc::c_char, + } + } + } + } +""" + return """ JSPropertySpec { + name: JSPropertySpec_Name { string_: %s as *const u8 as *const libc::c_char }, + attributes_: (%s) as u8, + isAccessor_: (%s), + u: JSPropertySpec_AccessorsOrValue { + accessors: JSPropertySpec_AccessorsOrValue_Accessors { + getter: JSPropertySpec_Accessor { + native: %s, + }, + setter: JSPropertySpec_Accessor { + native: %s, + } + } + } + } +""" + return self.generateGuardedArray( array, name, - ' JSPropertySpec {\n' - ' name: %s as *const u8 as *const libc::c_char,\n' - ' flags: (%s) as u8,\n' - ' getter: %s,\n' - ' setter: %s\n' - ' }', - ' JSPropertySpec {\n' - ' name: 0 as *const libc::c_char,\n' - ' flags: 0,\n' - ' getter: JSNativeWrapper { op: None, info: 0 as *const JSJitInfo },\n' - ' setter: JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }\n' - ' }', + template, + ' JSPropertySpec::ZERO', 'JSPropertySpec', - PropertyDefiner.getControllingCondition, specData) + condition, specData) class ConstDefiner(PropertyDefiner): @@ -1790,6 +1973,7 @@ class ConstDefiner(PropertyDefiner): 'ConstantSpec', PropertyDefiner.getControllingCondition, specData) + # We'll want to insert the indent at the beginnings of lines, but we # don't want to indent empty lines. So only indent lines that have a # non-newline character on them. @@ -1835,11 +2019,36 @@ class CGWrapper(CGThing): return self.pre + defn + self.post +class CGRecord(CGThing): + """ + CGThing that wraps value CGThing in record with key type equal to keyType parameter + """ + def __init__(self, keyType, value): + CGThing.__init__(self) + assert keyType.isString() + self.keyType = keyType + self.value = value + + def define(self): + if self.keyType.isByteString(): + keyDef = "ByteString" + elif self.keyType.isDOMString(): + keyDef = "DOMString" + elif self.keyType.isUSVString(): + keyDef = "USVString" + else: + assert False + + defn = keyDef + ", " + self.value.define() + return "Record<" + defn + ">" + + class CGImports(CGWrapper): """ Generates the appropriate import/use statements. """ - def __init__(self, child, descriptors, callbacks, dictionaries, enums, imports, config, ignored_warnings=None): + def __init__(self, child, descriptors, callbacks, dictionaries, enums, typedefs, imports, config, + ignored_warnings=None): """ Adds a set of imports. """ @@ -1866,8 +2075,8 @@ class CGImports(CGWrapper): def isImportable(type): if not type.isType(): - assert (type.isInterface() or type.isDictionary() or - type.isEnum() or type.isNamespace()) + assert (type.isInterface() or type.isDictionary() + or type.isEnum() or type.isNamespace()) return True return not (type.builtin or type.isSequence() or type.isUnion()) @@ -1912,7 +2121,7 @@ class CGImports(CGWrapper): members += [constructor] if d.proxy: - members += [o for o in d.operations.values() if o] + members += [o for o in list(d.operations.values()) if o] for m in members: if m.isMethod(): @@ -1928,6 +2137,11 @@ class CGImports(CGWrapper): for d in dictionaries: types += componentTypes(d) + # Import the type names used in the typedefs that are being defined. + for t in typedefs: + if not t.innerType.isCallback(): + types += componentTypes(t.innerType) + # Normalize the types we've collected and remove any ones which can't be imported. types = removeWrapperAndNullableTypes(types) @@ -1943,11 +2157,14 @@ class CGImports(CGWrapper): if name != 'GlobalScope': extras += [descriptor.path] parentName = descriptor.getParentName() - if parentName: + while parentName: descriptor = descriptorProvider.getDescriptor(parentName) extras += [descriptor.path, descriptor.bindingPath] - elif t.isType() and t.isMozMap(): - extras += ['dom::bindings::mozmap::MozMap'] + parentName = descriptor.getParentName() + elif t.isType() and t.isRecord(): + extras += ['crate::dom::bindings::record::Record'] + elif isinstance(t, IDLPromiseType): + extras += ['crate::dom::promise::Promise'] else: if t.isEnum(): extras += [getModuleFromObject(t) + '::' + getIdentifier(t).name + 'Values'] @@ -1997,15 +2214,15 @@ def DOMClassTypeId(desc): inner = "" if desc.hasDescendants(): if desc.interface.getExtendedAttribute("Abstract"): - return "::dom::bindings::codegen::InheritTypes::TopTypeId { abstract_: () }" + return "crate::dom::bindings::codegen::InheritTypes::TopTypeId { abstract_: () }" name = desc.interface.identifier.name - inner = "(::dom::bindings::codegen::InheritTypes::%sTypeId::%s)" % (name, name) + inner = "(crate::dom::bindings::codegen::InheritTypes::%sTypeId::%s)" % (name, name) elif len(protochain) == 1: - return "::dom::bindings::codegen::InheritTypes::TopTypeId { alone: () }" + return "crate::dom::bindings::codegen::InheritTypes::TopTypeId { alone: () }" reversed_protochain = list(reversed(protochain)) for (child, parent) in zip(reversed_protochain, reversed_protochain[1:]): - inner = "(::dom::bindings::codegen::InheritTypes::%sTypeId::%s%s)" % (parent, child, inner) - return "::dom::bindings::codegen::InheritTypes::TopTypeId { %s: %s }" % (protochain[0].lower(), inner) + inner = "(crate::dom::bindings::codegen::InheritTypes::%sTypeId::%s%s)" % (parent, child, inner) + return "crate::dom::bindings::codegen::InheritTypes::TopTypeId { %s: %s }" % (protochain[0].lower(), inner) def DOMClass(descriptor): @@ -2016,7 +2233,7 @@ def DOMClass(descriptor): # padding. protoList.extend(['PrototypeList::ID::Last'] * (descriptor.config.maxProtoChainLength - len(protoList))) prototypeChainString = ', '.join(protoList) - heapSizeOf = 'heap_size_of_raw_self_and_children::<%s>' % descriptor.concreteType + mallocSizeOf = 'malloc_size_of_including_raw_self::<%s>' % descriptor.concreteType if descriptor.isGlobal(): globals_ = camel_to_upper_snake(descriptor.name) else: @@ -2025,9 +2242,9 @@ def DOMClass(descriptor): DOMClass { interface_chain: [ %s ], type_id: %s, - heap_size_of: %s as unsafe fn(_) -> _, - global: InterfaceObjectMap::%s, -}""" % (prototypeChainString, DOMClassTypeId(descriptor), heapSizeOf, globals_) + malloc_size_of: %s as unsafe fn(&mut _, _) -> _, + global: InterfaceObjectMap::Globals::%s, +}""" % (prototypeChainString, DOMClassTypeId(descriptor), mallocSizeOf, globals_) class CGDOMJSClass(CGThing): @@ -2039,11 +2256,15 @@ class CGDOMJSClass(CGThing): self.descriptor = descriptor def define(self): + parentName = self.descriptor.getParentName() + if not parentName: + parentName = "crate::dom::bindings::reflector::Reflector" + args = { "domClass": DOMClass(self.descriptor), "enumerateHook": "None", "finalizeHook": FINALIZE_HOOK_NAME, - "flags": "0", + "flags": "JSCLASS_FOREGROUND_FINALIZE", "name": str_to_const_array(self.descriptor.interface.identifier.name), "resolveHook": "None", "slots": "1", @@ -2052,7 +2273,7 @@ class CGDOMJSClass(CGThing): if self.descriptor.isGlobal(): assert not self.descriptor.weakReferenceable args["enumerateHook"] = "Some(enumerate_global)" - args["flags"] = "JSCLASS_IS_GLOBAL | JSCLASS_DOM_GLOBAL" + args["flags"] = "JSCLASS_IS_GLOBAL | JSCLASS_DOM_GLOBAL | JSCLASS_FOREGROUND_FINALIZE" args["slots"] = "JSCLASS_GLOBAL_SLOT_COUNT + 1" args["resolveHook"] = "Some(resolve_global)" args["traceHook"] = "js::jsapi::JS_GlobalObjectTraceHook" @@ -2062,9 +2283,8 @@ class CGDOMJSClass(CGThing): static CLASS_OPS: js::jsapi::JSClassOps = js::jsapi::JSClassOps { addProperty: None, delProperty: None, - getProperty: None, - setProperty: None, - enumerate: %(enumerateHook)s, + enumerate: None, + newEnumerate: %(enumerateHook)s, resolve: %(resolveHook)s, mayResolve: None, finalize: Some(%(finalizeHook)s), @@ -2081,10 +2301,56 @@ static Class: DOMJSClass = DOMJSClass { (((%(slots)s) & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT) /* JSCLASS_HAS_RESERVED_SLOTS(%(slots)s) */, cOps: &CLASS_OPS, - reserved: [0 as *mut _; 3], + spec: ptr::null(), + ext: ptr::null(), + oOps: ptr::null(), }, dom_class: %(domClass)s -};""" % args +}; +""" % args + + +class CGAssertInheritance(CGThing): + """ + Generate a type assertion for inheritance + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + parent = self.descriptor.interface.parent + parentName = "" + if parent: + parentName = parent.identifier.name + else: + parentName = "crate::dom::bindings::reflector::Reflector" + + selfName = self.descriptor.interface.identifier.name + + if selfName == "PaintRenderingContext2D": + # PaintRenderingContext2D embeds a CanvasRenderingContext2D + # instead of a Reflector as an optimization, + # but this is fine since CanvasRenderingContext2D + # also has a reflector + # + # FIXME *RenderingContext2D should use Inline + parentName = "crate::dom::canvasrenderingcontext2d::CanvasRenderingContext2D" + args = { + "parentName": parentName, + "selfName": selfName, + } + + return """\ +impl %(selfName)s { + fn __assert_parent_type(&self) { + use crate::dom::bindings::inheritance::HasParent; + // If this type assertion fails, make sure the first field of your + // DOM struct is of the correct type -- it must be the parent class. + let _: &%(parentName)s = self.as_parent(); + } +} +""" % args def str_to_const_array(s): @@ -2108,7 +2374,9 @@ static PrototypeClass: JSClass = JSClass { // JSCLASS_HAS_RESERVED_SLOTS(%(slotCount)s) (%(slotCount)s & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT, cOps: 0 as *const _, - reserved: [0 as *mut os::raw::c_void; 3] + spec: ptr::null(), + ext: ptr::null(), + oOps: ptr::null(), }; """ % {'name': name, 'slotCount': slotCount} @@ -2145,7 +2413,14 @@ static NAMESPACE_OBJECT_CLASS: NamespaceObjectClass = unsafe { return """\ static INTERFACE_OBJECT_CLASS: NonCallbackInterfaceObjectClass = NonCallbackInterfaceObjectClass::new( - &%(constructorBehavior)s, + { + // Intermediate `const` because as of nightly-2018-10-05, + // rustc is conservative in promotion to `'static` of the return values of `const fn`s: + // https://github.com/rust-lang/rust/issues/54846 + // https://github.com/rust-lang/rust/pull/53851 + const BEHAVIOR: InterfaceConstructorBehavior = %(constructorBehavior)s; + &BEHAVIOR + }, %(representation)s, PrototypeList::ID::%(id)s, %(depth)s); @@ -2209,20 +2484,19 @@ def getAllTypes(descriptors, dictionaries, callbacks, typedefs): """ Generate all the types we're dealing with. For each type, a tuple containing type, descriptor, dictionary is yielded. The - descriptor and dictionary can be None if the type does not come - from a descriptor or dictionary; they will never both be non-None. + descriptor can be None if the type does not come from a descriptor. """ for d in descriptors: for t in getTypesFromDescriptor(d): - yield (t, d, None) + yield (t, d) for dictionary in dictionaries: for t in getTypesFromDictionary(dictionary): - yield (t, None, dictionary) + yield (t, None) for callback in callbacks: for t in getTypesFromCallback(callback): - yield (t, None, None) + yield (t, None) for typedef in typedefs: - yield (typedef.innerType, None, None) + yield (typedef.innerType, None) def UnionTypes(descriptors, dictionaries, callbacks, typedefs, config): @@ -2231,40 +2505,53 @@ def UnionTypes(descriptors, dictionaries, callbacks, typedefs, config): """ imports = [ - 'dom', - 'dom::bindings::codegen::PrototypeList', - 'dom::bindings::conversions::ConversionResult', - 'dom::bindings::conversions::FromJSValConvertible', - 'dom::bindings::conversions::ToJSValConvertible', - 'dom::bindings::conversions::ConversionBehavior', - 'dom::bindings::conversions::StringificationBehavior', - 'dom::bindings::conversions::root_from_handlevalue', - 'dom::bindings::error::throw_not_in_union', - 'dom::bindings::js::Root', - 'dom::bindings::mozmap::MozMap', - 'dom::bindings::str::ByteString', - 'dom::bindings::str::DOMString', - 'dom::bindings::str::USVString', - 'dom::types::*', + 'crate::dom', + 'crate::dom::bindings::codegen::PrototypeList', + 'crate::dom::bindings::conversions::ConversionResult', + 'crate::dom::bindings::conversions::FromJSValConvertible', + 'crate::dom::bindings::conversions::ToJSValConvertible', + 'crate::dom::bindings::conversions::ConversionBehavior', + 'crate::dom::bindings::conversions::StringificationBehavior', + 'crate::dom::bindings::conversions::root_from_handlevalue', + 'crate::dom::bindings::conversions::windowproxy_from_handlevalue', + 'std::ptr::NonNull', + 'std::rc::Rc', + 'crate::dom::bindings::record::Record', + 'crate::dom::bindings::num::Finite', + 'crate::dom::bindings::root::DomRoot', + 'crate::dom::bindings::str::ByteString', + 'crate::dom::bindings::str::DOMString', + 'crate::dom::bindings::str::USVString', + 'crate::dom::bindings::trace::RootedTraceableBox', + 'crate::dom::types::*', + 'crate::dom::windowproxy::WindowProxy', + 'crate::script_runtime::JSContext as SafeJSContext', 'js::error::throw_type_error', - 'js::jsapi::HandleValue', + 'js::rust::HandleValue', 'js::jsapi::Heap', + 'js::jsapi::IsCallable', 'js::jsapi::JSContext', 'js::jsapi::JSObject', - 'js::jsapi::MutableHandleValue', + 'js::rust::MutableHandleValue', 'js::jsval::JSVal', + 'js::typedarray' ] # Now find all the things we'll need as arguments and return values because # we need to wrap or unwrap them. unionStructs = dict() - for (t, descriptor, dictionary) in getAllTypes(descriptors, dictionaries, callbacks, typedefs): - if dictionary: - imports.append("%s::%s" % (CGDictionary.makeModuleName(dictionary), - CGDictionary.makeDictionaryName(dictionary))) + for (t, descriptor) in getAllTypes(descriptors, dictionaries, callbacks, typedefs): t = t.unroll() if not t.isUnion(): continue + for memberType in t.flatMemberTypes: + if memberType.isDictionary() or memberType.isEnum() or memberType.isCallback(): + memberModule = getModuleFromObject(memberType) + memberName = (memberType.callback.identifier.name + if memberType.isCallback() else memberType.inner.identifier.name) + imports.append("%s::%s" % (memberModule, memberName)) + if memberType.isEnum(): + imports.append("%s::%sValues" % (memberModule, memberName)) name = str(t) if name not in unionStructs: provider = descriptor or config.getDescriptorProvider() @@ -2274,13 +2561,14 @@ def UnionTypes(descriptors, dictionaries, callbacks, typedefs, config): ]) # Sort unionStructs by key, retrieve value - unionStructs = (i[1] for i in sorted(unionStructs.items(), key=operator.itemgetter(0))) + unionStructs = (i[1] for i in sorted(list(unionStructs.items()), key=operator.itemgetter(0))) return CGImports(CGList(unionStructs, "\n\n"), descriptors=[], callbacks=[], dictionaries=[], enums=[], + typedefs=[], imports=imports, config=config, ignored_warnings=[]) @@ -2391,9 +2679,19 @@ class CGAbstractMethod(CGThing): body = self.definition_body() if self.catchPanic: - body = CGWrapper(CGIndenter(body), - pre="return wrap_panic(panic::AssertUnwindSafe(|| {\n", - post=("""\n}), %s);""" % ("()" if self.returnType == "void" else "false"))) + if self.returnType == "void": + pre = "wrap_panic(&mut || {\n" + post = "\n})" + else: + pre = ( + "let mut result = false;\n" + "wrap_panic(&mut || result = (|| {\n" + ) + post = ( + "\n})());\n" + "return result" + ) + body = CGWrapper(CGIndenter(body), pre=pre, post=post) return CGWrapper(CGIndenter(body), pre=self.definition_prologue(), @@ -2420,23 +2718,22 @@ class CGConstructorEnabled(CGAbstractMethod): def __init__(self, descriptor): CGAbstractMethod.__init__(self, descriptor, 'ConstructorEnabled', 'bool', - [Argument("*mut JSContext", "aCx"), - Argument("HandleObject", "aObj")], - unsafe=True) + [Argument("SafeJSContext", "aCx"), + Argument("HandleObject", "aObj")]) def definition_body(self): conditions = [] iface = self.descriptor.interface bits = " | ".join(sorted( - "InterfaceObjectMap::" + camel_to_upper_snake(i) for i in iface.exposureSet + "InterfaceObjectMap::Globals::" + camel_to_upper_snake(i) for i in iface.exposureSet )) conditions.append("is_exposed_in(aObj, %s)" % bits) pref = iface.getExtendedAttribute("Pref") if pref: assert isinstance(pref, list) and len(pref) == 1 - conditions.append('PREFS.get("%s").as_boolean().unwrap_or(false)' % pref[0]) + conditions.append('pref!(%s)' % pref[0]) func = iface.getExtendedAttribute("Func") if func: @@ -2446,33 +2743,6 @@ class CGConstructorEnabled(CGAbstractMethod): return CGList((CGGeneric(cond) for cond in conditions), " &&\n") -def CreateBindingJSObject(descriptor, parent=None): - assert not descriptor.isGlobal() - create = "let raw = Box::into_raw(object);\nlet _rt = RootedTraceable::new(&*raw);\n" - if descriptor.proxy: - create += """ -let handler = RegisterBindings::PROXY_HANDLERS[PrototypeList::Proxies::%s as usize]; -rooted!(in(cx) let private = PrivateValue(raw as *const libc::c_void)); -let obj = NewProxyObject(cx, handler, - private.handle(), - proto.get(), %s.get(), - ptr::null_mut(), ptr::null_mut()); -assert!(!obj.is_null()); -rooted!(in(cx) let obj = obj);\ -""" % (descriptor.name, parent) - else: - create += ("rooted!(in(cx) let obj = JS_NewObjectWithGivenProto(\n" - " cx, &Class.base as *const JSClass, proto.handle()));\n" - "assert!(!obj.is_null());\n" - "\n" - "JS_SetReservedSlot(obj.get(), DOM_OBJECT_SLOT,\n" - " PrivateValue(raw as *const libc::c_void));") - if descriptor.weakReferenceable: - create += """ -JS_SetReservedSlot(obj.get(), DOM_WEAK_SLOT, PrivateValue(ptr::null()));""" - return create - - def InitUnforgeablePropertiesOnHolder(descriptor, properties): """ Define the unforgeable properties on the unforgeable holder for @@ -2482,8 +2752,8 @@ def InitUnforgeablePropertiesOnHolder(descriptor, properties): """ unforgeables = [] - defineUnforgeableAttrs = "define_guarded_properties(cx, unforgeable_holder.handle(), %s);" - defineUnforgeableMethods = "define_guarded_methods(cx, unforgeable_holder.handle(), %s);" + defineUnforgeableAttrs = "define_guarded_properties(cx, unforgeable_holder.handle(), %s, global);" + defineUnforgeableMethods = "define_guarded_methods(cx, unforgeable_holder.handle(), %s, global);" unforgeableMembers = [ (defineUnforgeableAttrs, properties.unforgeable_attrs), @@ -2508,8 +2778,8 @@ def CopyUnforgeablePropertiesToInstance(descriptor): # reflector, so we can make sure we don't get confused by named getters. if descriptor.proxy: copyCode += """\ -rooted!(in(cx) let mut expando = ptr::null_mut()); -ensure_expando_object(cx, obj.handle(), expando.handle_mut()); +rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>()); +ensure_expando_object(*cx, obj.handle().into(), expando.handle_mut()); """ obj = "expando" else: @@ -2519,14 +2789,15 @@ ensure_expando_object(cx, obj.handle(), expando.handle_mut()); # unforgeable holder for those with the right JSClass. Luckily, there # aren't too many globals being created. if descriptor.isGlobal(): - copyFunc = "JS_CopyPropertiesFrom" + copyFunc = "JS_CopyOwnPropertiesAndPrivateFields" else: copyFunc = "JS_InitializePropertiesFromCompatibleNativeObject" copyCode += """\ -rooted!(in(cx) let mut unforgeable_holder = ptr::null_mut()); -unforgeable_holder.handle_mut().set( - JS_GetReservedSlot(proto.get(), DOM_PROTO_UNFORGEABLE_HOLDER_SLOT).to_object()); -assert!(%(copyFunc)s(cx, %(obj)s.handle(), unforgeable_holder.handle())); +let mut slot = UndefinedValue(); +JS_GetReservedSlot(proto.get(), DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, &mut slot); +rooted!(in(*cx) let mut unforgeable_holder = ptr::null_mut::<JSObject>()); +unforgeable_holder.handle_mut().set(slot.to_object()); +assert!(%(copyFunc)s(*cx, %(obj)s.handle(), unforgeable_holder.handle())); """ % {'copyFunc': copyFunc, 'obj': obj} return copyCode @@ -2540,32 +2811,74 @@ class CGWrapMethod(CGAbstractMethod): def __init__(self, descriptor): assert not descriptor.interface.isCallback() assert not descriptor.isGlobal() - args = [Argument('*mut JSContext', 'cx'), + args = [Argument('SafeJSContext', 'cx'), Argument('&GlobalScope', 'scope'), Argument("Box<%s>" % descriptor.concreteType, 'object')] - retval = 'Root<%s>' % descriptor.concreteType + retval = 'DomRoot<%s>' % descriptor.concreteType CGAbstractMethod.__init__(self, descriptor, 'Wrap', retval, args, pub=True, unsafe=True) def definition_body(self): unforgeable = CopyUnforgeablePropertiesToInstance(self.descriptor) - create = CreateBindingJSObject(self.descriptor, "scope") + if self.descriptor.proxy: + create = """ +let handler: *const libc::c_void = + RegisterBindings::proxy_handlers::%(concreteType)s + .load(std::sync::atomic::Ordering::Acquire); +rooted!(in(*cx) let obj = NewProxyObject( + *cx, + handler, + Handle::from_raw(UndefinedHandleValue), + proto.get(), + ptr::null(), +)); +assert!(!obj.is_null()); +SetProxyReservedSlot( + obj.get(), + 0, + &PrivateValue(raw.as_ptr() as *const %(concreteType)s as *const libc::c_void), +); +""" + else: + create = """ +rooted!(in(*cx) let obj = JS_NewObjectWithGivenProto( + *cx, + &Class.base, + proto.handle(), +)); +assert!(!obj.is_null()); +JS_SetReservedSlot( + obj.get(), + DOM_OBJECT_SLOT, + &PrivateValue(raw.as_ptr() as *const %(concreteType)s as *const libc::c_void), +); +""" + create = create % {"concreteType": self.descriptor.concreteType} + if self.descriptor.weakReferenceable: + create += """ +let val = PrivateValue(ptr::null()); +JS_SetReservedSlot(obj.get(), DOM_WEAK_SLOT, &val); +""" + return CGGeneric("""\ +let raw = Root::new(MaybeUnreflectedDom::from_box(object)); + let scope = scope.reflector().get_jsobject(); assert!(!scope.get().is_null()); assert!(((*get_object_class(scope.get())).flags & JSCLASS_IS_GLOBAL) != 0); +let _ac = JSAutoRealm::new(*cx, scope.get()); -rooted!(in(cx) let mut proto = ptr::null_mut()); -let _ac = JSAutoCompartment::new(cx, scope.get()); +rooted!(in(*cx) let mut proto = ptr::null_mut::<JSObject>()); GetProtoObject(cx, scope, proto.handle_mut()); assert!(!proto.is_null()); %(createObject)s +let root = raw.reflect_with(obj.get()); %(copyUnforgeable)s -(*raw).init_reflector(obj.get()); -Root::from_ref(&*raw)""" % {'copyUnforgeable': unforgeable, 'createObject': create}) +DomRoot::from_ref(&*root)\ +""" % {'copyUnforgeable': unforgeable, 'createObject': create}) class CGWrapGlobalMethod(CGAbstractMethod): @@ -2575,15 +2888,16 @@ class CGWrapGlobalMethod(CGAbstractMethod): def __init__(self, descriptor, properties): assert not descriptor.interface.isCallback() assert descriptor.isGlobal() - args = [Argument('*mut JSContext', 'cx'), + args = [Argument('SafeJSContext', 'cx'), Argument("Box<%s>" % descriptor.concreteType, 'object')] - retval = 'Root<%s>' % descriptor.concreteType + retval = 'DomRoot<%s>' % descriptor.concreteType CGAbstractMethod.__init__(self, descriptor, 'Wrap', retval, args, pub=True, unsafe=True) self.properties = properties def definition_body(self): values = { + "concreteType": self.descriptor.concreteType, "unforgeable": CopyUnforgeablePropertiesToInstance(self.descriptor) } @@ -2592,40 +2906,39 @@ class CGWrapGlobalMethod(CGAbstractMethod): ("define_guarded_methods", self.properties.methods), ("define_guarded_constants", self.properties.consts) ] - members = ["%s(cx, obj.handle(), %s);" % (function, array.variableName()) + members = ["%s(cx, obj.handle(), %s, obj.handle());" % (function, array.variableName()) for (function, array) in pairs if array.length() > 0] values["members"] = "\n".join(members) return CGGeneric("""\ let origin = object.origin().clone(); -let raw = Box::into_raw(object); -let _rt = RootedTraceable::new(&*raw); +let raw = Root::new(MaybeUnreflectedDom::from_box(object)); -rooted!(in(cx) let mut obj = ptr::null_mut()); +rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>()); create_global_object( cx, &Class.base, - raw as *const libc::c_void, + raw.as_ptr() as *const %(concreteType)s as *const libc::c_void, _trace, obj.handle_mut(), &origin); assert!(!obj.is_null()); -(*raw).init_reflector(obj.get()); +let root = raw.reflect_with(obj.get()); -let _ac = JSAutoCompartment::new(cx, obj.get()); -rooted!(in(cx) let mut proto = ptr::null_mut()); +let _ac = JSAutoRealm::new(*cx, obj.get()); +rooted!(in(*cx) let mut proto = ptr::null_mut::<JSObject>()); GetProtoObject(cx, obj.handle(), proto.handle_mut()); -assert!(JS_SplicePrototype(cx, obj.handle(), proto.handle())); +assert!(JS_SetPrototype(*cx, obj.handle(), proto.handle())); let mut immutable = false; -assert!(JS_SetImmutablePrototype(cx, obj.handle(), &mut immutable)); +assert!(JS_SetImmutablePrototype(*cx, obj.handle(), &mut immutable)); assert!(immutable); %(members)s %(unforgeable)s -Root::from_ref(&*raw)\ +DomRoot::from_ref(&*root)\ """ % values) @@ -2640,8 +2953,8 @@ class CGIDLInterface(CGThing): def define(self): interface = self.descriptor.interface name = self.descriptor.concreteType - if (interface.getUserData("hasConcreteDescendant", False) or - interface.getUserData("hasProxyDescendant", False)): + if (interface.getUserData("hasConcreteDescendant", False) + or interface.getUserData("hasProxyDescendant", False)): depth = self.descriptor.prototypeDepth check = "class.interface_chain[%s] == PrototypeList::ID::%s" % (depth, name) elif self.descriptor.proxy: @@ -2664,6 +2977,50 @@ impl PartialEq for %(name)s { """ % {'check': check, 'name': name} +class CGDomObjectWrap(CGThing): + """ + Class for codegen of an implementation of the DomObjectWrap trait. + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + name = self.descriptor.concreteType + name = "dom::%s::%s" % (name.lower(), name) + return """\ +impl DomObjectWrap for %s { + const WRAP: unsafe fn( + SafeJSContext, + &GlobalScope, + Box<Self>, + ) -> Root<Dom<Self>> = Wrap; +} +""" % (name) + + +class CGDomObjectIteratorWrap(CGThing): + """ + Class for codegen of an implementation of the DomObjectIteratorWrap trait. + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + assert self.descriptor.interface.isIteratorInterface() + name = self.descriptor.interface.iterableInterface.identifier.name + return """\ +impl DomObjectIteratorWrap for %s { + const ITER_WRAP: unsafe fn( + SafeJSContext, + &GlobalScope, + Box<IterableIterator<Self>>, + ) -> Root<Dom<IterableIterator<Self>>> = Wrap; +} +""" % (name) + + class CGAbstractExternMethod(CGAbstractMethod): """ Abstract base class for codegen of implementation-only (no @@ -2714,35 +3071,83 @@ class PropertyArrays(): return define +class CGCollectJSONAttributesMethod(CGAbstractMethod): + """ + Generate the CollectJSONAttributes method for an interface descriptor + """ + def __init__(self, descriptor, toJSONMethod): + args = [Argument('*mut JSContext', 'cx'), + Argument('RawHandleObject', 'obj'), + Argument('*mut libc::c_void', 'this'), + Argument('&RootedGuard<*mut JSObject>', 'result')] + CGAbstractMethod.__init__(self, descriptor, 'CollectJSONAttributes', + 'bool', args, pub=True, unsafe=True) + self.toJSONMethod = toJSONMethod + + def definition_body(self): + ret = """let incumbent_global = GlobalScope::incumbent().expect("no incumbent global"); +let global = incumbent_global.reflector().get_jsobject();\n""" + interface = self.descriptor.interface + for m in interface.members: + if m.isAttr() and not m.isStatic() and m.type.isJSONType(): + name = m.identifier.name + conditions = MemberCondition(None, None, m.exposureSet, None) + ret_conditions = '&[' + ", ".join(conditions) + "]" + ret += fill( + """ + let conditions = ${conditions}; + let is_satisfied = conditions.iter().any(|c| + c.is_satisfied( + SafeJSContext::from_ptr(cx), + HandleObject::from_raw(obj), + global)); + if is_satisfied { + rooted!(in(cx) let mut temp = UndefinedValue()); + if !get_${name}(cx, obj, this, JSJitGetterCallArgs { _base: temp.handle_mut().into() }) { + return false; + } + if !JS_DefineProperty(cx, result.handle().into(), + ${nameAsArray} as *const u8 as *const libc::c_char, + temp.handle(), JSPROP_ENUMERATE as u32) { + return false; + } + } + """, + name=name, nameAsArray=str_to_const_array(name), conditions=ret_conditions) + ret += 'return true;\n' + return CGGeneric(ret) + + class CGCreateInterfaceObjectsMethod(CGAbstractMethod): """ Generate the CreateInterfaceObjects method for an interface descriptor. properties should be a PropertyArrays instance. """ - def __init__(self, descriptor, properties, haveUnscopables): - args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', 'global'), + def __init__(self, descriptor, properties, haveUnscopables, haveLegacyWindowAliases): + args = [Argument('SafeJSContext', 'cx'), Argument('HandleObject', 'global'), Argument('*mut ProtoOrIfaceArray', 'cache')] CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args, unsafe=True) self.properties = properties self.haveUnscopables = haveUnscopables + self.haveLegacyWindowAliases = haveLegacyWindowAliases def definition_body(self): name = self.descriptor.interface.identifier.name if self.descriptor.interface.isNamespace(): if self.descriptor.interface.getExtendedAttribute("ProtoObjectHack"): - proto = "JS_GetObjectPrototype(cx, global)" + proto = "GetRealmObjectPrototype(*cx)" else: - proto = "JS_NewPlainObject(cx)" + proto = "JS_NewPlainObject(*cx)" if self.properties.static_methods.length(): methods = self.properties.static_methods.variableName() else: methods = "&[]" return CGGeneric("""\ -rooted!(in(cx) let proto = %(proto)s); +rooted!(in(*cx) let proto = %(proto)s); assert!(!proto.is_null()); -rooted!(in(cx) let mut namespace = ptr::null_mut()); +rooted!(in(*cx) let mut namespace = ptr::null_mut::<JSObject>()); create_namespace_object(cx, global, proto.handle(), &NAMESPACE_OBJECT_CLASS, %(methods)s, %(name)s, namespace.handle_mut()); assert!(!namespace.is_null()); @@ -2755,7 +3160,7 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); if self.descriptor.interface.isCallback(): assert not self.descriptor.interface.ctor() and self.descriptor.interface.hasConstants() return CGGeneric("""\ -rooted!(in(cx) let mut interface = ptr::null_mut()); +rooted!(in(*cx) let mut interface = ptr::null_mut::<JSObject>()); create_callback_interface_object(cx, global, sConstants, %(name)s, interface.handle_mut()); assert!(!interface.is_null()); assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); @@ -2768,23 +3173,25 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); parentName = self.descriptor.getParentName() if not parentName: if self.descriptor.interface.getExtendedAttribute("ExceptionClass"): - getPrototypeProto = "prototype_proto.set(JS_GetErrorPrototype(cx))" + protoGetter = "GetRealmErrorPrototype" elif self.descriptor.interface.isIteratorInterface(): - getPrototypeProto = "prototype_proto.set(JS_GetIteratorPrototype(cx))" + protoGetter = "GetRealmIteratorPrototype" else: - getPrototypeProto = "prototype_proto.set(JS_GetObjectPrototype(cx, global))" + protoGetter = "GetRealmObjectPrototype" + getPrototypeProto = "prototype_proto.set(%s(*cx))" % protoGetter else: getPrototypeProto = ("%s::GetProtoObject(cx, global, prototype_proto.handle_mut())" % toBindingNamespace(parentName)) code = [CGGeneric("""\ -rooted!(in(cx) let mut prototype_proto = ptr::null_mut()); +rooted!(in(*cx) let mut prototype_proto = ptr::null_mut::<JSObject>()); %s; assert!(!prototype_proto.is_null());""" % getPrototypeProto)] properties = { "id": name, - "unscopables": "unscopable_names" if self.haveUnscopables else "&[]" + "unscopables": "unscopable_names" if self.haveUnscopables else "&[]", + "legacyWindowAliases": "legacy_window_aliases" if self.haveLegacyWindowAliases else "&[]" } for arrayName in self.properties.arrayNames(): array = getattr(self.properties, arrayName) @@ -2806,15 +3213,16 @@ assert!(!prototype_proto.is_null());""" % getPrototypeProto)] proto_properties = properties code.append(CGGeneric(""" -rooted!(in(cx) let mut prototype = ptr::null_mut()); +rooted!(in(*cx) let mut prototype = ptr::null_mut::<JSObject>()); create_interface_prototype_object(cx, - prototype_proto.handle(), + global.into(), + prototype_proto.handle().into(), &PrototypeClass, %(methods)s, %(attrs)s, %(consts)s, %(unscopables)s, - prototype.handle_mut()); + prototype.handle_mut().into()); assert!(!prototype.is_null()); assert!((*cache)[PrototypeList::ID::%(id)s as usize].is_null()); (*cache)[PrototypeList::ID::%(id)s as usize] = prototype.get(); @@ -2830,20 +3238,19 @@ assert!((*cache)[PrototypeList::ID::%(id)s as usize].is_null()); else: properties["length"] = 0 parentName = self.descriptor.getParentName() + code.append(CGGeneric("rooted!(in(*cx) let mut interface_proto = ptr::null_mut::<JSObject>());")) if parentName: parentName = toBindingNamespace(parentName) code.append(CGGeneric(""" -rooted!(in(cx) let mut interface_proto = ptr::null_mut()); %s::GetConstructorObject(cx, global, interface_proto.handle_mut());""" % parentName)) else: - code.append(CGGeneric(""" -rooted!(in(cx) let interface_proto = JS_GetFunctionPrototype(cx, global));""")) + code.append(CGGeneric("interface_proto.set(GetRealmFunctionPrototype(*cx));")) code.append(CGGeneric("""\ assert!(!interface_proto.is_null()); -rooted!(in(cx) let mut interface = ptr::null_mut()); +rooted!(in(*cx) let mut interface = ptr::null_mut::<JSObject>()); create_noncallback_interface_object(cx, - global, + global.into(), interface_proto.handle(), &INTERFACE_OBJECT_CLASS, %(static_methods)s, @@ -2852,9 +3259,10 @@ create_noncallback_interface_object(cx, prototype.handle(), %(name)s, %(length)s, + %(legacyWindowAliases)s, interface.handle_mut()); assert!(!interface.is_null());""" % properties)) - if self.descriptor.hasDescendants(): + if self.descriptor.shouldCacheConstructor(): code.append(CGGeneric("""\ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); (*cache)[PrototypeList::Constructor::%(id)s as usize] = interface.get(); @@ -2867,17 +3275,20 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); if aliasedMembers: def defineAlias(alias): if alias == "@@iterator": - symbolJSID = "RUST_SYMBOL_TO_JSID(GetWellKnownSymbol(cx, SymbolCode::iterator))" - getSymbolJSID = CGGeneric(fill("rooted!(in(cx) let iteratorId = ${symbolJSID});", + symbolJSID = "RUST_SYMBOL_TO_JSID(GetWellKnownSymbol(*cx, SymbolCode::iterator), \ + iteratorId.handle_mut())" + getSymbolJSID = CGGeneric(fill("rooted!(in(*cx) let mut iteratorId: jsid);\n${symbolJSID};\n", symbolJSID=symbolJSID)) defineFn = "JS_DefinePropertyById2" prop = "iteratorId.handle()" + enumFlags = "0" # Not enumerable, per spec. elif alias.startswith("@@"): raise TypeError("Can't handle any well-known Symbol other than @@iterator") else: getSymbolJSID = None defineFn = "JS_DefineProperty" prop = '"%s"' % alias + enumFlags = "JSPROP_ENUMERATE" return CGList([ getSymbolJSID, # XXX If we ever create non-enumerable properties that can @@ -2885,18 +3296,19 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); # match the enumerability of the property being aliased. CGGeneric(fill( """ - assert!(${defineFn}(cx, prototype.handle(), ${prop}, aliasedVal.handle(), - JSPROP_ENUMERATE, None, None)); + assert!(${defineFn}(*cx, prototype.handle(), ${prop}, aliasedVal.handle(), + ${enumFlags} as u32)); """, defineFn=defineFn, - prop=prop)) + prop=prop, + enumFlags=enumFlags)) ], "\n") def defineAliasesFor(m): return CGList([ CGGeneric(fill( """ - assert!(JS_GetProperty(cx, prototype.handle(), + assert!(JS_GetProperty(*cx, prototype.handle(), ${prop} as *const u8 as *const _, aliasedVal.handle_mut())); """, @@ -2908,7 +3320,7 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); // Set up aliases on the interface prototype object we just created. """)), - CGGeneric("rooted!(in(cx) let mut aliasedVal = UndefinedValue());\n\n") + CGGeneric("rooted!(in(*cx) let mut aliasedVal = UndefinedValue());\n\n") ] + [defineAliasesFor(m) for m in sorted(aliasedMembers)]) code.append(defineAliases) @@ -2944,15 +3356,15 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); holderClass = "&Class.base as *const JSClass" holderProto = "prototype.handle()" code.append(CGGeneric(""" -rooted!(in(cx) let mut unforgeable_holder = ptr::null_mut()); +rooted!(in(*cx) let mut unforgeable_holder = ptr::null_mut::<JSObject>()); unforgeable_holder.handle_mut().set( - JS_NewObjectWithoutMetadata(cx, %(holderClass)s, %(holderProto)s)); + JS_NewObjectWithoutMetadata(*cx, %(holderClass)s, %(holderProto)s)); assert!(!unforgeable_holder.is_null()); """ % {'holderClass': holderClass, 'holderProto': holderProto})) code.append(InitUnforgeablePropertiesOnHolder(self.descriptor, self.properties)) code.append(CGGeneric("""\ -JS_SetReservedSlot(prototype.get(), DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, - ObjectValue(unforgeable_holder.get()))""")) +let val = ObjectValue(unforgeable_holder.get()); +JS_SetReservedSlot(prototype.get(), DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, &val)""")) return CGList(code, "\n") @@ -2963,27 +3375,29 @@ class CGGetPerInterfaceObject(CGAbstractMethod): constructor object). """ def __init__(self, descriptor, name, idPrefix="", pub=False): - args = [Argument('*mut JSContext', 'cx'), + args = [Argument('SafeJSContext', 'cx'), Argument('HandleObject', 'global'), - Argument('MutableHandleObject', 'rval')] + Argument('MutableHandleObject', 'mut rval')] CGAbstractMethod.__init__(self, descriptor, name, - 'void', args, pub=pub, unsafe=True) + 'void', args, pub=pub) self.id = idPrefix + "::" + MakeNativeName(self.descriptor.name) def definition_body(self): return CGGeneric(""" -assert!(((*get_object_class(global.get())).flags & JSCLASS_DOM_GLOBAL) != 0); +unsafe { + assert!(((*get_object_class(global.get())).flags & JSCLASS_DOM_GLOBAL) != 0); + + /* Check to see whether the interface objects are already installed */ + let proto_or_iface_array = get_proto_or_iface_array(global.get()); + rval.set((*proto_or_iface_array)[%(id)s as usize]); + if !rval.get().is_null() { + return; + } -/* Check to see whether the interface objects are already installed */ -let proto_or_iface_array = get_proto_or_iface_array(global.get()); -rval.set((*proto_or_iface_array)[%(id)s as usize]); -if !rval.get().is_null() { - return; + CreateInterfaceObjects(cx, global, proto_or_iface_array); + rval.set((*proto_or_iface_array)[%(id)s as usize]); + assert!(!rval.get().is_null()); } - -CreateInterfaceObjects(cx, global, proto_or_iface_array); -rval.set((*proto_or_iface_array)[%(id)s as usize]); -assert!(!rval.get().is_null()); """ % {"id": self.id}) @@ -3072,7 +3486,6 @@ let traps = ProxyTraps { set: None, call: None, construct: None, - getPropertyDescriptor: Some(get_property_descriptor), hasOwn: Some(hasOwn), getOwnEnumerablePropertyKeys: Some(%(getOwnEnumerablePropertyKeys)s), nativeCall: None, @@ -3101,11 +3514,11 @@ class CGDefineDOMInterfaceMethod(CGAbstractMethod): def __init__(self, descriptor): assert descriptor.interface.hasInterfaceObject() args = [ - Argument('*mut JSContext', 'cx'), + Argument('SafeJSContext', 'cx'), Argument('HandleObject', 'global'), ] CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', - 'void', args, pub=True, unsafe=True) + 'void', args, pub=True) def define(self): return CGAbstractMethod.define(self) @@ -3122,15 +3535,15 @@ if !ConstructorEnabled(cx, global) { return; } -rooted!(in(cx) let mut proto = ptr::null_mut()); +rooted!(in(*cx) let mut proto = ptr::null_mut::<JSObject>()); %s(cx, global, proto.handle_mut()); assert!(!proto.is_null());""" % (function,)) def needCx(returnType, arguments, considerTypes): - return (considerTypes and - (typeNeedsCx(returnType, True) or - any(typeNeedsCx(a.type) for a in arguments))) + return (considerTypes + and (typeNeedsCx(returnType, True) + or any(typeNeedsCx(a.type) for a in arguments))) class CGCallGenerator(CGThing): @@ -3143,7 +3556,7 @@ class CGCallGenerator(CGThing): """ def __init__(self, errorResult, arguments, argsPre, returnType, extendedAttributes, descriptor, nativeMethodName, - static, object="this"): + static, object="this", hasCEReactions=False): CGThing.__init__(self) assert errorResult is None or isinstance(errorResult, str) @@ -3165,6 +3578,8 @@ class CGCallGenerator(CGThing): if "cx" not in argsPre and needsCx: args.prepend(CGGeneric("cx")) + if nativeMethodName in descriptor.inRealmMethods: + args.append(CGGeneric("InRealm::in_realm(&AlreadyInRealm::assert_for_cx(cx))")) # Build up our actual call self.cgRoot = CGList([], "\n") @@ -3176,6 +3591,9 @@ class CGCallGenerator(CGThing): call = CGWrapper(call, pre="%s." % object) call = CGList([call, CGWrapper(args, pre="(", post=")")]) + if hasCEReactions: + self.cgRoot.append(CGGeneric("push_new_element_queue();\n")) + self.cgRoot.append(CGList([ CGGeneric("let result: "), result, @@ -3184,6 +3602,9 @@ class CGCallGenerator(CGThing): CGGeneric(";"), ])) + if hasCEReactions: + self.cgRoot.append(CGGeneric("pop_current_element_queue();\n")) + if isFallible: if static: glob = "global.upcast::<GlobalScope>()" @@ -3258,11 +3679,12 @@ class CGPerSignatureCall(CGThing): idlNode.maplikeOrSetlikeOrIterable, idlNode.identifier.name)) else: + hasCEReactions = idlNode.getExtendedAttribute("CEReactions") cgThings.append(CGCallGenerator( errorResult, self.getArguments(), self.argsPre, returnType, self.extendedAttributes, descriptor, nativeMethodName, - static)) + static, hasCEReactions=hasCEReactions)) self.cgRoot = CGList(cgThings, "\n") @@ -3279,7 +3701,7 @@ class CGPerSignatureCall(CGThing): return 'infallible' not in self.extendedAttributes def wrap_return_value(self): - return wrapForType('args.rval()') + return wrapForType('MutableHandleValue::from_raw(args.rval())') def define(self): return (self.cgRoot.define() + "\n" + self.wrap_return_value()) @@ -3354,9 +3776,6 @@ class FakeArgument(): self.variadic = False self.defaultValue = None self._allowTreatNonObjectAsNull = allowTreatNonObjectAsNull - self.treatNullAs = interfaceMember.treatNullAs - self.enforceRange = False - self.clamp = False def allowTreatNonCallableAsNull(self): return self._allowTreatNonObjectAsNull @@ -3399,9 +3818,13 @@ class CGAbstractStaticBindingMethod(CGAbstractMethod): self.exposureSet = descriptor.interface.exposureSet def definition_body(self): - preamble = "let global = GlobalScope::from_object(JS_CALLEE(cx, vp).to_object());\n" + preamble = """\ +let args = CallArgs::from_vp(vp, argc); +let global = GlobalScope::from_object(args.callee()); +""" if len(self.exposureSet) == 1: - preamble += "let global = Root::downcast::<dom::types::%s>(global).unwrap();\n" % list(self.exposureSet)[0] + preamble += ("let global = DomRoot::downcast::<dom::types::%s>(global).unwrap();\n" % + list(self.exposureSet)[0]) return CGList([CGGeneric(preamble), self.generate_code()]) def generate_code(self): @@ -3416,8 +3839,9 @@ class CGSpecializedMethod(CGAbstractExternMethod): def __init__(self, descriptor, method): self.method = method name = method.identifier.name - args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', '_obj'), - Argument('*const %s' % descriptor.concreteType, 'this'), + args = [Argument('*mut JSContext', 'cx'), + Argument('RawHandleObject', '_obj'), + Argument('*mut libc::c_void', 'this'), Argument('*const JSJitMethodCallArgs', 'args')] CGAbstractExternMethod.__init__(self, descriptor, name, 'bool', args) @@ -3426,12 +3850,15 @@ class CGSpecializedMethod(CGAbstractExternMethod): self.method) return CGWrapper(CGMethodCall([], nativeName, self.method.isStatic(), self.descriptor, self.method), - pre="let this = &*this;\n" - "let args = &*args;\n" - "let argc = args._base.argc_;\n") + pre="let cx = SafeJSContext::from_ptr(cx);\n" + + ("let this = &*(this as *const %s);\n" % self.descriptor.concreteType) + + "let args = &*args;\n" + "let argc = args.argc_;\n") @staticmethod def makeNativeName(descriptor, method): + if method.underlyingAttr: + return CGSpecializedGetter.makeNativeName(descriptor, method.underlyingAttr) name = method.identifier.name nativeName = descriptor.binaryNameFor(name) if nativeName == name: @@ -3439,6 +3866,45 @@ class CGSpecializedMethod(CGAbstractExternMethod): return MakeNativeName(nativeName) +class CGDefaultToJSONMethod(CGSpecializedMethod): + def __init__(self, descriptor, method): + assert method.isDefaultToJSON() + CGSpecializedMethod.__init__(self, descriptor, method) + + def definition_body(self): + ret = dedent(""" + use crate::dom::bindings::inheritance::HasParent; + rooted!(in(cx) let result = JS_NewPlainObject(cx)); + if result.is_null() { + return false; + } + """) + + jsonDescriptors = [self.descriptor] + interface = self.descriptor.interface.parent + while interface: + descriptor = self.descriptor.getDescriptor(interface.identifier.name) + if descriptor.hasDefaultToJSON: + jsonDescriptors.append(descriptor) + interface = interface.parent + + parents = len(jsonDescriptors) - 1 + form = """ + if !${parentclass}CollectJSONAttributes(cx, _obj, this, &result) { + return false; + } + """ + + # Iterate the array in reverse: oldest ancestor first + for descriptor in jsonDescriptors[:0:-1]: + ret += fill(form, parentclass=toBindingNamespace(descriptor.name) + "::") + parents -= 1 + ret += fill(form, parentclass="") + ret += ('(*args).rval().set(ObjectValue(*result));\n' + 'return true;\n') + return CGGeneric(ret) + + class CGStaticMethod(CGAbstractStaticBindingMethod): """ A class for generating the Rust code for an IDL static method. @@ -3451,9 +3917,10 @@ class CGStaticMethod(CGAbstractStaticBindingMethod): def generate_code(self): nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, self.method) + safeContext = CGGeneric("let cx = SafeJSContext::from_ptr(cx);\n") setupArgs = CGGeneric("let args = CallArgs::from_vp(vp, argc);\n") call = CGMethodCall(["&global"], nativeName, True, self.descriptor, self.method) - return CGList([setupArgs, call]) + return CGList([safeContext, setupArgs, call]) class CGSpecializedGetter(CGAbstractExternMethod): @@ -3465,8 +3932,8 @@ class CGSpecializedGetter(CGAbstractExternMethod): self.attr = attr name = 'get_' + descriptor.internalNameFor(attr.identifier.name) args = [Argument('*mut JSContext', 'cx'), - Argument('HandleObject', '_obj'), - Argument('*const %s' % descriptor.concreteType, 'this'), + Argument('RawHandleObject', '_obj'), + Argument('*mut libc::c_void', 'this'), Argument('JSJitGetterCallArgs', 'args')] CGAbstractExternMethod.__init__(self, descriptor, name, "bool", args) @@ -3476,7 +3943,8 @@ class CGSpecializedGetter(CGAbstractExternMethod): return CGWrapper(CGGetterCall([], self.attr.type, nativeName, self.descriptor, self.attr), - pre="let this = &*this;\n") + pre="let cx = SafeJSContext::from_ptr(cx);\n" + + ("let this = &*(this as *const %s);\n" % self.descriptor.concreteType)) @staticmethod def makeNativeName(descriptor, attr): @@ -3505,10 +3973,11 @@ class CGStaticGetter(CGAbstractStaticBindingMethod): def generate_code(self): nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, self.attr) + safeContext = CGGeneric("let cx = SafeJSContext::from_ptr(cx);\n") setupArgs = CGGeneric("let args = CallArgs::from_vp(vp, argc);\n") call = CGGetterCall(["&global"], self.attr.type, nativeName, self.descriptor, self.attr) - return CGList([setupArgs, call]) + return CGList([safeContext, setupArgs, call]) class CGSpecializedSetter(CGAbstractExternMethod): @@ -3520,8 +3989,8 @@ class CGSpecializedSetter(CGAbstractExternMethod): self.attr = attr name = 'set_' + descriptor.internalNameFor(attr.identifier.name) args = [Argument('*mut JSContext', 'cx'), - Argument('HandleObject', 'obj'), - Argument('*const %s' % descriptor.concreteType, 'this'), + Argument('RawHandleObject', 'obj'), + Argument('*mut libc::c_void', 'this'), Argument('JSJitSetterCallArgs', 'args')] CGAbstractExternMethod.__init__(self, descriptor, name, "bool", args) @@ -3530,7 +3999,8 @@ class CGSpecializedSetter(CGAbstractExternMethod): self.attr) return CGWrapper(CGSetterCall([], self.attr.type, nativeName, self.descriptor, self.attr), - pre="let this = &*this;\n") + pre="let cx = SafeJSContext::from_ptr(cx);\n" + + ("let this = &*(this as *const %s);\n" % self.descriptor.concreteType)) @staticmethod def makeNativeName(descriptor, attr): @@ -3553,15 +4023,16 @@ class CGStaticSetter(CGAbstractStaticBindingMethod): def generate_code(self): nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, self.attr) + safeContext = CGGeneric("let cx = SafeJSContext::from_ptr(cx);\n") checkForArg = CGGeneric( "let args = CallArgs::from_vp(vp, argc);\n" "if argc == 0 {\n" - " throw_type_error(cx, \"Not enough arguments to %s setter.\");\n" + " throw_type_error(*cx, \"Not enough arguments to %s setter.\");\n" " return false;\n" "}" % self.attr.identifier.name) call = CGSetterCall(["&global"], self.attr.type, nativeName, self.descriptor, self.attr) - return CGList([checkForArg, call]) + return CGList([safeContext, checkForArg, call]) class CGSpecializedForwardingSetter(CGSpecializedSetter): @@ -3578,16 +4049,17 @@ class CGSpecializedForwardingSetter(CGSpecializedSetter): assert all(ord(c) < 128 for c in attrName) assert all(ord(c) < 128 for c in forwardToAttrName) return CGGeneric("""\ -rooted!(in(cx) let mut v = UndefinedValue()); -if !JS_GetProperty(cx, obj, %s as *const u8 as *const libc::c_char, v.handle_mut()) { +let cx = SafeJSContext::from_ptr(cx); +rooted!(in(*cx) let mut v = UndefinedValue()); +if !JS_GetProperty(*cx, HandleObject::from_raw(obj), %s as *const u8 as *const libc::c_char, v.handle_mut()) { return false; } if !v.is_object() { - throw_type_error(cx, "Value.%s is not an object."); + throw_type_error(*cx, "Value.%s is not an object."); return false; } -rooted!(in(cx) let target_obj = v.to_object()); -JS_SetProperty(cx, target_obj.handle(), %s as *const u8 as *const libc::c_char, args.get(0)) +rooted!(in(*cx) let target_obj = v.to_object()); +JS_SetProperty(*cx, target_obj.handle(), %s as *const u8 as *const libc::c_char, HandleValue::from_raw(args.get(0))) """ % (str_to_const_array(attrName), attrName, str_to_const_array(forwardToAttrName))) @@ -3604,8 +4076,8 @@ class CGSpecializedReplaceableSetter(CGSpecializedSetter): # JS_DefineProperty can only deal with ASCII. assert all(ord(c) < 128 for c in name) return CGGeneric("""\ -JS_DefineProperty(cx, obj, %s as *const u8 as *const libc::c_char, - args.get(0), JSPROP_ENUMERATE, None, None)""" % name) +JS_DefineProperty(cx, HandleObject::from_raw(obj), %s as *const u8 as *const libc::c_char, + HandleValue::from_raw(args.get(0)), JSPROP_ENUMERATE as u32)""" % name) class CGMemberJITInfo(CGThing): @@ -3634,11 +4106,16 @@ class CGMemberJITInfo(CGThing): initializer = fill( """ JSJitInfo { - call: ${opName} as *const os::raw::c_void, - protoID: PrototypeList::ID::${name} as u16, - depth: ${depth}, - _bitfield_1: - JSJitInfo::new_bitfield_1( + __bindgen_anon_1: JSJitInfo__bindgen_ty_1 { + ${opKind}: Some(${opName}) + }, + __bindgen_anon_2: JSJitInfo__bindgen_ty_2 { + protoID: PrototypeList::ID::${name} as u16, + }, + __bindgen_anon_3: JSJitInfo__bindgen_ty_3 { depth: ${depth} }, + _bitfield_align_1: [], + _bitfield_1: __BindgenBitfieldUnit::new( + new_jsjitinfo_bitfield_1!( JSJitInfo_OpType::${opType} as u8, JSJitInfo_AliasSet::${aliasSet} as u8, JSValueType::${returnType} as u8, @@ -3648,17 +4125,19 @@ class CGMemberJITInfo(CGThing): ${isAlwaysInSlot}, ${isLazilyCachedInSlot}, ${isTypedMethod}, - ${slotIndex} as u16, - ) + ${slotIndex}, + ).to_ne_bytes() + ), } """, opName=opName, name=self.descriptor.name, depth=self.descriptor.interface.inheritanceDepth(), opType=opType, + opKind=opType.lower(), aliasSet=aliasSet, - returnType=reduce(CGMemberJITInfo.getSingleReturnType, returnTypes, - ""), + returnType=functools.reduce(CGMemberJITInfo.getSingleReturnType, returnTypes, + ""), isInfallible=toStringBool(infallible), isMovable=toStringBool(movable), # FIXME(nox): https://github.com/servo/servo/issues/10991 @@ -3788,8 +4267,8 @@ class CGMemberJITInfo(CGThing): # don't want them coalesced with each other or loop-hoisted, since # their return value can change even if nothing is going on from our # point of view. - return (affects == "Nothing" and - (dependsOn != "Everything" and dependsOn != "DeviceState")) + return (affects == "Nothing" + and (dependsOn != "Everything" and dependsOn != "DeviceState")) def aliasSet(self): """Returns the alias set to store in the jitinfo. This may not be the @@ -3821,7 +4300,9 @@ class CGMemberJITInfo(CGThing): return "JSVAL_TYPE_UNDEFINED" if t.isSequence(): return "JSVAL_TYPE_OBJECT" - if t.isMozMap(): + if t.isRecord(): + return "JSVAL_TYPE_OBJECT" + if t.isPromise(): return "JSVAL_TYPE_OBJECT" if t.isGeckoInterface(): return "JSVAL_TYPE_OBJECT" @@ -3843,12 +4324,10 @@ class CGMemberJITInfo(CGThing): if u.hasNullableType: # Might be null or not return "JSVAL_TYPE_UNKNOWN" - return reduce(CGMemberJITInfo.getSingleReturnType, - u.flatMemberTypes, "") + return functools.reduce(CGMemberJITInfo.getSingleReturnType, + u.flatMemberTypes, "") if t.isDictionary(): return "JSVAL_TYPE_OBJECT" - if t.isDate(): - return "JSVAL_TYPE_OBJECT" if not t.isPrimitive(): raise TypeError("No idea what type " + str(t) + " is.") tag = t.tag() @@ -3879,10 +4358,10 @@ class CGMemberJITInfo(CGThing): if type == existingType: return existingType - if ((type == "JSVAL_TYPE_DOUBLE" and - existingType == "JSVAL_TYPE_INT32") or - (existingType == "JSVAL_TYPE_DOUBLE" and - type == "JSVAL_TYPE_INT32")): + if ((type == "JSVAL_TYPE_DOUBLE" + and existingType == "JSVAL_TYPE_INT32") + or (existingType == "JSVAL_TYPE_DOUBLE" + and type == "JSVAL_TYPE_INT32")): # Promote INT32 to DOUBLE as needed return "JSVAL_TYPE_DOUBLE" # Different types @@ -3913,13 +4392,11 @@ class CGMemberJITInfo(CGThing): return "JSJitInfo_ArgType::Object as i32" if t.isUnion(): u = t.unroll() - type = "JSJitInfo::Null as i32" if u.hasNullableType else "" - return reduce(CGMemberJITInfo.getSingleArgType, - u.flatMemberTypes, type) + type = "JSJitInfo_ArgType::Null as i32" if u.hasNullableType else "" + return functools.reduce(CGMemberJITInfo.getSingleArgType, + u.flatMemberTypes, type) if t.isDictionary(): return "JSJitInfo_ArgType::Object as i32" - if t.isDate(): - return "JSJitInfo_ArgType::Object as i32" if not t.isPrimitive(): raise TypeError("No idea what type " + str(t) + " is.") tag = t.tag() @@ -3963,7 +4440,7 @@ def getEnumValueName(value): if re.match("[^\x20-\x7E]", value): raise SyntaxError('Enum value "' + value + '" contains non-ASCII characters') if re.match("^[0-9]", value): - raise SyntaxError('Enum value "' + value + '" starts with a digit') + value = '_' + value value = re.sub(r'[^0-9A-Za-z_]', '_', value) if re.match("^_[A-Z]|__", value): raise SyntaxError('Enum value "' + value + '" is reserved by the C++ spec') @@ -3981,35 +4458,66 @@ class CGEnum(CGThing): ident = enum.identifier.name decl = """\ #[repr(usize)] -#[derive(JSTraceable, PartialEq, Copy, Clone, HeapSizeOf, Debug)] +#[derive(Copy, Clone, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub enum %s { %s } -""" % (ident, ",\n ".join(map(getEnumValueName, enum.values()))) - - pairs = ",\n ".join(['("%s", super::%s::%s)' % (val, ident, getEnumValueName(val)) for val in enum.values()]) - - inner = """\ -use dom::bindings::conversions::ToJSValConvertible; -use js::jsapi::{JSContext, MutableHandleValue}; +""" % (ident, ",\n ".join(map(getEnumValueName, list(enum.values())))) + + pairs = ",\n ".join(['("%s", super::%s::%s)' % (val, ident, getEnumValueName(val)) + for val in list(enum.values())]) + + inner = string.Template("""\ +use crate::dom::bindings::conversions::ConversionResult; +use crate::dom::bindings::conversions::FromJSValConvertible; +use crate::dom::bindings::conversions::ToJSValConvertible; +use crate::dom::bindings::utils::find_enum_value; +use js::jsapi::JSContext; +use js::rust::HandleValue; +use js::rust::MutableHandleValue; use js::jsval::JSVal; -pub const pairs: &'static [(&'static str, super::%s)] = &[ - %s, +pub const pairs: &'static [(&'static str, super::${ident})] = &[ + ${pairs}, ]; -impl super::%s { +impl super::${ident} { pub fn as_str(&self) -> &'static str { pairs[*self as usize].0 } } -impl ToJSValConvertible for super::%s { +impl Default for super::${ident} { + fn default() -> super::${ident} { + pairs[0].1 + } +} + +impl ToJSValConvertible for super::${ident} { unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { pairs[*self as usize].0.to_jsval(cx, rval); } } - """ % (ident, pairs, ident, ident) + +impl FromJSValConvertible for super::${ident} { + type Config = (); + unsafe fn from_jsval(cx: *mut JSContext, value: HandleValue, _option: ()) + -> Result<ConversionResult<super::${ident}>, ()> { + match find_enum_value(cx, value, pairs) { + Err(_) => Err(()), + Ok((None, search)) => { + Ok(ConversionResult::Failure( + format!("'{}' is not a valid enum value for enumeration '${ident}'.", search).into() + )) + } + Ok((Some(&value), _)) => Ok(ConversionResult::Success(value)), + } + } +} + """).substitute({ + 'ident': ident, + 'pairs': pairs + }) self.cgRoot = CGList([ CGGeneric(decl), CGNamespace.build([ident + "Values"], @@ -4044,7 +4552,17 @@ class CGConstant(CGThing): def define(self): name = self.constant.identifier.name value = convertConstIDLValueToRust(self.constant.value) - return "pub const %s: %s = %s;\n" % (name, builtinNames[self.constant.value.type.tag()], value) + + tag = self.constant.value.type.tag() + const_type = builtinNames[self.constant.value.type.tag()] + # Finite<f32> or Finite<f64> cannot be used un a constant declaration. + # Remote the Finite type from restricted float and double tag declarations. + if tag == IDLType.Tags.float: + const_type = "f32" + elif tag == IDLType.Tags.double: + const_type = "f64" + + return "pub const %s: %s = %s;\n" % (name, const_type, value) def getUnionTypeTemplateVars(type, descriptorProvider): @@ -4057,7 +4575,7 @@ def getUnionTypeTemplateVars(type, descriptorProvider): elif type.isDictionary(): name = type.name typeName = name - elif type.isSequence() or type.isMozMap(): + elif type.isSequence() or type.isRecord(): name = type.name inner = getUnionTypeTemplateVars(innerContainerType(type), descriptorProvider) typeName = wrapInNativeContainerType(type, CGGeneric(inner["typeName"])).define() @@ -4076,6 +4594,15 @@ def getUnionTypeTemplateVars(type, descriptorProvider): elif type.isObject(): name = type.name typeName = "Heap<*mut JSObject>" + elif type.isReadableStream(): + name = type.name + typeName = "DomRoot<ReadableStream>" + elif is_typed_array(type): + name = type.name + typeName = "typedarray::Heap" + name + elif type.isCallback(): + name = type.name + typeName = name else: raise TypeError("Can't handle %s in unions yet" % type) @@ -4107,15 +4634,29 @@ class CGUnionStruct(CGThing): self.type = type self.descriptorProvider = descriptorProvider + def membersNeedTracing(self): + for t in self.type.flatMemberTypes: + if type_needs_tracing(t): + return True + return False + def define(self): - templateVars = map(lambda t: getUnionTypeTemplateVars(t, self.descriptorProvider), - self.type.flatMemberTypes) + def getTypeWrapper(t): + if type_needs_tracing(t): + return "RootedTraceableBox" + if t.isCallback(): + return "Rc" + return "" + + templateVars = [(getUnionTypeTemplateVars(t, self.descriptorProvider), + getTypeWrapper(t)) for t in self.type.flatMemberTypes] enumValues = [ - " %s(%s)," % (v["name"], v["typeName"]) for v in templateVars + " %s(%s)," % (v["name"], "%s<%s>" % (wrapper, v["typeName"]) if wrapper else v["typeName"]) + for (v, wrapper) in templateVars ] enumConversions = [ " %s::%s(ref inner) => inner.to_jsval(cx, rval)," - % (self.type, v["name"]) for v in templateVars + % (self.type, v["name"]) for (v, _) in templateVars ] return ("""\ #[derive(JSTraceable)] @@ -4142,6 +4683,12 @@ class CGUnionConversionStruct(CGThing): self.type = type self.descriptorProvider = descriptorProvider + def membersNeedTracing(self): + for t in self.type.flatMemberTypes: + if type_needs_tracing(t): + return True + return False + def from_jsval(self): memberTypes = self.type.flatMemberTypes names = [] @@ -4155,13 +4702,13 @@ class CGUnionConversionStruct(CGThing): def get_match(name): return ( - "match %s::TryConvertTo%s(cx, value) {\n" + "match %s::TryConvertTo%s(SafeJSContext::from_ptr(cx), value) {\n" " Err(_) => return Err(()),\n" " Ok(Some(value)) => return Ok(ConversionResult::Success(%s::%s(value))),\n" " Ok(None) => (),\n" "}\n") % (self.type, name, self.type, name) - interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes) + interfaceMemberTypes = [t for t in memberTypes if t.isNonCallbackInterface()] if len(interfaceMemberTypes) > 0: typeNames = [get_name(memberType) for memberType in interfaceMemberTypes] interfaceObject = CGList(CGGeneric(get_match(typeName)) for typeName in typeNames) @@ -4169,7 +4716,7 @@ class CGUnionConversionStruct(CGThing): else: interfaceObject = None - arrayObjectMemberTypes = filter(lambda t: t.isSequence(), memberTypes) + arrayObjectMemberTypes = [t for t in memberTypes if t.isSequence()] if len(arrayObjectMemberTypes) > 0: assert len(arrayObjectMemberTypes) == 1 typeName = arrayObjectMemberTypes[0].name @@ -4178,21 +4725,15 @@ class CGUnionConversionStruct(CGThing): else: arrayObject = None - dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes) - if len(dateObjectMemberTypes) > 0: - assert len(dateObjectMemberTypes) == 1 - raise TypeError("Can't handle dates in unions.") - else: - dateObject = None - - callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes) + callbackMemberTypes = [t for t in memberTypes if t.isCallback() or t.isCallbackInterface()] if len(callbackMemberTypes) > 0: assert len(callbackMemberTypes) == 1 - raise TypeError("Can't handle callbacks in unions.") + typeName = callbackMemberTypes[0].name + callbackObject = CGGeneric(get_match(typeName)) else: callbackObject = None - dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes) + dictionaryMemberTypes = [t for t in memberTypes if t.isDictionary()] if len(dictionaryMemberTypes) > 0: assert len(dictionaryMemberTypes) == 1 typeName = dictionaryMemberTypes[0].name @@ -4201,7 +4742,7 @@ class CGUnionConversionStruct(CGThing): else: dictionaryObject = None - objectMemberTypes = filter(lambda t: t.isObject(), memberTypes) + objectMemberTypes = [t for t in memberTypes if t.isObject()] if len(objectMemberTypes) > 0: assert len(objectMemberTypes) == 1 typeName = objectMemberTypes[0].name @@ -4210,7 +4751,7 @@ class CGUnionConversionStruct(CGThing): else: object = None - mozMapMemberTypes = filter(lambda t: t.isMozMap(), memberTypes) + mozMapMemberTypes = [t for t in memberTypes if t.isRecord()] if len(mozMapMemberTypes) > 0: assert len(mozMapMemberTypes) == 1 typeName = mozMapMemberTypes[0].name @@ -4219,21 +4760,29 @@ class CGUnionConversionStruct(CGThing): else: mozMapObject = None - hasObjectTypes = interfaceObject or arrayObject or dateObject or object or mozMapObject + hasObjectTypes = object or interfaceObject or arrayObject or callbackObject or mozMapObject if hasObjectTypes: # "object" is not distinguishable from other types - assert not object or not (interfaceObject or arrayObject or dateObject or callbackObject or mozMapObject) + assert not object or not (interfaceObject or arrayObject or callbackObject or mozMapObject) templateBody = CGList([], "\n") + if arrayObject or callbackObject: + # An object can be both an sequence object and a callback or + # dictionary, but we shouldn't have both in the union's members + # because they are not distinguishable. + assert not (arrayObject and callbackObject) + templateBody.append(arrayObject if arrayObject else callbackObject) if interfaceObject: + assert not object templateBody.append(interfaceObject) - if arrayObject: - templateBody.append(arrayObject) + elif object: + templateBody.append(object) if mozMapObject: templateBody.append(mozMapObject) + conversions.append(CGIfWrapper("value.get().is_object()", templateBody)) if dictionaryObject: - assert not hasObjectTypes + assert not object conversions.append(dictionaryObject) stringTypes = [t for t in memberTypes if t.isString() or t.isEnum()] @@ -4248,9 +4797,9 @@ class CGUnionConversionStruct(CGThing): typename = get_name(memberType) return CGGeneric(get_match(typename)) other = [] - stringConversion = map(getStringOrPrimitiveConversion, stringTypes) - numericConversion = map(getStringOrPrimitiveConversion, numericTypes) - booleanConversion = map(getStringOrPrimitiveConversion, booleanTypes) + stringConversion = list(map(getStringOrPrimitiveConversion, stringTypes)) + numericConversion = list(map(getStringOrPrimitiveConversion, numericTypes)) + booleanConversion = list(map(getStringOrPrimitiveConversion, booleanTypes)) if stringConversion: if booleanConversion: other.append(CGIfWrapper("value.get().is_boolean()", booleanConversion[0])) @@ -4266,8 +4815,8 @@ class CGUnionConversionStruct(CGThing): other.append(booleanConversion[0]) conversions.append(CGList(other, "\n\n")) conversions.append(CGGeneric( - "throw_not_in_union(cx, \"%s\");\n" - "Err(())" % ", ".join(names))) + "Ok(ConversionResult::Failure(\"argument could not be converted to any of: %s\".into()))" % ", ".join(names) + )) method = CGWrapper( CGIndenter(CGList(conversions, "\n\n")), pre="unsafe fn from_jsval(cx: *mut JSContext,\n" @@ -4285,13 +4834,17 @@ class CGUnionConversionStruct(CGThing): def try_method(self, t): templateVars = getUnionTypeTemplateVars(t, self.descriptorProvider) - returnType = "Result<Option<%s>, ()>" % templateVars["typeName"] + actualType = templateVars["typeName"] + if type_needs_tracing(t): + actualType = "RootedTraceableBox<%s>" % actualType + if t.isCallback(): + actualType = "Rc<%s>" % actualType + returnType = "Result<Option<%s>, ()>" % actualType jsConversion = templateVars["jsConversion"] return CGWrapper( CGIndenter(jsConversion, 4), - # TryConvertToObject is unused, but not generating it while generating others is tricky. - pre="#[allow(dead_code)] unsafe fn TryConvertTo%s(cx: *mut JSContext, value: HandleValue) -> %s {\n" + pre="unsafe fn TryConvertTo%s(cx: SafeJSContext, value: HandleValue) -> %s {\n" % (t.name, returnType), post="\n}") @@ -4338,7 +4891,7 @@ class ClassMethod(ClassItem): def __init__(self, name, returnType, args, inline=False, static=False, virtual=False, const=False, bodyInHeader=False, templateArgs=None, visibility='public', body=None, - breakAfterReturnDecl="\n", + breakAfterReturnDecl="\n", unsafe=False, breakAfterSelf="\n", override=False): """ override indicates whether to flag the method as MOZ_OVERRIDE @@ -4357,6 +4910,7 @@ class ClassMethod(ClassItem): self.breakAfterReturnDecl = breakAfterReturnDecl self.breakAfterSelf = breakAfterSelf self.override = override + self.unsafe = unsafe ClassItem.__init__(self, name, visibility) def getDecorators(self, declaring): @@ -4389,7 +4943,7 @@ class ClassMethod(ClassItem): return string.Template( "${decorators}%s" - "${visibility}fn ${name}${templateClause}(${args})${returnType}${const}${override}${body}%s" % + "${visibility}${unsafe}fn ${name}${templateClause}(${args})${returnType}${const}${override}${body}%s" % (self.breakAfterReturnDecl, self.breakAfterSelf) ).substitute({ 'templateClause': templateClause, @@ -4400,7 +4954,8 @@ class ClassMethod(ClassItem): 'override': ' MOZ_OVERRIDE' if self.override else '', 'args': args, 'body': body, - 'visibility': self.visibility + ' ' if self.visibility != 'priv' else '' + 'visibility': self.visibility + ' ' if self.visibility != 'priv' else '', + 'unsafe': "unsafe " if self.unsafe else "", }) def define(self, cgClass): @@ -4470,7 +5025,7 @@ class ClassConstructor(ClassItem): "});\n" "// Note: callback cannot be moved after calling init.\n" "match Rc::get_mut(&mut ret) {\n" - " Some(ref mut callback) => unsafe { callback.parent.init(%s, %s) },\n" + " Some(ref mut callback) => callback.parent.init(%s, %s),\n" " None => unreachable!(),\n" "};\n" "ret") % (cgClass.name, '\n'.join(initializers), @@ -4485,7 +5040,7 @@ class ClassConstructor(ClassItem): body = ' {\n' + body + '}' return string.Template("""\ -pub fn ${decorators}new(${args}) -> Rc<${className}>${body} +pub unsafe fn ${decorators}new(${args}) -> Rc<${className}>${body} """).substitute({'decorators': self.getDecorators(True), 'className': cgClass.getNameString(), 'args': args, @@ -4668,11 +5223,11 @@ class CGProxySpecialOperation(CGPerSignatureCall): False, descriptor, operation, len(arguments)) - if operation.isSetter() or operation.isCreator(): + if operation.isSetter(): # arguments[0] is the index or name of the item that we're setting. argument = arguments[1] info = getJSToNativeConversionInfo( - argument.type, descriptor, treatNullAs=argument.treatNullAs, + argument.type, descriptor, exceptionCode="return false;") template = info.template declType = info.declType @@ -4682,7 +5237,7 @@ class CGProxySpecialOperation(CGPerSignatureCall): } self.cgRoot.prepend(instantiateJSToNativeConversionTemplate( template, templateValues, declType, argument.identifier.name)) - self.cgRoot.prepend(CGGeneric("rooted!(in(cx) let value = desc.value);")) + self.cgRoot.prepend(CGGeneric("rooted!(in(*cx) let value = desc.value);")) def getArguments(self): args = [(a, process_arg(a.identifier.name, a)) for a in self.arguments] @@ -4725,10 +5280,10 @@ class CGProxyNamedOperation(CGProxySpecialOperation): def define(self): # Our first argument is the id we're getting. argName = self.arguments[0].identifier.name - return ("let %s = string_jsid_to_string(cx, id);\n" + return ("let %s = jsid_to_string(*cx, Handle::from_raw(id)).expect(\"Not a string-convertible JSID?\");\n" "let this = UnwrapProxy(proxy);\n" - "let this = &*this;\n" % argName + - CGProxySpecialOperation.define(self)) + "let this = &*this;\n" % argName + + CGProxySpecialOperation.define(self)) class CGProxyNamedGetter(CGProxyNamedOperation): @@ -4768,56 +5323,52 @@ class CGProxyNamedDeleter(CGProxyNamedOperation): class CGProxyUnwrap(CGAbstractMethod): def __init__(self, descriptor): - args = [Argument('HandleObject', 'obj')] + args = [Argument('RawHandleObject', 'obj')] CGAbstractMethod.__init__(self, descriptor, "UnwrapProxy", '*const ' + descriptor.concreteType, args, alwaysInline=True, unsafe=True) def definition_body(self): return CGGeneric("""\ -/*if (xpc::WrapperFactory::IsXrayWrapper(obj)) { - obj = js::UnwrapObject(obj); -}*/ -//MOZ_ASSERT(IsProxy(obj)); -let box_ = GetProxyPrivate(obj.get()).to_private() as *const %s; + let mut slot = UndefinedValue(); + GetProxyReservedSlot(obj.get(), 0, &mut slot); + let box_ = slot.to_private() as *const %s; return box_;""" % self.descriptor.concreteType) class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): def __init__(self, descriptor): - args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', 'proxy'), - Argument('HandleId', 'id'), - Argument('MutableHandle<PropertyDescriptor>', 'desc')] + args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', 'proxy'), + Argument('RawHandleId', 'id'), + Argument('RawMutableHandle<PropertyDescriptor>', 'mut desc')] CGAbstractExternMethod.__init__(self, descriptor, "getOwnPropertyDescriptor", "bool", args) self.descriptor = descriptor + # https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty def getBody(self): indexedGetter = self.descriptor.operations['IndexedGetter'] - indexedSetter = self.descriptor.operations['IndexedSetter'] - - get = "" - if indexedGetter or indexedSetter: - get = "let index = get_array_index_from_id(cx, id);\n" + get = "let cx = SafeJSContext::from_ptr(cx);\n" if indexedGetter: + get += "let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" + attrs = "JSPROP_ENUMERATE" if self.descriptor.operations['IndexedSetter'] is None: attrs += " | JSPROP_READONLY" - # FIXME(#11868) Should assign to desc.value, desc.get() is a copy. - fillDescriptor = ("desc.get().value = result_root.get();\n" - "fill_property_descriptor(desc, proxy.get(), %s);\n" + fillDescriptor = ("desc.value = result_root.get();\n" + "fill_property_descriptor(MutableHandle::from_raw(desc), proxy.get(), (%s) as u32);\n" "return true;" % attrs) templateValues = { 'jsvalRef': 'result_root.handle_mut()', 'successCode': fillDescriptor, - 'pre': 'rooted!(in(cx) let mut result_root = UndefinedValue());' + 'pre': 'rooted!(in(*cx) let mut result_root = UndefinedValue());' } - get += ("if let Some(index) = index {\n" + - " let this = UnwrapProxy(proxy);\n" + - " let this = &*this;\n" + - CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" + - "}\n") + get += ("if let Some(index) = index {\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = &*this;\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" + + "}\n") namedGetter = self.descriptor.operations['NamedGetter'] if namedGetter: @@ -4830,49 +5381,54 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): attrs = " | ".join(attrs) else: attrs = "0" - # FIXME(#11868) Should assign to desc.value, desc.get() is a copy. - fillDescriptor = ("desc.get().value = result_root.get();\n" - "fill_property_descriptor(desc, proxy.get(), %s);\n" + fillDescriptor = ("desc.value = result_root.get();\n" + "fill_property_descriptor(MutableHandle::from_raw(desc), proxy.get(), (%s) as u32);\n" "return true;" % attrs) templateValues = { 'jsvalRef': 'result_root.handle_mut()', 'successCode': fillDescriptor, - 'pre': 'rooted!(in(cx) let mut result_root = UndefinedValue());' + 'pre': 'rooted!(in(*cx) let mut result_root = UndefinedValue());' } + + # See the similar-looking in CGDOMJSProxyHandler_get for the spec quote. + condition = "RUST_JSID_IS_STRING(id) || RUST_JSID_IS_INT(id)" + if indexedGetter: + condition = "index.is_none() && (%s)" % condition # Once we start supporting OverrideBuiltins we need to make # ResolveOwnProperty or EnumerateOwnProperties filter out named # properties that shadow prototype properties. namedGet = """ -if RUST_JSID_IS_STRING(id) { +if %s { let mut has_on_proto = false; - if !has_property_on_prototype(cx, proxy, id, &mut has_on_proto) { + if !has_property_on_prototype(*cx, proxy_lt, id_lt, &mut has_on_proto) { return false; } if !has_on_proto { %s } } -""" % CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues), 8).define() +""" % (condition, CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues), 8).define()) else: namedGet = "" - # FIXME(#11868) Should assign to desc.obj, desc.get() is a copy. return get + """\ -rooted!(in(cx) let mut expando = ptr::null_mut()); +rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>()); get_expando_object(proxy, expando.handle_mut()); //if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) { +let proxy_lt = Handle::from_raw(proxy); +let id_lt = Handle::from_raw(id); if !expando.is_null() { - if !JS_GetPropertyDescriptorById(cx, expando.handle(), id, desc) { + if !JS_GetPropertyDescriptorById(*cx, expando.handle().into(), id, desc) { return false; } if !desc.obj.is_null() { // Pretend the property lives on the wrapper. - desc.get().obj = proxy.get(); + desc.obj = proxy.get(); return true; } } """ + namedGet + """\ -desc.get().obj = ptr::null_mut(); +desc.obj = ptr::null_mut(); return true;""" def definition_body(self): @@ -4881,28 +5437,28 @@ return true;""" class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): def __init__(self, descriptor): - args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', 'proxy'), - Argument('HandleId', 'id'), - Argument('Handle<PropertyDescriptor>', 'desc'), + args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', 'proxy'), + Argument('RawHandleId', 'id'), + Argument('RawHandle<PropertyDescriptor>', 'desc'), Argument('*mut ObjectOpResult', 'opresult')] CGAbstractExternMethod.__init__(self, descriptor, "defineProperty", "bool", args) self.descriptor = descriptor def getBody(self): - set = "" + set = "let cx = SafeJSContext::from_ptr(cx);\n" indexedSetter = self.descriptor.operations['IndexedSetter'] if indexedSetter: - set += ("let index = get_array_index_from_id(cx, id);\n" + - "if let Some(index) = index {\n" + - " let this = UnwrapProxy(proxy);\n" + - " let this = &*this;\n" + - CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() + - " return (*opresult).succeed();\n" + - "}\n") + set += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" + + "if let Some(index) = index {\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = &*this;\n" + + CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() + + " return (*opresult).succeed();\n" + + "}\n") elif self.descriptor.operations['IndexedGetter']: - set += ("if get_array_index_from_id(cx, id).is_some() {\n" + - " return (*opresult).failNoIndexedSetter();\n" + + set += ("if get_array_index_from_id(*cx, Handle::from_raw(id)).is_some() {\n" + " return (*opresult).failNoIndexedSetter();\n" "}\n") namedSetter = self.descriptor.operations['NamedSetter'] @@ -4910,20 +5466,18 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): if self.descriptor.hasUnforgeableMembers: raise TypeError("Can't handle a named setter on an interface that has " "unforgeables. Figure out how that should work!") - set += ("if RUST_JSID_IS_STRING(id) {\n" + - CGIndenter(CGProxyNamedSetter(self.descriptor)).define() + - " return (*opresult).succeed();\n" + - "} else {\n" + - " return false;\n" + - "}\n") - else: - set += ("if RUST_JSID_IS_STRING(id) {\n" + - CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + - " if result.is_some() {\n" - " return (*opresult).failNoNamedSetter();\n" - " }\n" - "}\n") - set += "return proxyhandler::define_property(%s);" % ", ".join(a.name for a in self.args) + set += ("if RUST_JSID_IS_STRING(id) || RUST_JSID_IS_INT(id) {\n" + + CGIndenter(CGProxyNamedSetter(self.descriptor)).define() + + " return (*opresult).succeed();\n" + + "}\n") + elif self.descriptor.operations['NamedGetter']: + set += ("if RUST_JSID_IS_STRING(id) || RUST_JSID_IS_INT(id) {\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + + " if result.is_some() {\n" + " return (*opresult).failNoNamedSetter();\n" + " }\n" + "}\n") + set += "return proxyhandler::define_property(*cx, %s);" % ", ".join(a.name for a in self.args[1:]) return set def definition_body(self): @@ -4932,20 +5486,20 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): class CGDOMJSProxyHandler_delete(CGAbstractExternMethod): def __init__(self, descriptor): - args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', 'proxy'), - Argument('HandleId', 'id'), + args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', 'proxy'), + Argument('RawHandleId', 'id'), Argument('*mut ObjectOpResult', 'res')] CGAbstractExternMethod.__init__(self, descriptor, "delete", "bool", args) self.descriptor = descriptor def getBody(self): - set = "" + set = "let cx = SafeJSContext::from_ptr(cx);\n" if self.descriptor.operations['NamedDeleter']: if self.descriptor.hasUnforgeableMembers: raise TypeError("Can't handle a deleter on an interface that has " "unforgeables. Figure out how that should work!") set += CGProxyNamedDeleter(self.descriptor).define() - set += "return proxyhandler::delete(%s);" % ", ".join(a.name for a in self.args) + set += "return proxyhandler::delete(*cx, %s);" % ", ".join(a.name for a in self.args[1:]) return set def definition_body(self): @@ -4955,14 +5509,15 @@ class CGDOMJSProxyHandler_delete(CGAbstractExternMethod): class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod): def __init__(self, descriptor): args = [Argument('*mut JSContext', 'cx'), - Argument('HandleObject', 'proxy'), - Argument('*mut AutoIdVector', 'props')] + Argument('RawHandleObject', 'proxy'), + Argument('RawMutableHandleIdVector', 'props')] CGAbstractExternMethod.__init__(self, descriptor, "own_property_keys", "bool", args) self.descriptor = descriptor def getBody(self): body = dedent( """ + let cx = SafeJSContext::from_ptr(cx); let unwrapped_proxy = UnwrapProxy(proxy); """) @@ -4970,8 +5525,9 @@ class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod): body += dedent( """ for i in 0..(*unwrapped_proxy).Length() { - rooted!(in(cx) let rooted_jsid = int_to_jsid(i as i32)); - AppendToAutoIdVector(props, rooted_jsid.handle().get()); + rooted!(in(*cx) let mut rooted_jsid: jsid); + int_to_jsid(i as i32, rooted_jsid.handle_mut()); + AppendToIdVector(props, rooted_jsid.handle()); } """) @@ -4980,20 +5536,21 @@ class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod): """ for name in (*unwrapped_proxy).SupportedPropertyNames() { let cstring = CString::new(name).unwrap(); - let jsstring = JS_AtomizeAndPinString(cx, cstring.as_ptr()); - rooted!(in(cx) let rooted = jsstring); - let jsid = INTERNED_STRING_TO_JSID(cx, rooted.handle().get()); - rooted!(in(cx) let rooted_jsid = jsid); - AppendToAutoIdVector(props, rooted_jsid.handle().get()); + let jsstring = JS_AtomizeAndPinString(*cx, cstring.as_ptr()); + rooted!(in(*cx) let rooted = jsstring); + rooted!(in(*cx) let mut rooted_jsid: jsid); + RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), rooted_jsid.handle_mut()); + AppendToIdVector(props, rooted_jsid.handle()); } """) body += dedent( """ - rooted!(in(cx) let mut expando = ptr::null_mut()); + rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>()); get_expando_object(proxy, expando.handle_mut()); - if !expando.is_null() { - GetPropertyKeys(cx, expando.handle(), JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props); + if !expando.is_null() && + !GetPropertyKeys(*cx, expando.handle(), JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props) { + return false; } return true; @@ -5007,11 +5564,11 @@ class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod): class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod): def __init__(self, descriptor): - assert (descriptor.operations["IndexedGetter"] and - descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties")) + assert (descriptor.operations["IndexedGetter"] + and descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties")) args = [Argument('*mut JSContext', 'cx'), - Argument('HandleObject', 'proxy'), - Argument('*mut AutoIdVector', 'props')] + Argument('RawHandleObject', 'proxy'), + Argument('RawMutableHandleIdVector', 'props')] CGAbstractExternMethod.__init__(self, descriptor, "getOwnEnumerablePropertyKeys", "bool", args) self.descriptor = descriptor @@ -5019,6 +5576,7 @@ class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod): def getBody(self): body = dedent( """ + let cx = SafeJSContext::from_ptr(cx); let unwrapped_proxy = UnwrapProxy(proxy); """) @@ -5026,17 +5584,19 @@ class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod): body += dedent( """ for i in 0..(*unwrapped_proxy).Length() { - rooted!(in(cx) let rooted_jsid = int_to_jsid(i as i32)); - AppendToAutoIdVector(props, rooted_jsid.handle().get()); + rooted!(in(*cx) let mut rooted_jsid: jsid); + int_to_jsid(i as i32, rooted_jsid.handle_mut()); + AppendToIdVector(props, rooted_jsid.handle()); } """) body += dedent( """ - rooted!(in(cx) let mut expando = ptr::null_mut()); + rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>()); get_expando_object(proxy, expando.handle_mut()); - if !expando.is_null() { - GetPropertyKeys(cx, expando.handle(), JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props); + if !expando.is_null() && + !GetPropertyKeys(*cx, expando.handle(), JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props) { + return false; } return true; @@ -5050,31 +5610,33 @@ class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod): class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): def __init__(self, descriptor): - args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', 'proxy'), - Argument('HandleId', 'id'), Argument('*mut bool', 'bp')] + args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', 'proxy'), + Argument('RawHandleId', 'id'), Argument('*mut bool', 'bp')] CGAbstractExternMethod.__init__(self, descriptor, "hasOwn", "bool", args) self.descriptor = descriptor def getBody(self): indexedGetter = self.descriptor.operations['IndexedGetter'] + indexed = "let cx = SafeJSContext::from_ptr(cx);\n" if indexedGetter: - indexed = ("let index = get_array_index_from_id(cx, id);\n" + - "if let Some(index) = index {\n" + - " let this = UnwrapProxy(proxy);\n" + - " let this = &*this;\n" + - CGIndenter(CGProxyIndexedGetter(self.descriptor)).define() + "\n" + - " *bp = result.is_some();\n" + - " return true;\n" + - "}\n\n") - else: - indexed = "" + indexed += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" + + "if let Some(index) = index {\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = &*this;\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor)).define() + "\n" + + " *bp = result.is_some();\n" + + " return true;\n" + + "}\n\n") namedGetter = self.descriptor.operations['NamedGetter'] + condition = "RUST_JSID_IS_STRING(id) || RUST_JSID_IS_INT(id)" + if indexedGetter: + condition = "index.is_none() && (%s)" % condition if namedGetter: named = """\ -if RUST_JSID_IS_STRING(id) { +if %s { let mut has_on_proto = false; - if !has_property_on_prototype(cx, proxy, id, &mut has_on_proto) { + if !has_property_on_prototype(*cx, proxy_lt, id_lt, &mut has_on_proto) { return false; } if !has_on_proto { @@ -5084,15 +5646,17 @@ if RUST_JSID_IS_STRING(id) { } } -""" % CGIndenter(CGProxyNamedGetter(self.descriptor), 8).define() +""" % (condition, CGIndenter(CGProxyNamedGetter(self.descriptor), 8).define()) else: named = "" return indexed + """\ -rooted!(in(cx) let mut expando = ptr::null_mut()); +rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>()); +let proxy_lt = Handle::from_raw(proxy); +let id_lt = Handle::from_raw(id); get_expando_object(proxy, expando.handle_mut()); if !expando.is_null() { - let ok = JS_HasPropertyById(cx, expando.handle(), id, bp); + let ok = JS_HasPropertyById(*cx, expando.handle().into(), id, bp); if !ok || *bp { return ok; } @@ -5107,39 +5671,40 @@ return true;""" class CGDOMJSProxyHandler_get(CGAbstractExternMethod): def __init__(self, descriptor): - args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', 'proxy'), - Argument('HandleValue', 'receiver'), Argument('HandleId', 'id'), - Argument('MutableHandleValue', 'vp')] + args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', 'proxy'), + Argument('RawHandleValue', 'receiver'), Argument('RawHandleId', 'id'), + Argument('RawMutableHandleValue', 'vp')] CGAbstractExternMethod.__init__(self, descriptor, "get", "bool", args) self.descriptor = descriptor + # https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty def getBody(self): getFromExpando = """\ -rooted!(in(cx) let mut expando = ptr::null_mut()); +rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>()); get_expando_object(proxy, expando.handle_mut()); if !expando.is_null() { let mut hasProp = false; - if !JS_HasPropertyById(cx, expando.handle(), id, &mut hasProp) { + if !JS_HasPropertyById(*cx, expando.handle().into(), id, &mut hasProp) { return false; } if hasProp { - return JS_ForwardGetPropertyTo(cx, expando.handle(), id, receiver, vp); + return JS_ForwardGetPropertyTo(*cx, expando.handle().into(), id, receiver, vp); } }""" templateValues = { - 'jsvalRef': 'vp', + 'jsvalRef': 'vp_lt', 'successCode': 'return true;', } indexedGetter = self.descriptor.operations['IndexedGetter'] if indexedGetter: - getIndexedOrExpando = ("let index = get_array_index_from_id(cx, id);\n" + - "if let Some(index) = index {\n" + - " let this = UnwrapProxy(proxy);\n" + - " let this = &*this;\n" + - CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()) + getIndexedOrExpando = ("let index = get_array_index_from_id(*cx, id_lt);\n" + + "if let Some(index) = index {\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = &*this;\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()) getIndexedOrExpando += """\ // Even if we don't have this index, we don't forward the // get on to our expando object. @@ -5152,19 +5717,31 @@ if !expando.is_null() { namedGetter = self.descriptor.operations['NamedGetter'] if namedGetter: - getNamed = ("if RUST_JSID_IS_STRING(id) {\n" + - CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + - "}\n") + condition = "RUST_JSID_IS_STRING(id) || RUST_JSID_IS_INT(id)" + # From step 1: + # If O supports indexed properties and P is an array index, then: + # + # 3. Set ignoreNamedProps to true. + if indexedGetter: + condition = "index.is_none() && (%s)" % condition + getNamed = ("if %s {\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + + "}\n") % condition else: getNamed = "" return """\ //MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), //"Should not have a XrayWrapper here"); +let cx = SafeJSContext::from_ptr(cx); +let proxy_lt = Handle::from_raw(proxy); +let vp_lt = MutableHandle::from_raw(vp); +let id_lt = Handle::from_raw(id); +let receiver_lt = Handle::from_raw(receiver); %s let mut found = false; -if !get_property_on_prototype(cx, proxy, receiver, id, &mut found, vp) { +if !get_property_on_prototype(*cx, proxy_lt, receiver_lt, id_lt, &mut found, vp_lt) { return false; } @@ -5181,7 +5758,7 @@ return true;""" % (getIndexedOrExpando, getNamed) class CGDOMJSProxyHandler_className(CGAbstractExternMethod): def __init__(self, descriptor): - args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', '_proxy')] + args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', '_proxy')] CGAbstractExternMethod.__init__(self, descriptor, "className", "*const i8", args, doesNotPanic=True) self.descriptor = descriptor @@ -5203,7 +5780,7 @@ class CGAbstractClassHook(CGAbstractExternMethod): def definition_body_prologue(self): return CGGeneric(""" -let this = native_from_object::<%s>(obj).unwrap(); +let this = native_from_object_static::<%s>(obj).unwrap(); """ % self.descriptor.concreteType) def definition_body(self): @@ -5224,7 +5801,9 @@ finalize_global(obj); """ elif descriptor.weakReferenceable: release += """\ -let weak_box_ptr = JS_GetReservedSlot(obj, DOM_WEAK_SLOT).to_private() as *mut WeakBox<%s>; +let mut slot = UndefinedValue(); +JS_GetReservedSlot(obj, DOM_WEAK_SLOT, &mut slot); +let weak_box_ptr = slot.to_private() as *mut WeakBox<%s>; if !weak_box_ptr.is_null() { let count = { let weak_box = &*weak_box_ptr; @@ -5285,16 +5864,30 @@ class CGClassConstructHook(CGAbstractExternMethod): self.exposureSet = descriptor.interface.exposureSet def definition_body(self): - preamble = """let global = GlobalScope::from_object(JS_CALLEE(cx, vp).to_object());\n""" + preamble = """let cx = SafeJSContext::from_ptr(cx); +let global = GlobalScope::from_object(JS_CALLEE(*cx, vp).to_object()); +""" if len(self.exposureSet) == 1: - preamble += "let global = Root::downcast::<dom::types::%s>(global).unwrap();\n" % list(self.exposureSet)[0] + preamble += """\ +let global = DomRoot::downcast::<dom::types::%s>(global).unwrap(); +""" % list(self.exposureSet)[0] preamble += """let args = CallArgs::from_vp(vp, argc);\n""" preamble = CGGeneric(preamble) - name = self.constructor.identifier.name - nativeName = MakeNativeName(self.descriptor.binaryNameFor(name)) - callGenerator = CGMethodCall(["&global"], nativeName, True, - self.descriptor, self.constructor) - return CGList([preamble, callGenerator]) + if self.constructor.isHTMLConstructor(): + signatures = self.constructor.signatures() + assert len(signatures) == 1 + constructorCall = CGGeneric("""dom::bindings::htmlconstructor::call_html_constructor::<dom::types::%s>( + cx, + &args, + &*global, + GetProtoObject, + )""" % self.descriptor.name) + else: + name = self.constructor.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNameFor(name)) + constructorCall = CGMethodCall(["&global"], nativeName, True, + self.descriptor, self.constructor) + return CGList([preamble, constructorCall]) class CGClassFinalizeHook(CGAbstractClassHook): @@ -5323,29 +5916,37 @@ class CGInterfaceTrait(CGThing): def __init__(self, descriptor): CGThing.__init__(self) - def attribute_arguments(needCx, argument=None): + def attribute_arguments(needCx, argument=None, inRealm=False): if needCx: - yield "cx", "*mut JSContext" + yield "cx", "SafeJSContext" if argument: yield "value", argument_type(descriptor, argument) + if inRealm: + yield "_comp", "InRealm" + def members(): for m in descriptor.interface.members: - if (m.isMethod() and not m.isStatic() and - not m.isMaplikeOrSetlikeOrIterableMethod() and - (not m.isIdentifierLess() or m.isStringifier())): + if (m.isMethod() and not m.isStatic() + and not m.isMaplikeOrSetlikeOrIterableMethod() + and (not m.isIdentifierLess() or (m.isStringifier() and not m.underlyingAttr)) + and not m.isDefaultToJSON()): name = CGSpecializedMethod.makeNativeName(descriptor, m) infallible = 'infallible' in descriptor.getExtendedAttributes(m) for idx, (rettype, arguments) in enumerate(m.signatures()): - arguments = method_arguments(descriptor, rettype, arguments) + arguments = method_arguments(descriptor, rettype, arguments, + inRealm=name in descriptor.inRealmMethods) rettype = return_type(descriptor, rettype, infallible) yield name + ('_' * idx), arguments, rettype elif m.isAttr() and not m.isStatic(): name = CGSpecializedGetter.makeNativeName(descriptor, m) infallible = 'infallible' in descriptor.getExtendedAttributes(m, getter=True) yield (name, - attribute_arguments(typeNeedsCx(m.type, True)), + attribute_arguments( + typeNeedsCx(m.type, True), + inRealm=name in descriptor.inRealmMethods + ), return_type(descriptor, m.type, infallible)) if not m.readonly: @@ -5355,10 +5956,16 @@ class CGInterfaceTrait(CGThing): rettype = "()" else: rettype = "ErrorResult" - yield name, attribute_arguments(typeNeedsCx(m.type, False), m.type), rettype + yield (name, + attribute_arguments( + typeNeedsCx(m.type, False), + m.type, + inRealm=name in descriptor.inRealmMethods + ), + rettype) if descriptor.proxy: - for name, operation in descriptor.operations.iteritems(): + for name, operation in descriptor.operations.items(): if not operation or operation.isStringifier(): continue @@ -5369,7 +5976,8 @@ class CGInterfaceTrait(CGThing): if operation.isGetter(): if not rettype.nullable(): rettype = IDLNullableType(rettype.location, rettype) - arguments = method_arguments(descriptor, rettype, arguments) + arguments = method_arguments(descriptor, rettype, arguments, + inRealm=name in descriptor.inRealmMethods) # If this interface 'supports named properties', then we # should be able to access 'supported property names' @@ -5379,17 +5987,22 @@ class CGInterfaceTrait(CGThing): if operation.isNamed(): yield "SupportedPropertyNames", [], "Vec<DOMString>" else: - arguments = method_arguments(descriptor, rettype, arguments) + arguments = method_arguments(descriptor, rettype, arguments, + inRealm=name in descriptor.inRealmMethods) rettype = return_type(descriptor, rettype, infallible) yield name, arguments, rettype def fmt(arguments): - return "".join(", %s: %s" % argument for argument in arguments) + keywords = {"async"} + return "".join( + ", %s: %s" % (name if name not in keywords else "r#" + name, type_) + for name, type_ in arguments + ) def contains_unsafe_arg(arguments): if not arguments or len(arguments) == 0: return False - return reduce((lambda x, y: x or y[1] == '*mut JSContext'), arguments, False) + return functools.reduce((lambda x, y: x or y[1] == '*mut JSContext'), arguments, False) methods = [] for name, arguments, rettype in members(): @@ -5421,16 +6034,17 @@ class CGWeakReferenceableTrait(CGThing): return self.code -def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries=None, enums=None): +def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries=None, enums=None, typedefs=None): if not callbacks: callbacks = [] if not dictionaries: dictionaries = [] if not enums: enums = [] + if not typedefs: + typedefs = [] - return CGImports(cgthings, descriptors, callbacks, dictionaries, enums, [ - 'core::nonzero::NonZero', + return CGImports(cgthings, descriptors, callbacks, dictionaries, enums, typedefs, [ 'js', 'js::JSCLASS_GLOBAL_SLOT_COUNT', 'js::JSCLASS_IS_DOMJSCLASS', @@ -5438,22 +6052,27 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'js::JSCLASS_RESERVED_SLOTS_MASK', 'js::JS_CALLEE', 'js::error::throw_type_error', - 'js::jsapi::AutoIdVector', - 'js::jsapi::Call', + 'js::error::throw_internal_error', + 'js::rust::wrappers::Call', + 'js::jsapi::__BindgenBitfieldUnit', 'js::jsapi::CallArgs', 'js::jsapi::CurrentGlobalOrNull', - 'js::jsapi::FreeOp', - 'js::jsapi::GetPropertyKeys', + 'js::rust::wrappers::GetPropertyKeys', 'js::jsapi::GetWellKnownSymbol', - 'js::jsapi::Handle', - 'js::jsapi::HandleId', - 'js::jsapi::HandleObject', - 'js::jsapi::HandleValue', + 'js::rust::Handle', + 'js::jsapi::Handle as RawHandle', + 'js::rust::HandleId', + 'js::jsapi::HandleId as RawHandleId', + 'js::rust::HandleObject', + 'js::jsapi::HandleObject as RawHandleObject', + 'js::rust::HandleValue', + 'js::jsapi::HandleValue as RawHandleValue', 'js::jsapi::HandleValueArray', 'js::jsapi::Heap', - 'js::jsapi::INTERNED_STRING_TO_JSID', + 'js::rust::wrappers::RUST_INTERNED_STRING_TO_JSID', 'js::jsapi::IsCallable', - 'js::jsapi::JSAutoCompartment', + 'js::jsapi::JSAutoRealm', + 'js::jsapi::JSCLASS_FOREGROUND_FINALIZE', 'js::jsapi::JSCLASS_RESERVED_SLOTS_SHIFT', 'js::jsapi::JSClass', 'js::jsapi::JSContext', @@ -5464,6 +6083,9 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'js::jsapi::JSITER_SYMBOLS', 'js::jsapi::JSJitGetterCallArgs', 'js::jsapi::JSJitInfo', + 'js::jsapi::JSJitInfo__bindgen_ty_1', + 'js::jsapi::JSJitInfo__bindgen_ty_2', + 'js::jsapi::JSJitInfo__bindgen_ty_3', 'js::jsapi::JSJitInfo_AliasSet', 'js::jsapi::JSJitInfo_ArgType', 'js::jsapi::JSJitInfo_OpType', @@ -5475,45 +6097,58 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'js::jsapi::JSPROP_ENUMERATE', 'js::jsapi::JSPROP_PERMANENT', 'js::jsapi::JSPROP_READONLY', - 'js::jsapi::JSPROP_SHARED', 'js::jsapi::JSPropertySpec', + 'js::jsapi::JSPropertySpec_Accessor', + 'js::jsapi::JSPropertySpec_AccessorsOrValue', + 'js::jsapi::JSPropertySpec_AccessorsOrValue_Accessors', + 'js::jsapi::JSPropertySpec_Name', + 'js::jsapi::JSPropertySpec_ValueWrapper', + 'js::jsapi::JSPropertySpec_ValueWrapper_Type', + 'js::jsapi::JSPropertySpec_ValueWrapper__bindgen_ty_1', 'js::jsapi::JSString', 'js::jsapi::JSTracer', 'js::jsapi::JSType', 'js::jsapi::JSTypedMethodJitInfo', 'js::jsapi::JSValueType', 'js::jsapi::JS_AtomizeAndPinString', - 'js::jsapi::JS_CallFunctionValue', - 'js::jsapi::JS_CopyPropertiesFrom', - 'js::jsapi::JS_DefineProperty', - 'js::jsapi::JS_DefinePropertyById2', + 'js::rust::wrappers::JS_CallFunctionValue', + 'js::rust::wrappers::JS_CopyOwnPropertiesAndPrivateFields', + 'js::rust::wrappers::JS_DefineProperty', + 'js::rust::wrappers::JS_DefinePropertyById2', 'js::jsapi::JS_ForwardGetPropertyTo', - 'js::jsapi::JS_GetErrorPrototype', - 'js::jsapi::JS_GetFunctionPrototype', - 'js::jsapi::JS_GetGlobalForObject', - 'js::jsapi::JS_GetIteratorPrototype', - 'js::jsapi::JS_GetObjectPrototype', - 'js::jsapi::JS_GetProperty', + 'js::jsapi::GetRealmErrorPrototype', + 'js::jsapi::GetRealmFunctionPrototype', + 'js::jsapi::GetRealmIteratorPrototype', + 'js::jsapi::GetRealmObjectPrototype', + 'js::rust::wrappers::JS_GetProperty', 'js::jsapi::JS_GetPropertyById', 'js::jsapi::JS_GetPropertyDescriptorById', - 'js::jsapi::JS_GetReservedSlot', + 'js::glue::JS_GetReservedSlot', 'js::jsapi::JS_HasProperty', 'js::jsapi::JS_HasPropertyById', - 'js::jsapi::JS_InitializePropertiesFromCompatibleNativeObject', + 'js::rust::wrappers::JS_InitializePropertiesFromCompatibleNativeObject', + 'js::jsapi::JS_NewPlainObject', 'js::jsapi::JS_NewObject', - 'js::jsapi::JS_NewObjectWithGivenProto', - 'js::jsapi::JS_NewObjectWithoutMetadata', - 'js::jsapi::JS_ObjectIsDate', - 'js::jsapi::JS_SetImmutablePrototype', - 'js::jsapi::JS_SetProperty', + 'js::rust::RootedGuard', + 'js::rust::wrappers::JS_NewObjectWithGivenProto', + 'js::rust::wrappers::JS_NewObjectWithoutMetadata', + 'js::rust::wrappers::ObjectIsDate', + 'js::rust::wrappers::JS_SetImmutablePrototype', + 'js::rust::wrappers::JS_SetProperty', + 'js::rust::wrappers::JS_SetPrototype', 'js::jsapi::JS_SetReservedSlot', - 'js::jsapi::JS_SplicePrototype', - 'js::jsapi::JS_WrapValue', - 'js::jsapi::MutableHandle', - 'js::jsapi::MutableHandleObject', - 'js::jsapi::MutableHandleValue', + 'js::rust::wrappers::JS_WrapValue', + 'js::rust::wrappers::JS_WrapObject', + 'js::rust::MutableHandle', + 'js::jsapi::MutableHandle as RawMutableHandle', + 'js::rust::MutableHandleObject', + 'js::jsapi::MutableHandleObject as RawMutableHandleObject', + 'js::rust::MutableHandleValue', + 'js::jsapi::MutableHandleValue as RawMutableHandleValue', + 'js::jsapi::MutableHandleIdVector as RawMutableHandleIdVector', 'js::jsapi::ObjectOpResult', 'js::jsapi::PropertyDescriptor', + 'js::jsapi::Rooted', 'js::jsapi::RootedId', 'js::jsapi::RootedObject', 'js::jsapi::RootedString', @@ -5525,126 +6160,143 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'js::jsval::ObjectOrNullValue', 'js::jsval::PrivateValue', 'js::jsval::UndefinedValue', - 'js::glue::AppendToAutoIdVector', + 'js::jsapi::UndefinedHandleValue', + 'js::rust::wrappers::AppendToIdVector', 'js::glue::CallJitGetterOp', 'js::glue::CallJitMethodOp', 'js::glue::CallJitSetterOp', 'js::glue::CreateProxyHandler', - 'js::glue::GetProxyPrivate', - 'js::glue::NewProxyObject', + 'js::glue::GetProxyReservedSlot', + 'js::glue::SetProxyReservedSlot', + 'js::rust::wrappers::NewProxyObject', 'js::glue::ProxyTraps', + 'js::glue::RUST_JSID_IS_INT', 'js::glue::RUST_JSID_IS_STRING', - 'js::glue::RUST_SYMBOL_TO_JSID', - 'js::glue::int_to_jsid', + 'js::rust::wrappers::RUST_SYMBOL_TO_JSID', + 'js::rust::wrappers::int_to_jsid', + 'js::glue::UnwrapObjectDynamic', 'js::panic::maybe_resume_unwind', 'js::panic::wrap_panic', 'js::rust::GCMethods', + 'js::rust::CustomAutoRooterGuard', 'js::rust::define_methods', 'js::rust::define_properties', 'js::rust::get_object_class', - 'dom', - 'dom::bindings', - 'dom::bindings::codegen::InterfaceObjectMap', - 'dom::bindings::constant::ConstantSpec', - 'dom::bindings::constant::ConstantVal', - 'dom::bindings::interface::ConstructorClassHook', - 'dom::bindings::interface::InterfaceConstructorBehavior', - 'dom::bindings::interface::NonCallbackInterfaceObjectClass', - 'dom::bindings::interface::create_callback_interface_object', - 'dom::bindings::interface::create_global_object', - 'dom::bindings::interface::create_interface_prototype_object', - 'dom::bindings::interface::create_named_constructors', - 'dom::bindings::interface::create_noncallback_interface_object', - 'dom::bindings::interface::define_guarded_constants', - 'dom::bindings::interface::define_guarded_methods', - 'dom::bindings::interface::define_guarded_properties', - 'dom::bindings::interface::is_exposed_in', - 'dom::bindings::iterable::Iterable', - 'dom::bindings::iterable::IteratorType', - 'dom::bindings::js::JS', - 'dom::bindings::js::Root', - 'dom::bindings::js::RootedReference', - 'dom::bindings::namespace::NamespaceObjectClass', - 'dom::bindings::namespace::create_namespace_object', - 'dom::bindings::reflector::MutDomObject', - 'dom::bindings::reflector::DomObject', - 'dom::bindings::utils::AsVoidPtr', - 'dom::bindings::utils::DOMClass', - 'dom::bindings::utils::DOMJSClass', - 'dom::bindings::utils::DOM_PROTO_UNFORGEABLE_HOLDER_SLOT', - 'dom::bindings::utils::JSCLASS_DOM_GLOBAL', - 'dom::bindings::utils::ProtoOrIfaceArray', - 'dom::bindings::utils::enumerate_global', - 'dom::bindings::utils::finalize_global', - 'dom::bindings::utils::find_enum_value', - 'dom::bindings::utils::generic_getter', - 'dom::bindings::utils::generic_lenient_getter', - 'dom::bindings::utils::generic_lenient_setter', - 'dom::bindings::utils::generic_method', - 'dom::bindings::utils::generic_setter', - 'dom::bindings::utils::get_array_index_from_id', - 'dom::bindings::utils::get_dictionary_property', - 'dom::bindings::utils::get_property_on_prototype', - 'dom::bindings::utils::get_proto_or_iface_array', - 'dom::bindings::utils::has_property_on_prototype', - 'dom::bindings::utils::is_platform_object', - 'dom::bindings::utils::resolve_global', - 'dom::bindings::utils::set_dictionary_property', - 'dom::bindings::utils::trace_global', - 'dom::bindings::trace::JSTraceable', - 'dom::bindings::trace::RootedTraceable', - 'dom::bindings::trace::RootedTraceableBox', - 'dom::bindings::callback::CallSetup', - 'dom::bindings::callback::CallbackContainer', - 'dom::bindings::callback::CallbackInterface', - 'dom::bindings::callback::CallbackFunction', - 'dom::bindings::callback::CallbackObject', - 'dom::bindings::callback::ExceptionHandling', - 'dom::bindings::callback::wrap_call_this_object', - 'dom::bindings::conversions::ConversionBehavior', - 'dom::bindings::conversions::ConversionResult', - 'dom::bindings::conversions::DOM_OBJECT_SLOT', - 'dom::bindings::conversions::FromJSValConvertible', - 'dom::bindings::conversions::IDLInterface', - 'dom::bindings::conversions::StringificationBehavior', - 'dom::bindings::conversions::ToJSValConvertible', - 'dom::bindings::conversions::is_array_like', - 'dom::bindings::conversions::native_from_handlevalue', - 'dom::bindings::conversions::native_from_object', - 'dom::bindings::conversions::private_from_object', - 'dom::bindings::conversions::root_from_handleobject', - 'dom::bindings::conversions::root_from_handlevalue', - 'dom::bindings::conversions::root_from_object', - 'dom::bindings::conversions::string_jsid_to_string', - 'dom::bindings::codegen::PrototypeList', - 'dom::bindings::codegen::RegisterBindings', - 'dom::bindings::codegen::UnionTypes', - 'dom::bindings::error::Error', - 'dom::bindings::error::ErrorResult', - 'dom::bindings::error::Fallible', - 'dom::bindings::error::Error::JSFailed', - 'dom::bindings::error::throw_dom_exception', - 'dom::bindings::guard::Condition', - 'dom::bindings::guard::Guard', - 'dom::bindings::inheritance::Castable', - 'dom::bindings::proxyhandler', - 'dom::bindings::proxyhandler::ensure_expando_object', - 'dom::bindings::proxyhandler::fill_property_descriptor', - 'dom::bindings::proxyhandler::get_expando_object', - 'dom::bindings::proxyhandler::get_property_descriptor', - 'dom::bindings::mozmap::MozMap', - 'dom::bindings::num::Finite', - 'dom::bindings::str::ByteString', - 'dom::bindings::str::DOMString', - 'dom::bindings::str::USVString', - 'dom::bindings::weakref::DOM_WEAK_SLOT', - 'dom::bindings::weakref::WeakBox', - 'dom::bindings::weakref::WeakReferenceable', - 'dom::browsingcontext::BrowsingContext', - 'dom::globalscope::GlobalScope', - 'mem::heap_size_of_raw_self_and_children', + 'js::typedarray', + 'crate::dom', + 'crate::dom::bindings', + 'crate::dom::bindings::codegen::InterfaceObjectMap', + 'crate::dom::bindings::constant::ConstantSpec', + 'crate::dom::bindings::constant::ConstantVal', + 'crate::dom::bindings::interface::ConstructorClassHook', + 'crate::dom::bindings::interface::InterfaceConstructorBehavior', + 'crate::dom::bindings::interface::NonCallbackInterfaceObjectClass', + 'crate::dom::bindings::interface::create_global_object', + 'crate::dom::bindings::interface::create_callback_interface_object', + 'crate::dom::bindings::interface::create_interface_prototype_object', + 'crate::dom::bindings::interface::create_named_constructors', + 'crate::dom::bindings::interface::create_noncallback_interface_object', + 'crate::dom::bindings::interface::define_guarded_constants', + 'crate::dom::bindings::interface::define_guarded_methods', + 'crate::dom::bindings::interface::define_guarded_properties', + 'crate::dom::bindings::interface::is_exposed_in', + 'crate::dom::bindings::htmlconstructor::pop_current_element_queue', + 'crate::dom::bindings::htmlconstructor::push_new_element_queue', + 'crate::dom::bindings::iterable::Iterable', + 'crate::dom::bindings::iterable::IteratorType', + 'crate::dom::bindings::namespace::NamespaceObjectClass', + 'crate::dom::bindings::namespace::create_namespace_object', + 'crate::dom::bindings::reflector::MutDomObject', + 'crate::dom::bindings::reflector::DomObject', + 'crate::dom::bindings::reflector::DomObjectWrap', + 'crate::dom::bindings::reflector::DomObjectIteratorWrap', + 'crate::dom::bindings::root::Dom', + 'crate::dom::bindings::root::DomRoot', + 'crate::dom::bindings::root::DomSlice', + 'crate::dom::bindings::root::MaybeUnreflectedDom', + 'crate::dom::bindings::root::OptionalHeapSetter', + 'crate::dom::bindings::root::Root', + 'crate::dom::bindings::utils::AsVoidPtr', + 'crate::dom::bindings::utils::DOMClass', + 'crate::dom::bindings::utils::DOMJSClass', + 'crate::dom::bindings::utils::DOM_PROTO_UNFORGEABLE_HOLDER_SLOT', + 'crate::dom::bindings::utils::JSCLASS_DOM_GLOBAL', + 'crate::dom::bindings::utils::ProtoOrIfaceArray', + 'crate::dom::bindings::utils::enumerate_global', + 'crate::dom::bindings::utils::finalize_global', + 'crate::dom::bindings::utils::generic_getter', + 'crate::dom::bindings::utils::generic_lenient_getter', + 'crate::dom::bindings::utils::generic_lenient_setter', + 'crate::dom::bindings::utils::generic_method', + 'crate::dom::bindings::utils::generic_setter', + 'crate::dom::bindings::utils::get_array_index_from_id', + 'crate::dom::bindings::utils::get_dictionary_property', + 'crate::dom::bindings::utils::get_property_on_prototype', + 'crate::dom::bindings::utils::get_proto_or_iface_array', + 'crate::dom::bindings::utils::has_property_on_prototype', + 'crate::dom::bindings::utils::is_platform_object_dynamic', + 'crate::dom::bindings::utils::is_platform_object_static', + 'crate::dom::bindings::utils::resolve_global', + 'crate::dom::bindings::utils::set_dictionary_property', + 'crate::dom::bindings::utils::trace_global', + 'crate::dom::bindings::trace::JSTraceable', + 'crate::dom::bindings::trace::RootedTraceableBox', + 'crate::dom::bindings::callback::CallSetup', + 'crate::dom::bindings::callback::CallbackContainer', + 'crate::dom::bindings::callback::CallbackInterface', + 'crate::dom::bindings::callback::CallbackFunction', + 'crate::dom::bindings::callback::CallbackObject', + 'crate::dom::bindings::callback::ExceptionHandling', + 'crate::dom::bindings::callback::wrap_call_this_object', + 'crate::dom::bindings::conversions::ConversionBehavior', + 'crate::dom::bindings::conversions::ConversionResult', + 'crate::dom::bindings::conversions::DOM_OBJECT_SLOT', + 'crate::dom::bindings::conversions::FromJSValConvertible', + 'crate::dom::bindings::conversions::IDLInterface', + 'crate::dom::bindings::conversions::StringificationBehavior', + 'crate::dom::bindings::conversions::ToJSValConvertible', + 'crate::dom::bindings::conversions::is_array_like', + 'crate::dom::bindings::conversions::native_from_handlevalue', + 'crate::dom::bindings::conversions::native_from_object', + 'crate::dom::bindings::conversions::native_from_object_static', + 'crate::dom::bindings::conversions::private_from_object', + 'crate::dom::bindings::conversions::root_from_handleobject', + 'crate::dom::bindings::conversions::root_from_handlevalue', + 'crate::dom::bindings::conversions::root_from_object', + 'crate::dom::bindings::conversions::jsid_to_string', + 'crate::dom::bindings::codegen::PrototypeList', + 'crate::dom::bindings::codegen::RegisterBindings', + 'crate::dom::bindings::codegen::UnionTypes', + 'crate::dom::bindings::error::Error', + 'crate::dom::bindings::error::ErrorResult', + 'crate::dom::bindings::error::Fallible', + 'crate::dom::bindings::error::Error::JSFailed', + 'crate::dom::bindings::error::throw_dom_exception', + 'crate::dom::bindings::guard::Condition', + 'crate::dom::bindings::guard::Guard', + 'crate::dom::bindings::inheritance::Castable', + 'crate::dom::bindings::proxyhandler', + 'crate::dom::bindings::proxyhandler::ensure_expando_object', + 'crate::dom::bindings::proxyhandler::fill_property_descriptor', + 'crate::dom::bindings::proxyhandler::get_expando_object', + 'crate::dom::bindings::record::Record', + 'std::ptr::NonNull', + 'crate::dom::bindings::num::Finite', + 'crate::dom::bindings::str::ByteString', + 'crate::dom::bindings::str::DOMString', + 'crate::dom::bindings::str::USVString', + 'crate::dom::bindings::weakref::DOM_WEAK_SLOT', + 'crate::dom::bindings::weakref::WeakBox', + 'crate::dom::bindings::weakref::WeakReferenceable', + 'crate::dom::windowproxy::WindowProxy', + 'crate::dom::globalscope::GlobalScope', + 'crate::mem::malloc_size_of_including_raw_self', + 'crate::realms::InRealm', + 'crate::realms::AlreadyInRealm', + 'crate::script_runtime::JSContext as SafeJSContext', 'libc', - 'servo_config::prefs::PREFS', + 'servo_config::pref', + 'servo_config::prefs', 'std::borrow::ToOwned', 'std::cmp', 'std::mem', @@ -5657,6 +6309,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'std::rc::Rc', 'std::default::Default', 'std::ffi::CString', + 'std::ops::Deref', ], config) @@ -5677,24 +6330,23 @@ class CGDescriptor(CGThing): cgThings = [] + defaultToJSONMethod = None unscopableNames = [] for m in descriptor.interface.members: - if (m.isMethod() and - (not m.isIdentifierLess() or m == descriptor.operations["Stringifier"])): + if (m.isMethod() + and (not m.isIdentifierLess() or m == descriptor.operations["Stringifier"])): if m.getExtendedAttribute("Unscopable"): assert not m.isStatic() unscopableNames.append(m.identifier.name) - if m.isStatic(): + if m.isDefaultToJSON(): + defaultToJSONMethod = m + elif m.isStatic(): assert descriptor.interface.hasInterfaceObject() cgThings.append(CGStaticMethod(descriptor, m)) elif not descriptor.interface.isCallback(): cgThings.append(CGSpecializedMethod(descriptor, m)) cgThings.append(CGMemberJITInfo(descriptor, m)) elif m.isAttr(): - if m.stringifier: - raise TypeError("Stringifier attributes not supported yet. " - "See https://github.com/servo/servo/issues/7590\n" - "%s" % m.location) if m.getExtendedAttribute("Unscopable"): assert not m.isStatic() unscopableNames.append(m.identifier.name) @@ -5718,6 +6370,10 @@ class CGDescriptor(CGThing): if (not m.isStatic() and not descriptor.interface.isCallback()): cgThings.append(CGMemberJITInfo(descriptor, m)) + if defaultToJSONMethod: + cgThings.append(CGDefaultToJSONMethod(descriptor, defaultToJSONMethod)) + cgThings.append(CGMemberJITInfo(descriptor, defaultToJSONMethod)) + if descriptor.concrete: cgThings.append(CGClassFinalizeHook(descriptor)) cgThings.append(CGClassTraceHook(descriptor)) @@ -5735,6 +6391,9 @@ class CGDescriptor(CGThing): properties = PropertyArrays(descriptor) + if defaultToJSONMethod: + cgThings.append(CGCollectJSONAttributesMethod(descriptor, defaultToJSONMethod)) + if descriptor.concrete: if descriptor.proxy: # cgThings.append(CGProxyIsProxy(descriptor)) @@ -5762,12 +6421,18 @@ class CGDescriptor(CGThing): pass else: cgThings.append(CGDOMJSClass(descriptor)) + if not descriptor.interface.isIteratorInterface(): + cgThings.append(CGAssertInheritance(descriptor)) pass if descriptor.isGlobal(): cgThings.append(CGWrapGlobalMethod(descriptor, properties)) else: cgThings.append(CGWrapMethod(descriptor)) + if descriptor.interface.isIteratorInterface(): + cgThings.append(CGDomObjectIteratorWrap(descriptor)) + else: + cgThings.append(CGDomObjectWrap(descriptor)) reexports.append('Wrap') haveUnscopables = False @@ -5790,6 +6455,15 @@ class CGDescriptor(CGThing): if descriptor.weakReferenceable: cgThings.append(CGWeakReferenceableTrait(descriptor)) + legacyWindowAliases = descriptor.interface.legacyWindowAliases + haveLegacyWindowAliases = len(legacyWindowAliases) != 0 + if haveLegacyWindowAliases: + cgThings.append( + CGList([CGGeneric("const legacy_window_aliases: &'static [&'static [u8]] = &["), + CGIndenter(CGList([CGGeneric(str_to_const_array(name)) for + name in legacyWindowAliases], ",\n")), + CGGeneric("];\n")], "\n")) + cgThings.append(CGGeneric(str(properties))) if not descriptor.interface.getExtendedAttribute("Inline"): @@ -5811,7 +6485,8 @@ class CGDescriptor(CGThing): cgThings.append(CGDefineDOMInterfaceMethod(descriptor)) reexports.append('DefineDOMInterface') cgThings.append(CGConstructorEnabled(descriptor)) - cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties, haveUnscopables)) + cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties, haveUnscopables, + haveLegacyWindowAliases)) cgThings = generate_imports(config, CGList(cgThings, '\n'), [descriptor]) cgThings = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), @@ -5819,7 +6494,7 @@ class CGDescriptor(CGThing): post='\n') if reexports: - reexports = ', '.join(map(lambda name: reexportedName(name), reexports)) + reexports = ', '.join([reexportedName(name) for name in reexports]) cgThings = CGList([CGGeneric('pub use self::%s::{%s};' % (toBindingNamespace(descriptor.name), reexports)), cgThings], '\n') @@ -5879,7 +6554,7 @@ class CGDictionary(CGThing): descriptorProvider, isMember="Dictionary", defaultValue=member.defaultValue, - exceptionCode="return Err(());")) + exceptionCode="return Err(());\n")) for member in dictionary.members] def define(self): @@ -5890,43 +6565,58 @@ class CGDictionary(CGThing): def struct(self): d = self.dictionary if d.parent: - inheritance = " pub parent: %s::%s,\n" % (self.makeModuleName(d.parent), - self.makeClassName(d.parent)) + typeName = "%s::%s" % (self.makeModuleName(d.parent), + self.makeClassName(d.parent)) + if type_needs_tracing(d.parent): + typeName = "RootedTraceableBox<%s>" % typeName + inheritance = " pub parent: %s,\n" % typeName else: inheritance = "" memberDecls = [" pub %s: %s," % (self.makeMemberName(m[0].identifier.name), self.getMemberType(m)) for m in self.memberInfo] + derive = ["JSTraceable"] + mustRoot = "" + if self.membersNeedTracing(): + mustRoot = "#[unrooted_must_root_lint::must_root]\n" + if not self.hasRequiredFields(self.dictionary): + derive += ["Default"] + return (string.Template( - "#[derive(JSTraceable)]\n" - "pub struct ${selfName} {\n" + - "${inheritance}" + - "\n".join(memberDecls) + "\n" + - "}").substitute({"selfName": self.makeClassName(d), - "inheritance": inheritance})) + "#[derive(${derive})]\n" + + "${mustRoot}" + + "pub struct ${selfName} {\n" + + "${inheritance}" + + "\n".join(memberDecls) + "\n" + + "}").substitute({"selfName": self.makeClassName(d), + "inheritance": inheritance, + "mustRoot": mustRoot, + "derive": ', '.join(derive)})) def impl(self): d = self.dictionary if d.parent: - initParent = ("parent: {\n" - " match try!(%s::%s::new(cx, val)) {\n" + initParent = ("{\n" + " match %s::%s::new(cx, val)? {\n" " ConversionResult::Success(v) => v,\n" " ConversionResult::Failure(error) => {\n" - " throw_type_error(cx, &error);\n" + " throw_type_error(*cx, &error);\n" " return Err(());\n" " }\n" " }\n" - "},\n" % (self.makeModuleName(d.parent), - self.makeClassName(d.parent))) + "}" % (self.makeModuleName(d.parent), + self.makeClassName(d.parent))) else: initParent = "" - def memberInit(memberInfo): + def memberInit(memberInfo, isInitial): member, _ = memberInfo name = self.makeMemberName(member.identifier.name) conversion = self.getMemberConversion(memberInfo, member.type) - return CGGeneric("%s: %s,\n" % (name, conversion.define())) + if isInitial: + return CGGeneric("%s: %s,\n" % (name, conversion.define())) + return CGGeneric("dictionary.%s = %s;\n" % (name, conversion.define())) def varInsert(varName, dictionaryName): insertion = ("rooted!(in(cx) let mut %s_js = UndefinedValue());\n" @@ -5946,59 +6636,86 @@ class CGDictionary(CGThing): (name, name, varInsert(name, member.identifier.name).define())) return CGGeneric("%s\n" % insertion.define()) - memberInits = CGList([memberInit(m) for m in self.memberInfo]) - memberInserts = CGList([memberInsert(m) for m in self.memberInfo]) + memberInserts = [memberInsert(m) for m in self.memberInfo] + + if d.parent: + memberInserts = [CGGeneric("self.parent.to_jsobject(cx, obj);\n")] + memberInserts + + selfName = self.makeClassName(d) + if self.membersNeedTracing(): + actualType = "RootedTraceableBox<%s>" % selfName + preInitial = "let dictionary = RootedTraceableBox::new(%s {\n" % selfName + postInitial = "});\n" + else: + actualType = selfName + preInitial = "let dictionary = %s {\n" % selfName + postInitial = "};\n" + initParent = ("parent: %s,\n" % initParent) if initParent else "" + memberInits = CGList([memberInit(m, True) for m in self.memberInfo]) return string.Template( "impl ${selfName} {\n" - " pub unsafe fn empty(cx: *mut JSContext) -> ${selfName} {\n" - " match ${selfName}::new(cx, HandleValue::null()) {\n" - " Ok(ConversionResult::Success(v)) => v,\n" - " _ => unreachable!(),\n" - " }\n" - " }\n" - " pub unsafe fn new(cx: *mut JSContext, val: HandleValue) \n" - " -> Result<ConversionResult<${selfName}>, ()> {\n" - " let object = if val.get().is_null_or_undefined() {\n" - " ptr::null_mut()\n" - " } else if val.get().is_object() {\n" - " val.get().to_object()\n" - " } else {\n" - " throw_type_error(cx, \"Value not an object.\");\n" - " return Err(());\n" - " };\n" - " rooted!(in(cx) let object = object);\n" - " Ok(ConversionResult::Success(${selfName} {\n" + "${empty}\n" + " pub fn new(cx: SafeJSContext, val: HandleValue) \n" + " -> Result<ConversionResult<${actualType}>, ()> {\n" + " unsafe {\n" + " let object = if val.get().is_null_or_undefined() {\n" + " ptr::null_mut()\n" + " } else if val.get().is_object() {\n" + " val.get().to_object()\n" + " } else {\n" + " return Ok(ConversionResult::Failure(\"Value is not an object.\".into()));\n" + " };\n" + " rooted!(in(*cx) let object = object);\n" + "${preInitial}" "${initParent}" "${initMembers}" - " }))\n" + "${postInitial}" + " Ok(ConversionResult::Success(dictionary))\n" + " }\n" " }\n" "}\n" "\n" - "impl FromJSValConvertible for ${selfName} {\n" + "impl FromJSValConvertible for ${actualType} {\n" " type Config = ();\n" " unsafe fn from_jsval(cx: *mut JSContext, value: HandleValue, _option: ())\n" - " -> Result<ConversionResult<${selfName}>, ()> {\n" - " ${selfName}::new(cx, value)\n" + " -> Result<ConversionResult<${actualType}>, ()> {\n" + " ${selfName}::new(SafeJSContext::from_ptr(cx), value)\n" " }\n" "}\n" "\n" - "impl ToJSValConvertible for ${selfName} {\n" - " unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {\n" - " rooted!(in(cx) let obj = JS_NewObject(cx, ptr::null()));\n" + "impl ${selfName} {\n" + " pub(crate) unsafe fn to_jsobject(&self, cx: *mut JSContext, mut obj: MutableHandleObject) {\n" "${insertMembers}" + " }\n" + "}\n" + "\n" + "impl ToJSValConvertible for ${selfName} {\n" + " unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {\n" + " rooted!(in(cx) let mut obj = JS_NewObject(cx, ptr::null()));\n" + " self.to_jsobject(cx, obj.handle_mut());\n" " rval.set(ObjectOrNullValue(obj.get()))\n" " }\n" "}\n").substitute({ - "selfName": self.makeClassName(d), - "initParent": CGIndenter(CGGeneric(initParent), indentLevel=12).define(), - "initMembers": CGIndenter(memberInits, indentLevel=12).define(), - "insertMembers": CGIndenter(memberInserts, indentLevel=8).define(), + "selfName": selfName, + "actualType": actualType, + "empty": CGIndenter(CGGeneric(self.makeEmpty()), indentLevel=4).define(), + "initParent": CGIndenter(CGGeneric(initParent), indentLevel=16).define(), + "initMembers": CGIndenter(memberInits, indentLevel=16).define(), + "insertMembers": CGIndenter(CGList(memberInserts), indentLevel=8).define(), + "preInitial": CGIndenter(CGGeneric(preInitial), indentLevel=8).define(), + "postInitial": CGIndenter(CGGeneric(postInitial), indentLevel=8).define(), }) + def membersNeedTracing(self): + return type_needs_tracing(self.dictionary) + @staticmethod def makeDictionaryName(dictionary): - return dictionary.identifier.name + if isinstance(dictionary, IDLWrapperType): + return CGDictionary.makeDictionaryName(dictionary.inner) + else: + return dictionary.identifier.name def makeClassName(self, dictionary): return self.makeDictionaryName(dictionary) @@ -6027,7 +6744,7 @@ class CGDictionary(CGThing): assert (member.defaultValue is None) == (default is None) if not member.optional: assert default is None - default = ("throw_type_error(cx, \"Missing required member \\\"%s\\\".\");\n" + default = ("throw_type_error(*cx, \"Missing required member \\\"%s\\\".\");\n" "return Err(());") % member.identifier.name elif not default: default = "None" @@ -6035,19 +6752,61 @@ class CGDictionary(CGThing): conversion = ( "{\n" - " rooted!(in(cx) let mut rval = UndefinedValue());\n" - " match try!(get_dictionary_property(cx, object.handle(), \"%s\", rval.handle_mut())) {\n" - " true => {\n" + " rooted!(in(*cx) let mut rval = UndefinedValue());\n" + " if get_dictionary_property(*cx, object.handle(), \"%s\", rval.handle_mut())?" + " && !rval.is_undefined() {\n" "%s\n" - " },\n" - " false => {\n" + " } else {\n" "%s\n" - " },\n" " }\n" "}") % (member.identifier.name, indent(conversion), indent(default)) return CGGeneric(conversion) + def makeEmpty(self): + if self.hasRequiredFields(self.dictionary): + return "" + parentTemplate = "parent: %s::%s::empty(),\n" + fieldTemplate = "%s: %s,\n" + functionTemplate = ( + "pub fn empty() -> Self {\n" + " Self {\n" + "%s" + " }\n" + "}" + ) + if self.membersNeedTracing(): + parentTemplate = "dictionary.parent = %s::%s::empty();\n" + fieldTemplate = "dictionary.%s = %s;\n" + functionTemplate = ( + "pub fn empty() -> RootedTraceableBox<Self> {\n" + " let mut dictionary = RootedTraceableBox::new(Self::default());\n" + "%s" + " dictionary\n" + "}" + ) + s = "" + if self.dictionary.parent: + s += parentTemplate % (self.makeModuleName(self.dictionary.parent), + self.makeClassName(self.dictionary.parent)) + for member, info in self.memberInfo: + if not member.optional: + return "" + default = info.default + if not default: + default = "None" + s += fieldTemplate % (self.makeMemberName(member.identifier.name), default) + return functionTemplate % CGIndenter(CGGeneric(s), 12).define() + + def hasRequiredFields(self, dictionary): + if dictionary.parent: + if self.hasRequiredFields(dictionary.parent): + return True + for member in dictionary.members: + if not member.optional: + return True + return False + @staticmethod def makeMemberName(name): # Can't use Rust keywords as member names. @@ -6075,7 +6834,10 @@ class CGRegisterProxyHandlersMethod(CGAbstractMethod): def definition_body(self): return CGList([ - CGGeneric("PROXY_HANDLERS[Proxies::%s as usize] = Bindings::%s::DefineProxyHandler();" + CGGeneric("proxy_handlers::%s.store(\n" + " Bindings::%s::DefineProxyHandler() as *mut _,\n" + " std::sync::atomic::Ordering::Release,\n" + ");" % (desc.name, '::'.join([desc.name + 'Binding'] * 2))) for desc in self.descriptors ], "\n") @@ -6084,10 +6846,18 @@ class CGRegisterProxyHandlersMethod(CGAbstractMethod): class CGRegisterProxyHandlers(CGThing): def __init__(self, config): descriptors = config.getDescriptors(proxy=True) - length = len(descriptors) self.root = CGList([ - CGGeneric("pub static mut PROXY_HANDLERS: [*const libc::c_void; %d] = [0 as *const libc::c_void; %d];" - % (length, length)), + CGGeneric( + "#[allow(non_upper_case_globals)]\n" + + "pub mod proxy_handlers {\n" + + "".join( + " pub static %s: std::sync::atomic::AtomicPtr<libc::c_void> =\n" + " std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());\n" + % desc.name + for desc in descriptors + ) + + "}\n" + ), CGRegisterProxyHandlersMethod(descriptors), ], "\n") @@ -6097,7 +6867,7 @@ class CGRegisterProxyHandlers(CGThing): class CGBindingRoot(CGThing): """ - Root codegen class for binding generation. Instantiate the class, and call + DomRoot codegen class for binding generation. Instantiate the class, and call declare or define to generate header or cpp code (respectively). """ def __init__(self, config, prefix, webIDLFile): @@ -6128,7 +6898,7 @@ class CGBindingRoot(CGThing): # Do codegen for all the enums. cgthings = [CGEnum(e) for e in enums] - # Do codegen for all the typdefs + # Do codegen for all the typedefs for t in typedefs: typeName = getRetvalDeclarationForType(t.innerType, config.getDescriptorProvider()) substs = { @@ -6166,7 +6936,7 @@ class CGBindingRoot(CGThing): # Add imports curr = generate_imports(config, curr, callbackDescriptors, mainCallbacks, - dictionaries, enums) + dictionaries, enums, typedefs) # Add the auto-generated comment. curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) @@ -6202,6 +6972,9 @@ def type_needs_tracing(t): if t.isUnion(): return any(type_needs_tracing(member) for member in t.flatMemberTypes) + if is_typed_array(t): + return True + return False if t.isDictionary(): @@ -6222,9 +6995,33 @@ def type_needs_tracing(t): assert False, (t, type(t)) +def is_typed_array(t): + assert isinstance(t, IDLObject), (t, type(t)) + + return t.isTypedArray() or t.isArrayBuffer() or t.isArrayBufferView() + + +def type_needs_auto_root(t): + """ + Certain IDL types, such as `sequence<any>` or `sequence<object>` need to be + traced and wrapped via (Custom)AutoRooter + """ + assert isinstance(t, IDLObject), (t, type(t)) + + if t.isType(): + if t.isSequence() and (t.inner.isAny() or t.inner.isObject()): + return True + # SpiderMonkey interfaces, we currently don't support any other except typed arrays + if is_typed_array(t): + return True + + return False + + def argument_type(descriptorProvider, ty, optional=False, defaultValue=None, variadic=False): info = getJSToNativeConversionInfo( - ty, descriptorProvider, isArgument=True) + ty, descriptorProvider, isArgument=True, + isAutoRooted=type_needs_auto_root(ty)) declType = info.declType if variadic: @@ -6238,12 +7035,15 @@ def argument_type(descriptorProvider, ty, optional=False, defaultValue=None, var if ty.isDictionary() and not type_needs_tracing(ty): declType = CGWrapper(declType, pre="&") + if type_needs_auto_root(ty): + declType = CGTemplatedType("CustomAutoRooterGuard", declType) + return declType.define() -def method_arguments(descriptorProvider, returnType, arguments, passJSBits=True, trailing=None): +def method_arguments(descriptorProvider, returnType, arguments, passJSBits=True, trailing=None, inRealm=False): if needCx(returnType, arguments, passJSBits): - yield "cx", "*mut JSContext" + yield "cx", "SafeJSContext" for argument in arguments: ty = argument_type(descriptorProvider, argument.type, argument.optional, @@ -6253,6 +7053,9 @@ def method_arguments(descriptorProvider, returnType, arguments, passJSBits=True, if trailing: yield trailing + if inRealm: + yield "_comp", "InRealm" + def return_type(descriptorProvider, rettype, infallible): result = getRetvalDeclarationForType(rettype, descriptorProvider) @@ -6263,7 +7066,8 @@ def return_type(descriptorProvider, rettype, infallible): class CGNativeMember(ClassMethod): def __init__(self, descriptorProvider, member, name, signature, extendedAttrs, - breakAfter=True, passJSBitsAsNeeded=True, visibility="public"): + breakAfter=True, passJSBitsAsNeeded=True, visibility="public", + unsafe=False): """ If passJSBitsAsNeeded is false, we don't automatically pass in a JSContext* or a JSObject* based on the return and argument types. @@ -6279,9 +7083,10 @@ class CGNativeMember(ClassMethod): static=member.isStatic(), # Mark our getters, which are attrs that # have a non-void return type, as const. - const=(not member.isStatic() and member.isAttr() and - not signature[0].isVoid()), + const=(not member.isStatic() and member.isAttr() + and not signature[0].isVoid()), breakAfterSelf=breakAfterSelf, + unsafe=unsafe, visibility=visibility) def getReturnType(self, type): @@ -6297,8 +7102,7 @@ class CGNativeMember(ClassMethod): class CGCallback(CGClass): - def __init__(self, idlObject, descriptorProvider, baseName, methods, - getters=[], setters=[]): + def __init__(self, idlObject, descriptorProvider, baseName, methods): self.baseName = baseName self._deps = idlObject.getDeps() name = idlObject.identifier.name @@ -6317,12 +7121,13 @@ class CGCallback(CGClass): CGClass.__init__(self, name, bases=[ClassBase(baseName)], constructors=self.getConstructors(), - methods=realMethods + getters + setters, - decorators="#[derive(JSTraceable, PartialEq)]\n#[allow_unrooted_interior]") + methods=realMethods, + decorators="#[derive(JSTraceable, PartialEq)]\n" + "#[unrooted_must_root_lint::allow_unrooted_interior]") def getConstructors(self): return [ClassConstructor( - [Argument("*mut JSContext", "aCx"), Argument("*mut JSObject", "aCallback")], + [Argument("SafeJSContext", "aCx"), Argument("*mut JSObject", "aCallback")], bodyInHeader=True, visibility="pub", explicit=False, @@ -6335,7 +7140,7 @@ class CGCallback(CGClass): args = list(method.args) # Strip out the JSContext*/JSObject* args # that got added. - assert args[0].name == "cx" and args[0].argType == "*mut JSContext" + assert args[0].name == "cx" and args[0].argType == "SafeJSContext" assert args[1].name == "aThisObj" and args[1].argType == "HandleObject" args = args[2:] # Record the names of all the arguments, so we can use them when we call @@ -6361,23 +7166,23 @@ class CGCallback(CGClass): setupCall = "let s = CallSetup::new(self, aExceptionHandling);\n" bodyWithThis = string.Template( - setupCall + - "rooted!(in(s.get_context()) let mut thisObjJS = ptr::null_mut());\n" - "wrap_call_this_object(s.get_context(), thisObj, thisObjJS.handle_mut());\n" - "if thisObjJS.is_null() {\n" - " return Err(JSFailed);\n" - "}\n" - "return ${methodName}(${callArgs});").substitute({ - "callArgs": ", ".join(argnamesWithThis), - "methodName": 'self.' + method.name, - }) + setupCall + + "rooted!(in(*s.get_context()) let mut thisObjJS = ptr::null_mut::<JSObject>());\n" + "wrap_call_this_object(s.get_context(), thisObj, thisObjJS.handle_mut());\n" + "if thisObjJS.is_null() {\n" + " return Err(JSFailed);\n" + "}\n" + "unsafe { ${methodName}(${callArgs}) }").substitute({ + "callArgs": ", ".join(argnamesWithThis), + "methodName": 'self.' + method.name, + }) bodyWithoutThis = string.Template( - setupCall + - "rooted!(in(s.get_context()) let thisObjJS = ptr::null_mut());\n" - "return ${methodName}(${callArgs});").substitute({ - "callArgs": ", ".join(argnamesWithoutThis), - "methodName": 'self.' + method.name, - }) + setupCall + + "rooted!(in(*s.get_context()) let thisObjJS = ptr::null_mut::<JSObject>());\n" + "unsafe { ${methodName}(${callArgs}) }").substitute({ + "callArgs": ", ".join(argnamesWithoutThis), + "methodName": 'self.' + method.name, + }) return [ClassMethod(method.name + '_', method.returnType, args, bodyInHeader=True, templateArgs=["T: DomObject"], @@ -6418,7 +7223,7 @@ class CGCallbackFunctionImpl(CGGeneric): def __init__(self, callback): impl = string.Template("""\ impl CallbackContainer for ${type} { - unsafe fn new(cx: *mut JSContext, callback: *mut JSObject) -> Rc<${type}> { + unsafe fn new(cx: SafeJSContext, callback: *mut JSObject) -> Rc<${type}> { ${type}::new(cx, callback) } @@ -6440,21 +7245,18 @@ class CGCallbackInterface(CGCallback): def __init__(self, descriptor): iface = descriptor.interface attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()] - getters = [CallbackGetter(a, descriptor) for a in attrs] - setters = [CallbackSetter(a, descriptor) for a in attrs - if not a.readonly] + assert not attrs methods = [m for m in iface.members if m.isMethod() and not m.isStatic() and not m.isIdentifierLess()] methods = [CallbackOperation(m, sig, descriptor) for m in methods for sig in m.signatures()] assert not iface.isJSImplemented() or not iface.ctor() - CGCallback.__init__(self, iface, descriptor, "CallbackInterface", - methods, getters=getters, setters=setters) + CGCallback.__init__(self, iface, descriptor, "CallbackInterface", methods) class FakeMember(): def __init__(self): - self.treatNullAs = "Default" + pass def isStatic(self): return False @@ -6500,6 +7302,7 @@ class CallbackMember(CGNativeMember): name, (self.retvalType, args), extendedAttrs={}, passJSBitsAsNeeded=False, + unsafe=needThisHandling, visibility=visibility) # We have to do all the generation of our body now, because # the caller relies on us throwing if we can't manage it. @@ -6518,7 +7321,7 @@ class CallbackMember(CGNativeMember): replacements["argCount"] = self.argCountStr replacements["argvDecl"] = string.Template( "rooted_vec!(let mut argv);\n" - "argv.extend((0..${argCount}).map(|_| Heap::new(UndefinedValue())));\n" + "argv.extend((0..${argCount}).map(|_| Heap::default()));\n" ).substitute(replacements) else: # Avoid weird 0-sized arrays @@ -6533,10 +7336,7 @@ class CallbackMember(CGNativeMember): "${convertArgs}" "${doCall}" "${returnResult}").substitute(replacements) - return CGWrapper(CGIndenter(CGList([ - CGGeneric(pre), - CGGeneric(body), - ], "\n"), 4), pre="unsafe {\n", post="\n}").define() + return pre + "\n" + body def getResultConversion(self): replacements = { @@ -6593,24 +7393,28 @@ class CallbackMember(CGNativeMember): conversion = wrapForType( "argv_root.handle_mut()", result=argval, - successCode="argv[%s] = Heap::new(argv_root.get());" % jsvalIndex, - pre="rooted!(in(cx) let mut argv_root = UndefinedValue());") + successCode=("{\n" + "let arg = &mut argv[%s];\n" + "*arg = Heap::default();\n" + "arg.set(argv_root.get());\n" + "}") % jsvalIndex, + pre="rooted!(in(*cx) let mut argv_root = UndefinedValue());") if arg.variadic: conversion = string.Template( - "for idx in 0..${arg}.len() {\n" + - CGIndenter(CGGeneric(conversion)).define() + "\n" - "}" + "for idx in 0..${arg}.len() {\n" + + CGIndenter(CGGeneric(conversion)).define() + "\n" + + "}" ).substitute({"arg": arg.identifier.name}) elif arg.optional and not arg.defaultValue: conversion = ( CGIfWrapper("%s.is_some()" % arg.identifier.name, - CGGeneric(conversion)).define() + - " else if argc == %d {\n" - " // This is our current trailing argument; reduce argc\n" - " argc -= 1;\n" - "} else {\n" - " argv[%d] = Heap::new(UndefinedValue());\n" - "}" % (i + 1, i)) + CGGeneric(conversion)).define() + + " else if argc == %d {\n" + " // This is our current trailing argument; reduce argc\n" + " argc -= 1;\n" + "} else {\n" + " argv[%d] = Heap::default();\n" + "}" % (i + 1, i)) return conversion def getArgs(self, returnType, argList): @@ -6623,7 +7427,7 @@ class CallbackMember(CGNativeMember): return args # We want to allow the caller to pass in a "this" object, as # well as a JSContext. - return [Argument("*mut JSContext", "cx"), + return [Argument("SafeJSContext", "cx"), Argument("HandleObject", "aThisObj")] + args def getCallSetup(self): @@ -6632,7 +7436,7 @@ class CallbackMember(CGNativeMember): return "" return ( "CallSetup s(CallbackPreserveColor(), aRv, aExceptionHandling);\n" - "JSContext* cx = s.get_context();\n" + "JSContext* cx = *s.get_context();\n" "if (!cx) {\n" " return Err(JSFailed);\n" "}\n") @@ -6664,7 +7468,7 @@ class CallbackMethod(CallbackMember): needThisHandling) def getRvalDecl(self): - return "rooted!(in(cx) let mut rval = UndefinedValue());\n" + return "rooted!(in(*cx) let mut rval = UndefinedValue());\n" def getCall(self): replacements = { @@ -6680,9 +7484,9 @@ class CallbackMethod(CallbackMember): replacements["argc"] = "0" return string.Template( "${getCallable}" - "rooted!(in(cx) let rootedThis = ${thisObj});\n" + "rooted!(in(*cx) let rootedThis = ${thisObj});\n" "let ok = ${callGuard}JS_CallFunctionValue(\n" - " cx, rootedThis.handle(), callable.handle(),\n" + " *cx, rootedThis.handle(), callable.handle(),\n" " &HandleValueArray {\n" " length_: ${argc} as ::libc::size_t,\n" " elements_: ${argv}\n" @@ -6703,7 +7507,7 @@ class CallCallback(CallbackMethod): return "aThisObj.get()" def getCallableDecl(self): - return "rooted!(in(cx) let callable = ObjectValue(self.callback()));\n" + return "rooted!(in(*cx) let callable = ObjectValue(self.callback()));\n" def getCallGuard(self): if self.callback._treatNonObjectAsNull: @@ -6733,14 +7537,14 @@ class CallbackOperationBase(CallbackMethod): "methodName": self.methodName } getCallableFromProp = string.Template( - 'try!(self.parent.get_callable_property(cx, "${methodName}"))' + 'self.parent.get_callable_property(cx, "${methodName}")?' ).substitute(replacements) if not self.singleOperation: - return 'rooted!(in(cx) let callable =\n' + getCallableFromProp + ');\n' + return 'rooted!(in(*cx) let callable =\n' + getCallableFromProp + ');\n' return ( 'let isCallable = IsCallable(self.callback());\n' - 'rooted!(in(cx) let callable =\n' + - CGIndenter( + 'rooted!(in(*cx) let callable =\n' + + CGIndenter( CGIfElseWrapper('isCallable', CGGeneric('ObjectValue(self.callback())'), CGGeneric(getCallableFromProp))).define() + ');\n') @@ -6762,59 +7566,6 @@ class CallbackOperation(CallbackOperationBase): descriptor, descriptor.interface.isSingleOperationInterface()) -class CallbackGetter(CallbackMember): - def __init__(self, attr, descriptor): - self.ensureASCIIName(attr) - self.attrName = attr.identifier.name - CallbackMember.__init__(self, - (attr.type, []), - callbackGetterName(attr), - descriptor, - needThisHandling=False) - - def getRvalDecl(self): - return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n" - - def getCall(self): - replacements = { - "attrName": self.attrName - } - return string.Template( - 'if (!JS_GetProperty(cx, mCallback, "${attrName}", &rval)) {\n' - ' return Err(JSFailed);\n' - '}\n').substitute(replacements) - - -class CallbackSetter(CallbackMember): - def __init__(self, attr, descriptor): - self.ensureASCIIName(attr) - self.attrName = attr.identifier.name - CallbackMember.__init__(self, - (BuiltinTypes[IDLBuiltinType.Types.void], - [FakeArgument(attr.type, attr)]), - callbackSetterName(attr), - descriptor, - needThisHandling=False) - - def getRvalDecl(self): - # We don't need an rval - return "" - - def getCall(self): - replacements = { - "attrName": self.attrName, - "argv": "argv.handleAt(0)", - } - return string.Template( - 'MOZ_ASSERT(argv.length() == 1);\n' - 'if (!JS_SetProperty(cx, mCallback, "${attrName}", ${argv})) {\n' - ' return Err(JSFailed);\n' - '}\n').substitute(replacements) - - def getArgcDecl(self): - return None - - class CGIterableMethodGenerator(CGGeneric): """ Creates methods for iterable interfaces. Unwrapping/wrapping @@ -6827,24 +7578,35 @@ class CGIterableMethodGenerator(CGGeneric): CGGeneric.__init__(self, fill( """ if !IsCallable(arg0) { - throw_type_error(cx, "Argument 1 of ${ifaceName}.forEach is not callable."); + throw_type_error(*cx, "Argument 1 of ${ifaceName}.forEach is not callable."); return false; } - rooted!(in(cx) let arg0 = ObjectValue(arg0)); - rooted!(in(cx) let mut call_arg1 = UndefinedValue()); - rooted!(in(cx) let mut call_arg2 = UndefinedValue()); + rooted!(in(*cx) let arg0 = ObjectValue(arg0)); + rooted!(in(*cx) let mut call_arg1 = UndefinedValue()); + rooted!(in(*cx) let mut call_arg2 = UndefinedValue()); let mut call_args = vec![UndefinedValue(), UndefinedValue(), ObjectValue(*_obj)]; - rooted!(in(cx) let mut ignoredReturnVal = UndefinedValue()); - for i in 0..(*this).get_iterable_length() { - (*this).get_value_at_index(i).to_jsval(cx, call_arg1.handle_mut()); - (*this).get_key_at_index(i).to_jsval(cx, call_arg2.handle_mut()); + rooted!(in(*cx) let mut ignoredReturnVal = UndefinedValue()); + + // This has to be a while loop since get_iterable_length() may change during + // the callback, and we need to avoid iterator invalidation. + // + // It is possible for this to loop infinitely, but that matches the spec + // and other browsers. + // + // https://heycam.github.io/webidl/#es-forEach + let mut i = 0; + while i < (*this).get_iterable_length() { + (*this).get_value_at_index(i).to_jsval(*cx, call_arg1.handle_mut()); + (*this).get_key_at_index(i).to_jsval(*cx, call_arg2.handle_mut()); call_args[0] = call_arg1.handle().get(); call_args[1] = call_arg2.handle().get(); let call_args = HandleValueArray { length_: 3, elements_: call_args.as_ptr() }; - if !Call(cx, arg1, arg0.handle(), &call_args, + if !Call(*cx, arg1, arg0.handle(), &call_args, ignoredReturnVal.handle_mut()) { return false; } + + i += 1; } let result = (); @@ -6853,9 +7615,7 @@ class CGIterableMethodGenerator(CGGeneric): return CGGeneric.__init__(self, fill( """ - let result = ${iterClass}::new(&*this, - IteratorType::${itrMethod}, - super::${ifaceName}IteratorBinding::Wrap); + let result = ${iterClass}::new(&*this, IteratorType::${itrMethod}); """, iterClass=iteratorNativeType(descriptor, True), ifaceName=descriptor.interface.identifier.name, @@ -6868,10 +7628,16 @@ def camel_to_upper_snake(s): def process_arg(expr, arg): if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback(): - if arg.type.nullable() or arg.type.isSequence() or arg.optional: + if arg.variadic or arg.type.isSequence(): expr += ".r()" + elif arg.type.nullable() and arg.optional and not arg.defaultValue: + expr += ".as_ref().map(Option::as_deref)" + elif arg.type.nullable() or arg.optional and not arg.defaultValue: + expr += ".as_deref()" else: expr = "&" + expr + elif isinstance(arg.type, IDLPromiseType): + expr = "&" + expr return expr @@ -6886,8 +7652,9 @@ class GlobalGenRoots(): @staticmethod def InterfaceObjectMap(config): mods = [ - "dom::bindings::codegen", - "js::jsapi::{HandleObject, JSContext}", + "crate::dom::bindings::codegen", + "crate::script_runtime::JSContext", + "js::rust::HandleObject", "phf", ] imports = CGList([CGGeneric("use %s;" % mod) for mod in mods], "\n") @@ -6899,9 +7666,9 @@ class GlobalGenRoots(): for (idx, d) in enumerate(global_descriptors) ) global_flags = CGWrapper(CGIndenter(CGList([ - CGGeneric("const %s = %#x," % args) + CGGeneric("const %s = %#x;" % args) for args in flags - ], "\n")), pre="pub flags Globals: u8 {\n", post="\n}") + ], "\n")), pre="pub struct Globals: u8 {\n", post="\n}") globals_ = CGWrapper(CGIndenter(global_flags), pre="bitflags! {\n", post="\n}") phf = CGGeneric("include!(concat!(env!(\"OUT_DIR\"), \"/InterfaceObjectMapPhf.rs\"));") @@ -6917,11 +7684,13 @@ class GlobalGenRoots(): for d in config.getDescriptors(hasInterfaceObject=True, isInline=False): binding = toBindingNamespace(d.name) pairs.append((d.name, binding, binding)) + for alias in d.interface.legacyWindowAliases: + pairs.append((alias, binding, binding)) for ctor in d.interface.namedConstructors: pairs.append((ctor.identifier.name, binding, binding)) pairs.sort(key=operator.itemgetter(0)) mappings = [ - CGGeneric('"%s": "codegen::Bindings::%s::%s::DefineDOMInterface as unsafe fn(_, _)"' % pair) + CGGeneric('"%s": "codegen::Bindings::%s::%s::DefineDOMInterface"' % pair) for pair in pairs ] return CGWrapper( @@ -6938,8 +7707,6 @@ class GlobalGenRoots(): for d in config.getDescriptors(hasInterfaceObject=True) if d.shouldHaveGetConstructorObjectMethod()]) - proxies = [d.name for d in config.getDescriptors(proxy=True)] - return CGList([ CGGeneric(AUTOGENERATED_WARNING_COMMENT), CGGeneric("pub const PROTO_OR_IFACE_LENGTH: usize = %d;\n" % (len(protos) + len(constructors))), @@ -6956,7 +7723,6 @@ class GlobalGenRoots(): " debug_assert!(proto_id < ID::Last as u16);\n" " INTERFACES[proto_id as usize]\n" "}\n\n"), - CGNonNamespacedEnum('Proxies', proxies, 0, deriving="PartialEq, Copy, Clone"), ]) @staticmethod @@ -6966,10 +7732,8 @@ class GlobalGenRoots(): CGRegisterProxyHandlers(config), ], "\n") - return CGImports(code, descriptors=[], callbacks=[], dictionaries=[], enums=[], imports=[ - 'dom::bindings::codegen::Bindings', - 'dom::bindings::codegen::PrototypeList::Proxies', - 'libc', + return CGImports(code, descriptors=[], callbacks=[], dictionaries=[], enums=[], typedefs=[], imports=[ + 'crate::dom::bindings::codegen::Bindings', ], config=config, ignored_warnings=[]) @staticmethod @@ -6978,8 +7742,8 @@ class GlobalGenRoots(): for d in config.getDescriptors(register=True, isCallback=False, isIteratorInterface=False)]) - curr = CGList([CGGeneric("pub use dom::%s::%s;\n" % (name.lower(), - MakeNativeName(name))) + curr = CGList([CGGeneric("pub use crate::dom::%s::%s;\n" % (name.lower(), + MakeNativeName(name))) for name in descriptors]) curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) return curr @@ -6991,9 +7755,9 @@ class GlobalGenRoots(): return getModuleFromObject(d).split('::')[-1] descriptors = config.getDescriptors(register=True, isIteratorInterface=False) - descriptors = (set(toBindingNamespace(d.name) for d in descriptors) | - set(leafModule(d) for d in config.callbacks) | - set(leafModule(d) for d in config.getDictionaries())) + descriptors = (set(toBindingNamespace(d.name) for d in descriptors) + | set(leafModule(d) for d in config.callbacks) + | set(leafModule(d) for d in config.getDictionaries())) curr = CGList([CGGeneric("pub mod %s;\n" % name) for name in sorted(descriptors)]) curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) return curr @@ -7002,12 +7766,12 @@ class GlobalGenRoots(): def InheritTypes(config): descriptors = config.getDescriptors(register=True, isCallback=False) - imports = [CGGeneric("use dom::types::*;\n"), - CGGeneric("use dom::bindings::conversions::{DerivedFrom, get_dom_class};\n"), - CGGeneric("use dom::bindings::inheritance::Castable;\n"), - CGGeneric("use dom::bindings::js::{JS, LayoutJS, Root};\n"), - CGGeneric("use dom::bindings::trace::JSTraceable;\n"), - CGGeneric("use dom::bindings::reflector::DomObject;\n"), + imports = [CGGeneric("use crate::dom::types::*;\n"), + CGGeneric("use crate::dom::bindings::conversions::{DerivedFrom, get_dom_class};\n"), + CGGeneric("use crate::dom::bindings::inheritance::Castable;\n"), + CGGeneric("use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom};\n"), + CGGeneric("use crate::dom::bindings::trace::JSTraceable;\n"), + CGGeneric("use crate::dom::bindings::reflector::DomObject;\n"), CGGeneric("use js::jsapi::JSTracer;\n\n"), CGGeneric("use std::mem;\n\n")] allprotos = [] @@ -7066,7 +7830,7 @@ impl Clone for TopTypeId { # TypeId enum. return "%s(%sTypeId)" % (name, name) if name in hierarchy else name - for base, derived in hierarchy.iteritems(): + for base, derived in hierarchy.items(): variants = [] if config.getDescriptor(base).concrete: variants.append(CGGeneric(base)) @@ -7113,7 +7877,7 @@ impl %(base)s { def SupportedDomApis(config): descriptors = config.getDescriptors(isExposedConditionally=False) - base_path = os.path.join('dom', 'bindings', 'codegen') + base_path = os.path.dirname(__file__) with open(os.path.join(base_path, 'apis.html.template')) as f: base_template = f.read() with open(os.path.join(base_path, 'api.html.template')) as f: @@ -7130,6 +7894,8 @@ impl %(base)s { for m in descriptor.interface.members: if PropertyDefiner.getStringAttr(m, 'Pref') or \ PropertyDefiner.getStringAttr(m, 'Func') or \ + PropertyDefiner.getStringAttr(m, 'Exposed') or \ + m.getExtendedAttribute('SecureContext') or \ (m.isMethod() and m.isIdentifierLess()): continue display = m.identifier.name + ('()' if m.isMethod() else '') diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index 0fe9bf6c004..b92f68af3b9 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -1,10 +1,11 @@ # 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 http://mozilla.org/MPL/2.0/. +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +import functools import os -from WebIDL import IDLExternalInterface, IDLWrapperType, WebIDLError +from WebIDL import IDLExternalInterface, IDLSequenceType, IDLWrapperType, WebIDLError class Configuration: @@ -15,7 +16,7 @@ class Configuration: def __init__(self, filename, parseData): # Read the configuration file. glbl = {} - execfile(filename, glbl) + exec(compile(open(filename).read(), filename, 'exec'), glbl) config = glbl['DOMInterfaces'] # Build descriptors for all the interfaces we have in the parse data. @@ -38,11 +39,6 @@ class Configuration: iface = thing self.interfaces[iface.identifier.name] = iface if iface.identifier.name not in config: - # Completely skip consequential interfaces with no descriptor - # if they have no interface object because chances are we - # don't need to do anything interesting with them. - if iface.isConsequential() and not iface.hasInterfaceObject(): - continue entry = {} else: entry = config[iface.identifier.name] @@ -67,7 +63,9 @@ class Configuration: c.isCallback() and not c.isInterface()] # Keep the descriptor list sorted for determinism. - self.descriptors.sort(lambda x, y: cmp(x.name, y.name)) + def cmp(x, y): + return (x > y) - (x < y) + self.descriptors.sort(key=functools.cmp_to_key(lambda x, y: cmp(x.name, y.name))) def getInterface(self, ifname): return self.interfaces[ifname] @@ -75,35 +73,45 @@ class Configuration: def getDescriptors(self, **filters): """Gets the descriptors that match the given filters.""" curr = self.descriptors - for key, val in filters.iteritems(): + for key, val in filters.items(): if key == 'webIDLFile': - getter = lambda x: x.interface.filename() + def getter(x): + return x.interface.filename() elif key == 'hasInterfaceObject': - getter = lambda x: x.interface.hasInterfaceObject() + def getter(x): + return x.interface.hasInterfaceObject() elif key == 'isCallback': - getter = lambda x: x.interface.isCallback() + def getter(x): + return x.interface.isCallback() elif key == 'isNamespace': - getter = lambda x: x.interface.isNamespace() + def getter(x): + return x.interface.isNamespace() elif key == 'isJSImplemented': - getter = lambda x: x.interface.isJSImplemented() + def getter(x): + return x.interface.isJSImplemented() elif key == 'isGlobal': - getter = lambda x: x.isGlobal() + def getter(x): + return x.isGlobal() elif key == 'isInline': - getter = lambda x: x.interface.getExtendedAttribute('Inline') is not None + def getter(x): + return x.interface.getExtendedAttribute('Inline') is not None elif key == 'isExposedConditionally': - getter = lambda x: x.interface.isExposedConditionally() + def getter(x): + return x.interface.isExposedConditionally() elif key == 'isIteratorInterface': - getter = lambda x: x.interface.isIteratorInterface() + def getter(x): + return x.interface.isIteratorInterface() else: - getter = lambda x: getattr(x, key) - curr = filter(lambda x: getter(x) == val, curr) + def getter(x): + return getattr(x, key) + curr = [x for x in curr if getter(x) == val] return curr def getEnums(self, webIDLFile): - return filter(lambda e: e.filename() == webIDLFile, self.enums) + return [e for e in self.enums if e.filename() == webIDLFile] def getTypedefs(self, webIDLFile): - return filter(lambda e: e.filename() == webIDLFile, self.typedefs) + return [e for e in self.typedefs if e.filename() == webIDLFile] @staticmethod def _filterForFile(items, webIDLFile=""): @@ -111,7 +119,7 @@ class Configuration: if not webIDLFile: return items - return filter(lambda x: x.filename() == webIDLFile, items) + return [x for x in items if x.filename() == webIDLFile] def getDictionaries(self, webIDLFile=""): return self._filterForFile(self.dictionaries, webIDLFile=webIDLFile) @@ -128,8 +136,8 @@ class Configuration: # We should have exactly one result. if len(descriptors) != 1: - raise NoSuchDescriptorError("For " + interfaceName + " found " + - str(len(descriptors)) + " matches") + raise NoSuchDescriptorError("For " + interfaceName + " found " + + str(len(descriptors)) + " matches") return descriptors[0] def getDescriptorProvider(self): @@ -160,10 +168,10 @@ class DescriptorProvider: def MemberIsUnforgeable(member, descriptor): - return ((member.isAttr() or member.isMethod()) and - not member.isStatic() and - (member.isUnforgeable() or - bool(descriptor.interface.getExtendedAttribute("Unforgeable")))) + return ((member.isAttr() or member.isMethod()) + and not member.isStatic() + and (member.isUnforgeable() + or bool(descriptor.interface.getExtendedAttribute("Unforgeable")))) class Descriptor(DescriptorProvider): @@ -204,40 +212,41 @@ class Descriptor(DescriptorProvider): self.returnType = 'Rc<%s>' % typeName self.argumentType = '&%s' % typeName self.nativeType = typeName - pathDefault = 'dom::types::%s' % typeName + pathDefault = 'crate::dom::types::%s' % typeName elif self.interface.isCallback(): - ty = 'dom::bindings::codegen::Bindings::%sBinding::%s' % (ifaceName, ifaceName) + ty = 'crate::dom::bindings::codegen::Bindings::%sBinding::%s' % (ifaceName, ifaceName) pathDefault = ty self.returnType = "Rc<%s>" % ty self.argumentType = "???" self.nativeType = ty else: - self.returnType = "Root<%s>" % typeName + self.returnType = "DomRoot<%s>" % typeName self.argumentType = "&%s" % typeName self.nativeType = "*const %s" % typeName if self.interface.isIteratorInterface(): - pathDefault = 'dom::bindings::iterable::IterableIterator' + pathDefault = 'crate::dom::bindings::iterable::IterableIterator' else: - pathDefault = 'dom::types::%s' % MakeNativeName(typeName) + pathDefault = 'crate::dom::types::%s' % MakeNativeName(typeName) self.concreteType = typeName self.register = desc.get('register', True) self.path = desc.get('path', pathDefault) - self.bindingPath = 'dom::bindings::codegen::Bindings::%s' % ('::'.join([ifaceName + 'Binding'] * 2)) + self.inRealmMethods = [name for name in desc.get('inRealms', [])] + self.bindingPath = 'crate::dom::bindings::codegen::Bindings::%s' % ('::'.join([ifaceName + 'Binding'] * 2)) self.outerObjectHook = desc.get('outerObjectHook', 'None') self.proxy = False self.weakReferenceable = desc.get('weakReferenceable', False) # If we're concrete, we need to crawl our ancestor interfaces and mark # them as having a concrete descendant. - self.concrete = (not self.interface.isCallback() and - not self.interface.isNamespace() and - not self.interface.getExtendedAttribute("Abstract") and - not self.interface.getExtendedAttribute("Inline") and - not spiderMonkeyInterface) - self.hasUnforgeableMembers = (self.concrete and - any(MemberIsUnforgeable(m, self) for m in - self.interface.members)) + self.concrete = (not self.interface.isCallback() + and not self.interface.isNamespace() + and not self.interface.getExtendedAttribute("Abstract") + and not self.interface.getExtendedAttribute("Inline") + and not spiderMonkeyInterface) + self.hasUnforgeableMembers = (self.concrete + and any(MemberIsUnforgeable(m, self) for m in + self.interface.members)) self.operations = { 'IndexedGetter': None, @@ -249,6 +258,8 @@ class Descriptor(DescriptorProvider): 'Stringifier': None, } + self.hasDefaultToJSON = False + def addOperation(operation, m): if not self.operations[operation]: self.operations[operation] = m @@ -258,6 +269,8 @@ class Descriptor(DescriptorProvider): for m in self.interface.members: if m.isMethod() and m.isStringifier(): addOperation('Stringifier', m) + if m.isMethod() and m.isDefaultToJSON(): + self.hasDefaultToJSON = True if self.concrete: iface = self.interface @@ -279,8 +292,6 @@ class Descriptor(DescriptorProvider): addIndexedOrNamedOperation('Getter', m) if m.isSetter(): addIndexedOrNamedOperation('Setter', m) - if m.isCreator(): - addIndexedOrNamedOperation('Creator', m) if m.isDeleter(): addIndexedOrNamedOperation('Deleter', m) @@ -316,7 +327,7 @@ class Descriptor(DescriptorProvider): if config == '*': iface = self.interface while iface: - add('all', map(lambda m: m.name, iface.members), attribute) + add('all', [m.name for m in iface.members], attribute) iface = iface.parent else: add('all', [config], attribute) @@ -390,15 +401,26 @@ class Descriptor(DescriptorProvider): parent = parent.parent return None + def supportsIndexedProperties(self): + return self.operations['IndexedGetter'] is not None + def hasDescendants(self): - return (self.interface.getUserData("hasConcreteDescendant", False) or - self.interface.getUserData("hasProxyDescendant", False)) + return (self.interface.getUserData("hasConcreteDescendant", False) + or self.interface.getUserData("hasProxyDescendant", False)) + + def hasHTMLConstructor(self): + ctor = self.interface.ctor() + return ctor and ctor.isHTMLConstructor() def shouldHaveGetConstructorObjectMethod(self): assert self.interface.hasInterfaceObject() if self.interface.getExtendedAttribute("Inline"): return False - return self.interface.isCallback() or self.interface.isNamespace() or self.hasDescendants() + return (self.interface.isCallback() or self.interface.isNamespace() + or self.hasDescendants() or self.hasHTMLConstructor()) + + def shouldCacheConstructor(self): + return self.hasDescendants() or self.hasHTMLConstructor() def isExposedConditionally(self): return self.interface.isExposedConditionally() @@ -408,8 +430,8 @@ class Descriptor(DescriptorProvider): Returns true if this is the primary interface for a global object of some sort. """ - return bool(self.interface.getExtendedAttribute("Global") or - self.interface.getExtendedAttribute("PrimaryGlobal")) + return bool(self.interface.getExtendedAttribute("Global") + or self.interface.getExtendedAttribute("PrimaryGlobal")) # Some utility methods @@ -420,8 +442,8 @@ def MakeNativeName(name): def getModuleFromObject(object): - return ('dom::bindings::codegen::Bindings::' + - os.path.basename(object.location.filename()).split('.webidl')[0] + 'Binding') + return ('crate::dom::bindings::codegen::Bindings::' + + os.path.basename(object.location.filename()).split('.webidl')[0] + 'Binding') def getTypesFromDescriptor(descriptor): @@ -453,7 +475,7 @@ def getTypesFromDictionary(dictionary): types = [] curDict = dictionary while curDict: - types.extend([m.type for m in curDict.members]) + types.extend([getUnwrappedType(m.type) for m in curDict.members]) curDict = curDict.parent return types @@ -469,6 +491,12 @@ def getTypesFromCallback(callback): return types +def getUnwrappedType(type): + while isinstance(type, IDLSequenceType): + type = type.inner + return type + + def iteratorNativeType(descriptor, infer=False): assert descriptor.interface.isIterable() iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable diff --git a/components/script/dom/bindings/codegen/GlobalGen.py b/components/script/dom/bindings/codegen/GlobalGen.py deleted file mode 100644 index 966e4bdbcd2..00000000000 --- a/components/script/dom/bindings/codegen/GlobalGen.py +++ /dev/null @@ -1,90 +0,0 @@ -# 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 http://mozilla.org/MPL/2.0/. - -# We do one global pass over all the WebIDL to generate our prototype enum -# and generate information for subsequent phases. - -import sys -import os -sys.path.append(os.path.join(".", "parser")) -sys.path.append(os.path.join(".", "ply")) -import WebIDL -import cPickle -from Configuration import Configuration -from CodegenRust import GlobalGenRoots, replaceFileIfChanged - - -def generate_file(config, name, filename): - root = getattr(GlobalGenRoots, name)(config) - code = root.define() - - if replaceFileIfChanged(filename, code): - print "Generating %s" % (filename) - else: - print "%s hasn't changed - not touching it" % (filename) - - -def main(): - # Parse arguments. - from optparse import OptionParser - usageString = "usage: %prog [options] configFile outputdir webidldir [files]" - o = OptionParser(usage=usageString) - o.add_option("--cachedir", dest='cachedir', default=None, - help="Directory in which to cache lex/parse tables.") - o.add_option("--only-html", dest='only_html', action="store_true", - help="Only generate HTML from WebIDL inputs") - o.add_option("--filelist", dest='filelist', default=None, - help="A file containing the list (one per line) of webidl files to process.") - (options, args) = o.parse_args() - - if len(args) < 2: - o.error(usageString) - - configFile = args[0] - outputdir = args[1] - baseDir = args[2] - if options.filelist is not None: - fileList = (l.strip() for l in open(options.filelist).xreadlines()) - else: - fileList = args[3:] - - # Parse the WebIDL. - parser = WebIDL.Parser(options.cachedir) - for filename in fileList: - fullPath = os.path.normpath(os.path.join(baseDir, filename)) - with open(fullPath, 'rb') as f: - lines = f.readlines() - parser.parse(''.join(lines), fullPath) - parserResults = parser.finish() - - if not options.only_html: - # Write the parser results out to a pickle. - resultsPath = os.path.join(outputdir, 'ParserResults.pkl') - with open(resultsPath, 'wb') as resultsFile: - cPickle.dump(parserResults, resultsFile, -1) - - # Load the configuration. - config = Configuration(configFile, parserResults) - - to_generate = [ - ('SupportedDomApis', 'apis.html'), - ] - - if not options.only_html: - to_generate = [ - ('PrototypeList', 'PrototypeList.rs'), - ('RegisterBindings', 'RegisterBindings.rs'), - ('InterfaceObjectMap', 'InterfaceObjectMap.rs'), - ('InterfaceObjectMapData', 'InterfaceObjectMapData.json'), - ('InterfaceTypes', 'InterfaceTypes.rs'), - ('InheritTypes', 'InheritTypes.rs'), - ('Bindings', os.path.join('Bindings', 'mod.rs')), - ('UnionTypes', 'UnionTypes.rs'), - ] - - for name, filename in to_generate: - generate_file(config, name, os.path.join(outputdir, filename)) - -if __name__ == '__main__': - main() diff --git a/components/script/dom/bindings/codegen/parser/WebIDL.py b/components/script/dom/bindings/codegen/parser/WebIDL.py index dc9fa036141..d74278c3e0c 100644 --- a/components/script/dom/bindings/codegen/parser/WebIDL.py +++ b/components/script/dom/bindings/codegen/parser/WebIDL.py @@ -4,13 +4,15 @@ """ A WebIDL parser. """ + from ply import lex, yacc import re import os import traceback import math import string -from collections import defaultdict +from collections import defaultdict, OrderedDict +from itertools import chain # Machinery @@ -40,32 +42,22 @@ def parseInt(literal): return value * sign -# Magic for creating enums -def M_add_class_attribs(attribs, start): - def foo(name, bases, dict_): - for v, k in enumerate(attribs): - dict_[k] = start + v - assert 'length' not in dict_ - dict_['length'] = start + len(attribs) - return type(name, bases, dict_) - return foo - - def enum(*names, **kw): - if len(kw) == 1: - base = kw['base'].__class__ - start = base.length - else: - assert len(kw) == 0 - base = object - start = 0 - - class Foo(base): - __metaclass__ = M_add_class_attribs(names, start) - + class Foo(object): + attrs = OrderedDict() + def __init__(self, names): + for v, k in enumerate(names): + self.attrs[k] = v + def __getattr__(self, attr): + if attr in self.attrs: + return self.attrs[attr] + raise AttributeError def __setattr__(self, name, value): # this makes it read-only raise NotImplementedError - return Foo() + + if "base" not in kw: + return Foo(names) + return Foo(chain(list(kw["base"].attrs.keys()), names)) class WebIDLError(Exception): @@ -132,6 +124,9 @@ class BuiltinLocation(object): return (isinstance(other, BuiltinLocation) and self.msg == other.msg) + def __hash__(self): + return hash(self.msg) + def filename(self): return '<builtin>' @@ -162,6 +157,9 @@ class IDLObject(object): def isNamespace(self): return False + def isInterfaceMixin(self): + return False + def isEnum(self): return False @@ -241,15 +239,19 @@ class IDLScope(IDLObject): # A mapping from global name to the set of global interfaces # that have that global name. self.globalNameMapping = defaultdict(set) - self.primaryGlobalAttr = None - self.primaryGlobalName = None def __str__(self): return self.QName() def QName(self): - if self._name: - return self._name.QName() + "::" + # It's possible for us to be called before __init__ has been called, for + # the IDLObjectWithScope case. In that case, self._name won't be set yet. + if hasattr(self, "_name"): + name = self._name + else: + name = None + if name: + return name.QName() + "::" return "::" def ensureUnique(self, identifier, object): @@ -305,8 +307,8 @@ class IDLScope(IDLObject): # because we need to merge overloads of NamedConstructors and we need to # detect conflicts in those across interfaces. See also the comment in # IDLInterface.addExtendedAttributes for "NamedConstructor". - if (originalObject.tag == IDLInterfaceMember.Tags.Method and - newObject.tag == IDLInterfaceMember.Tags.Method): + if (isinstance(originalObject, IDLMethod) and + isinstance(newObject, IDLMethod)): return originalObject.addOverload(newObject) # Default to throwing, derived classes can override. @@ -316,7 +318,7 @@ class IDLScope(IDLObject): newObject.location) raise WebIDLError( - "Multiple unresolvable definitions of identifier '%s' in scope '%s%s" + "Multiple unresolvable definitions of identifier '%s' in scope '%s'%s" % (identifier.name, str(self), conflictdesc), []) def _lookupIdentifier(self, identifier): @@ -327,6 +329,13 @@ class IDLScope(IDLObject): assert identifier.scope == self return self._lookupIdentifier(identifier) + def addIfaceGlobalNames(self, interfaceName, globalNames): + """Record the global names (from |globalNames|) that can be used in + [Exposed] to expose things in a global named |interfaceName|""" + self.globalNames.update(globalNames) + for name in globalNames: + self.globalNameMapping[name].add(interfaceName) + class IDLIdentifier(IDLObject): def __init__(self, location, scope, name): @@ -367,8 +376,6 @@ class IDLUnresolvedIdentifier(IDLObject): [location]) if name[0] == '_' and not allowDoubleUnderscore: name = name[1:] - # TODO: Bug 872377, Restore "toJSON" to below list. - # We sometimes need custom serialization, so allow toJSON for now. if (name in ["constructor", "toString"] and not allowForbidden): raise WebIDLError("Cannot use reserved identifier '%s'" % (name), @@ -409,48 +416,11 @@ class IDLObjectWithIdentifier(IDLObject): if parentScope: self.resolve(parentScope) - self.treatNullAs = "Default" - def resolve(self, parentScope): assert isinstance(parentScope, IDLScope) assert isinstance(self.identifier, IDLUnresolvedIdentifier) self.identifier.resolve(parentScope, self) - def checkForStringHandlingExtendedAttributes(self, attrs, - isDictionaryMember=False, - isOptional=False): - """ - A helper function to deal with TreatNullAs. Returns the list - of attrs it didn't handle itself. - """ - assert isinstance(self, IDLArgument) or isinstance(self, IDLAttribute) - unhandledAttrs = list() - for attr in attrs: - if not attr.hasValue(): - unhandledAttrs.append(attr) - continue - - identifier = attr.identifier() - value = attr.value() - if identifier == "TreatNullAs": - if not self.type.isDOMString() or self.type.nullable(): - raise WebIDLError("[TreatNullAs] is only allowed on " - "arguments or attributes whose type is " - "DOMString", - [self.location]) - if isDictionaryMember: - raise WebIDLError("[TreatNullAs] is not allowed for " - "dictionary members", [self.location]) - if value != 'EmptyString': - raise WebIDLError("[TreatNullAs] must take the identifier " - "'EmptyString', not '%s'" % value, - [self.location]) - self.treatNullAs = value - else: - unhandledAttrs.append(attr) - - return unhandledAttrs - class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope): def __init__(self, location, parentScope, identifier): @@ -496,8 +466,17 @@ class IDLExposureMixins(): raise WebIDLError("Unknown [Exposed] value %s" % globalName, [self._location]) - if len(self._exposureGlobalNames) == 0: - self._exposureGlobalNames.add(scope.primaryGlobalName) + # Verify that we are exposed _somwhere_ if we have some place to be + # exposed. We don't want to assert that we're definitely exposed + # because a lot of our parser tests have small-enough IDL snippets that + # they don't include any globals, and we don't really want to go through + # and add global interfaces and [Exposed] annotations to all those + # tests. + if len(scope.globalNames) != 0: + if (len(self._exposureGlobalNames) == 0): + raise WebIDLError(("'%s' is not exposed anywhere even though we have " + "globals to be exposed to") % self, + [self.location]) globalNameSetToExposureSet(scope, self._exposureGlobalNames, self.exposureSet) @@ -505,18 +484,14 @@ class IDLExposureMixins(): def isExposedInWindow(self): return 'Window' in self.exposureSet - def isExposedOnMainThread(self): - return (self.isExposedInWindow() or - self.isExposedInSystemGlobals()) - def isExposedInAnyWorker(self): return len(self.getWorkerExposureSet()) > 0 def isExposedInWorkerDebugger(self): return len(self.getWorkerDebuggerExposureSet()) > 0 - def isExposedInSystemGlobals(self): - return 'BackstagePass' in self.exposureSet + def isExposedInAnyWorklet(self): + return len(self.getWorkletExposureSet()) > 0 def isExposedInSomeButNotAllWorkers(self): """ @@ -534,22 +509,24 @@ class IDLExposureMixins(): workerScopes = self._globalScope.globalNameMapping["Worker"] return workerScopes.intersection(self.exposureSet) + def getWorkletExposureSet(self): + workletScopes = self._globalScope.globalNameMapping["Worklet"] + return workletScopes.intersection(self.exposureSet) + def getWorkerDebuggerExposureSet(self): workerDebuggerScopes = self._globalScope.globalNameMapping["WorkerDebugger"] return workerDebuggerScopes.intersection(self.exposureSet) -class IDLExternalInterface(IDLObjectWithIdentifier, IDLExposureMixins): +class IDLExternalInterface(IDLObjectWithIdentifier): def __init__(self, location, parentScope, identifier): assert isinstance(identifier, IDLUnresolvedIdentifier) assert isinstance(parentScope, IDLScope) self.parent = None IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) - IDLExposureMixins.__init__(self, location) IDLObjectWithIdentifier.resolve(self, parentScope) def finish(self, scope): - IDLExposureMixins.finish(self, scope) pass def validate(self): @@ -564,11 +541,11 @@ class IDLExternalInterface(IDLObjectWithIdentifier, IDLExposureMixins): def isInterface(self): return True - def isConsequential(self): - return False - def addExtendedAttributes(self, attrs): - assert len(attrs) == 0 + if len(attrs) != 0: + raise WebIDLError("There are no extended attributes that are " + "allowed on external interfaces", + [attrs[0].location, self.location]) def resolve(self, parentScope): pass @@ -579,16 +556,41 @@ class IDLExternalInterface(IDLObjectWithIdentifier, IDLExposureMixins): def isJSImplemented(self): return False - def isProbablyShortLivingObject(self): - return False - - def isNavigatorProperty(self): + def hasProbablyShortLivingWrapper(self): return False def _getDependentObjects(self): return set() +class IDLPartialDictionary(IDLObject): + def __init__(self, location, name, members, nonPartialDictionary): + assert isinstance(name, IDLUnresolvedIdentifier) + + IDLObject.__init__(self, location) + self.identifier = name + self.members = members + self._nonPartialDictionary = nonPartialDictionary + self._finished = False + nonPartialDictionary.addPartialDictionary(self) + + def addExtendedAttributes(self, attrs): + pass + + def finish(self, scope): + if self._finished: + return + self._finished = True + + # Need to make sure our non-partial dictionary gets + # finished so it can report cases when we only have partial + # dictionaries. + self._nonPartialDictionary.finish(scope) + + def validate(self): + pass + + class IDLPartialInterfaceOrNamespace(IDLObject): def __init__(self, location, name, members, nonPartialInterfaceOrNamespace): assert isinstance(name, IDLUnresolvedIdentifier) @@ -602,13 +604,13 @@ class IDLPartialInterfaceOrNamespace(IDLObject): self._haveSecureContextExtendedAttribute = False self._nonPartialInterfaceOrNamespace = nonPartialInterfaceOrNamespace self._finished = False - nonPartialInterfaceOrNamespace.addPartialInterface(self) + nonPartialInterfaceOrNamespace.addPartial(self) def addExtendedAttributes(self, attrs): for attr in attrs: identifier = attr.identifier() - if identifier in ["Constructor", "NamedConstructor"]: + if identifier == "NamedConstructor": self.propagatedExtendedAttrs.append(attr) elif identifier == "SecureContext": self._haveSecureContextExtendedAttribute = True @@ -673,36 +675,225 @@ def globalNameSetToExposureSet(globalScope, nameSet, exposureSet): for name in nameSet: exposureSet.update(globalScope.globalNameMapping[name]) - -class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): - def __init__(self, location, parentScope, name, parent, members, - isKnownNonPartial): +class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMixins): + def __init__(self, location, parentScope, name): assert isinstance(parentScope, IDLScope) assert isinstance(name, IDLUnresolvedIdentifier) + + self._finished = False + self.members = [] + self._partials = [] + self._extendedAttrDict = {} + self._isKnownNonPartial = False + + IDLObjectWithScope.__init__(self, location, parentScope, name) + IDLExposureMixins.__init__(self, location) + + def finish(self, scope): + if not self._isKnownNonPartial: + raise WebIDLError("%s does not have a non-partial declaration" % + str(self), [self.location]) + + IDLExposureMixins.finish(self, scope) + + # Now go ahead and merge in our partials. + for partial in self._partials: + partial.finish(scope) + self.addExtendedAttributes(partial.propagatedExtendedAttrs) + self.members.extend(partial.members) + + def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): + assert isinstance(scope, IDLScope) + assert isinstance(originalObject, IDLInterfaceMember) + assert isinstance(newObject, IDLInterfaceMember) + + retval = IDLScope.resolveIdentifierConflict(self, scope, identifier, + originalObject, newObject) + + # Might be a ctor, which isn't in self.members + if newObject in self.members: + self.members.remove(newObject) + return retval + + def typeName(self): + if self.isInterface(): + return "interface" + if self.isNamespace(): + return "namespace" + assert self.isInterfaceMixin() + return "interface mixin" + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + + def setNonPartial(self, location, members): + if self._isKnownNonPartial: + raise WebIDLError("Two non-partial definitions for the " + "same %s" % self.typeName(), + [location, self.location]) + self._isKnownNonPartial = True + # Now make it look like we were parsed at this new location, since + # that's the place where the interface is "really" defined + self.location = location + # Put the new members at the beginning + self.members = members + self.members + + def addPartial(self, partial): + assert self.identifier.name == partial.identifier.name + self._partials.append(partial) + + def getPartials(self): + # Don't let people mutate our guts. + return list(self._partials) + + def finishMembers(self, scope): + # Assuming we've merged in our partials, set the _exposureGlobalNames on + # any members that don't have it set yet. Note that any partial + # interfaces that had [Exposed] set have already set up + # _exposureGlobalNames on all the members coming from them, so this is + # just implementing the "members default to interface or interface mixin + # that defined them" and "partial interfaces or interface mixins default + # to interface or interface mixin they're a partial for" rules from the + # spec. + for m in self.members: + # If m, or the partial m came from, had [Exposed] + # specified, it already has a nonempty exposure global names set. + if len(m._exposureGlobalNames) == 0: + m._exposureGlobalNames.update(self._exposureGlobalNames) + if m.isAttr() and m.stringifier: + m.expand(self.members) + + # resolve() will modify self.members, so we need to iterate + # over a copy of the member list here. + for member in list(self.members): + member.resolve(self) + + for member in self.members: + member.finish(scope) + + # Now that we've finished our members, which has updated their exposure + # sets, make sure they aren't exposed in places where we are not. + for member in self.members: + if not member.exposureSet.issubset(self.exposureSet): + raise WebIDLError("Interface or interface mixin member has " + "larger exposure set than its container", + [member.location, self.location]) + + def isExternal(self): + return False + + +class IDLInterfaceMixin(IDLInterfaceOrInterfaceMixinOrNamespace): + def __init__(self, location, parentScope, name, members, isKnownNonPartial): + self.actualExposureGlobalNames = set() + + assert isKnownNonPartial or len(members) == 0 + IDLInterfaceOrInterfaceMixinOrNamespace.__init__(self, location, parentScope, name) + + if isKnownNonPartial: + self.setNonPartial(location, members) + + def __str__(self): + return "Interface mixin '%s'" % self.identifier.name + + def isInterfaceMixin(self): + return True + + def finish(self, scope): + if self._finished: + return + self._finished = True + + # Expose to the globals of interfaces that includes this mixin if this + # mixin has no explicit [Exposed] so that its members can be exposed + # based on the base interface exposure set. + # + # Make sure this is done before IDLExposureMixins.finish call, since + # that converts our set of exposure global names to an actual exposure + # set. + hasImplicitExposure = len(self._exposureGlobalNames) == 0 + if hasImplicitExposure: + self._exposureGlobalNames.update(self.actualExposureGlobalNames) + + IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope) + + self.finishMembers(scope) + + def validate(self): + for member in self.members: + + if member.isAttr(): + if member.inherit: + raise WebIDLError("Interface mixin member cannot include " + "an inherited attribute", + [member.location, self.location]) + if member.isStatic(): + raise WebIDLError("Interface mixin member cannot include " + "a static member", + [member.location, self.location]) + + if member.isMethod(): + if member.isStatic(): + raise WebIDLError("Interface mixin member cannot include " + "a static operation", + [member.location, self.location]) + if (member.isGetter() or + member.isSetter() or + member.isDeleter() or + member.isLegacycaller()): + raise WebIDLError("Interface mixin member cannot include a " + "special operation", + [member.location, self.location]) + + def addExtendedAttributes(self, attrs): + for attr in attrs: + identifier = attr.identifier() + + if identifier == "SecureContext": + if not attr.noArguments(): + raise WebIDLError("[%s] must take no arguments" % identifier, + [attr.location]) + # This gets propagated to all our members. + for member in self.members: + if member.getExtendedAttribute("SecureContext"): + raise WebIDLError("[SecureContext] specified on both " + "an interface mixin member and on" + "the interface mixin itself", + [member.location, attr.location]) + member.addExtendedAttributes([attr]) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, + self._exposureGlobalNames) + else: + raise WebIDLError("Unknown extended attribute %s on interface" % identifier, + [attr.location]) + + attrlist = attr.listValue() + self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True + + def _getDependentObjects(self): + return set(self.members) + + +class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): + def __init__(self, location, parentScope, name, parent, members, + isKnownNonPartial, toStringTag): assert isKnownNonPartial or not parent assert isKnownNonPartial or len(members) == 0 self.parent = None self._callback = False - self._finished = False - self.members = [] self.maplikeOrSetlikeOrIterable = None - self._partialInterfaces = [] - self._extendedAttrDict = {} # namedConstructors needs deterministic ordering because bindings code # outputs the constructs in the order that namedConstructors enumerates # them. self.namedConstructors = list() - self.implementedInterfaces = set() - self._consequential = False - self._isKnownNonPartial = False + self.legacyWindowAliases = [] + self.includedMixins = set() # self.interfacesBasedOnSelf is the set of interfaces that inherit from - # self or have self as a consequential interface, including self itself. + # self, including self itself. # Used for distinguishability checking. self.interfacesBasedOnSelf = set([self]) - # self.interfacesImplementingSelf is the set of interfaces that directly - # have self as a consequential interface - self.interfacesImplementingSelf = set() self._hasChildInterfaces = False self._isOnGlobalProtoChain = False # Tracking of the number of reserved slots we need for our @@ -713,9 +904,14 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): # If this is an iterator interface, we need to know what iterable # interface we're iterating for in order to get its nativeType. self.iterableInterface = None + # True if we have cross-origin members. + self.hasCrossOriginMembers = False + # True if some descendant (including ourselves) has cross-origin members + self.hasDescendantWithCrossOriginMembers = False - IDLObjectWithScope.__init__(self, location, parentScope, name) - IDLExposureMixins.__init__(self, location) + self.toStringTag = toStringTag + + IDLInterfaceOrInterfaceMixinOrNamespace.__init__(self, location, parentScope, name) if isKnownNonPartial: self.setNonPartial(location, parent, members) @@ -735,37 +931,23 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): def isIteratorInterface(self): return self.iterableInterface is not None - def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): - assert isinstance(scope, IDLScope) - assert isinstance(originalObject, IDLInterfaceMember) - assert isinstance(newObject, IDLInterfaceMember) - - retval = IDLScope.resolveIdentifierConflict(self, scope, identifier, - originalObject, newObject) - - # Might be a ctor, which isn't in self.members - if newObject in self.members: - self.members.remove(newObject) - return retval - def finish(self, scope): if self._finished: return self._finished = True - if not self._isKnownNonPartial: - raise WebIDLError("Interface %s does not have a non-partial " - "declaration" % self.identifier.name, - [self.location]) - - IDLExposureMixins.finish(self, scope) + IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope) - # Now go ahead and merge in our partial interfaces. - for partial in self._partialInterfaces: - partial.finish(scope) - self.addExtendedAttributes(partial.propagatedExtendedAttrs) - self.members.extend(partial.members) + if len(self.legacyWindowAliases) > 0: + if not self.hasInterfaceObject(): + raise WebIDLError("Interface %s unexpectedly has [LegacyWindowAlias] " + "and [NoInterfaceObject] together" % self.identifier.name, + [self.location]) + if not self.isExposedInWindow(): + raise WebIDLError("Interface %s has [LegacyWindowAlias] " + "but not exposed in Window" % self.identifier.name, + [self.location]) # Generate maplike/setlike interface members. Since generated members # need to be treated like regular interface members, do this before @@ -788,19 +970,6 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): # our required methods in Codegen. Generate members now. self.maplikeOrSetlikeOrIterable.expand(self.members, self.isJSImplemented()) - # Now that we've merged in our partial interfaces, set the - # _exposureGlobalNames on any members that don't have it set yet. Note - # that any partial interfaces that had [Exposed] set have already set up - # _exposureGlobalNames on all the members coming from them, so this is - # just implementing the "members default to interface that defined them" - # and "partial interfaces default to interface they're a partial for" - # rules from the spec. - for m in self.members: - # If m, or the partial interface m came from, had [Exposed] - # specified, it already has a nonempty exposure global names set. - if len(m._exposureGlobalNames) == 0: - m._exposureGlobalNames.update(self._exposureGlobalNames) - assert not self.parent or isinstance(self.parent, IDLIdentifierPlaceholder) parent = self.parent.finish(scope) if self.parent else None if parent and isinstance(parent, IDLExternalInterface): @@ -809,7 +978,11 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): (self.identifier.name, self.parent.identifier.name), [self.location]) - assert not parent or isinstance(parent, IDLInterface) + if parent and not isinstance(parent, IDLInterface): + raise WebIDLError("%s inherits from %s which is not an interface " % + (self.identifier.name, + self.parent.identifier.name), + [self.location, parent.location]) self.parent = parent @@ -835,10 +1008,8 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): self.totalMembersInSlots = self.parent.totalMembersInSlots - # Interfaces with [Global] or [PrimaryGlobal] must not - # have anything inherit from them - if (self.parent.getExtendedAttribute("Global") or - self.parent.getExtendedAttribute("PrimaryGlobal")): + # Interfaces with [Global] must not have anything inherit from them + if self.parent.getExtendedAttribute("Global"): # Note: This is not a self.parent.isOnGlobalProtoChain() check # because ancestors of a [Global] interface can have other # descendants. @@ -854,10 +1025,8 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): self.parent.identifier.name), [self.location, self.parent.location]) - # Callbacks must not inherit from non-callbacks or inherit from - # anything that has consequential interfaces. + # Callbacks must not inherit from non-callbacks. # XXXbz Can non-callbacks inherit from callbacks? Spec issue pending. - # XXXbz Can callbacks have consequential interfaces? Spec issue pending if self.isCallback(): if not self.parent.isCallback(): raise WebIDLError("Callback interface %s inheriting from " @@ -894,48 +1063,40 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): self.parent.identifier.name), [self.location, self.parent.location]) - for iface in self.implementedInterfaces: - iface.finish(scope) + for mixin in self.includedMixins: + mixin.finish(scope) cycleInGraph = self.findInterfaceLoopPoint(self) if cycleInGraph: - raise WebIDLError("Interface %s has itself as ancestor or " - "implemented interface" % self.identifier.name, - [self.location, cycleInGraph.location]) - - if self.isCallback(): - # "implements" should have made sure we have no - # consequential interfaces. - assert len(self.getConsequentialInterfaces()) == 0 - # And that we're not consequential. - assert not self.isConsequential() - - # Now resolve() and finish() our members before importing the - # ones from our implemented interfaces. - - # resolve() will modify self.members, so we need to iterate - # over a copy of the member list here. - for member in list(self.members): - member.resolve(self) + raise WebIDLError( + "Interface %s has itself as ancestor" % self.identifier.name, + [self.location, cycleInGraph.location]) - for member in self.members: - member.finish(scope) - - # Now that we've finished our members, which has updated their exposure - # sets, make sure they aren't exposed in places where we are not. - for member in self.members: - if not member.exposureSet.issubset(self.exposureSet): - raise WebIDLError("Interface member has larger exposure set " - "than the interface itself", - [member.location, self.location]) + self.finishMembers(scope) ctor = self.ctor() if ctor is not None: - assert len(ctor._exposureGlobalNames) == 0 + if not self.hasInterfaceObject(): + raise WebIDLError( + "Can't have both a constructor and [NoInterfaceObject]", + [self.location, ctor.location]) + + if self.globalNames: + raise WebIDLError( + "Can't have both a constructor and [Global]", + [self.location, ctor.location]) + + assert(ctor._exposureGlobalNames == self._exposureGlobalNames) ctor._exposureGlobalNames.update(self._exposureGlobalNames) - ctor.finish(scope) + # Remove the constructor operation from our member list so + # it doesn't get in the way later. + self.members.remove(ctor) for ctor in self.namedConstructors: + if self.globalNames: + raise WebIDLError( + "Can't have both a named constructor and [Global]", + [self.location, ctor.location]) assert len(ctor._exposureGlobalNames) == 0 ctor._exposureGlobalNames.update(self._exposureGlobalNames) ctor.finish(scope) @@ -945,41 +1106,16 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): # admixed. self.originalMembers = list(self.members) - # Import everything from our consequential interfaces into - # self.members. Sort our consequential interfaces by name - # just so we have a consistent order. - for iface in sorted(self.getConsequentialInterfaces(), - cmp=cmp, + for mixin in sorted(self.includedMixins, key=lambda x: x.identifier.name): - # Flag the interface as being someone's consequential interface - iface.setIsConsequentialInterfaceOf(self) - # Verify that we're not exposed somewhere where iface is not exposed - if not self.exposureSet.issubset(iface.exposureSet): - raise WebIDLError("Interface %s is exposed in globals where its " - "consequential interface %s is not exposed." % - (self.identifier.name, iface.identifier.name), - [self.location, iface.location]) - - # If we have a maplike or setlike, and the consequential interface - # also does, throw an error. - if iface.maplikeOrSetlikeOrIterable and self.maplikeOrSetlikeOrIterable: - raise WebIDLError("Maplike/setlike/iterable interface %s cannot have " - "maplike/setlike/iterable interface %s as a " - "consequential interface" % - (self.identifier.name, - iface.identifier.name), - [self.maplikeOrSetlikeOrIterable.location, - iface.maplikeOrSetlikeOrIterable.location]) - additionalMembers = iface.originalMembers - for additionalMember in additionalMembers: + for mixinMember in mixin.members: for member in self.members: - if additionalMember.identifier.name == member.identifier.name: + if mixinMember.identifier.name == member.identifier.name: raise WebIDLError( - "Multiple definitions of %s on %s coming from 'implements' statements" % + "Multiple definitions of %s on %s coming from 'includes' statements" % (member.identifier.name, self), - [additionalMember.location, member.location]) - self.members.extend(additionalMembers) - iface.interfacesImplementingSelf.add(self) + [mixinMember.location, member.location]) + self.members.extend(mixin.members) for ancestor in self.getInheritedInterfaces(): ancestor.interfacesBasedOnSelf.add(self) @@ -992,8 +1128,6 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): ancestor.identifier.name), [self.maplikeOrSetlikeOrIterable.location, ancestor.maplikeOrSetlikeOrIterable.location]) - for ancestorConsequential in ancestor.getConsequentialInterfaces(): - ancestorConsequential.interfacesBasedOnSelf.add(self) # Deal with interfaces marked [Unforgeable], now that we have our full # member list, except unforgeables pulled in from parents. We want to @@ -1009,10 +1143,9 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): [self.location]) for m in self.members: - if ((m.isMethod() and m.isJsonifier()) or - m.identifier.name == "toJSON"): + if m.identifier.name == "toJSON": raise WebIDLError("Unforgeable interface %s has a " - "jsonifier so we won't be able to add " + "toJSON so we won't be able to add " "one ourselves" % self.identifier.name, [self.location, m.location]) @@ -1028,6 +1161,21 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): not hasattr(member, "originatingInterface")): member.originatingInterface = self + for member in self.members: + if ((member.isMethod() and + member.getExtendedAttribute("CrossOriginCallable")) or + (member.isAttr() and + (member.getExtendedAttribute("CrossOriginReadable") or + member.getExtendedAttribute("CrossOriginWritable")))): + self.hasCrossOriginMembers = True + break + + if self.hasCrossOriginMembers: + parent = self + while parent: + parent.hasDescendantWithCrossOriginMembers = True + parent = parent.parent + # Compute slot indices for our members before we pull in unforgeable # members from our parent. Also, maplike/setlike declarations get a # slot to hold their backing object. @@ -1036,6 +1184,12 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): (member.getExtendedAttribute("StoreInSlot") or member.getExtendedAttribute("Cached"))) or member.isMaplikeOrSetlike()): + if self.isJSImplemented() and not member.isMaplikeOrSetlike(): + raise WebIDLError("Interface %s is JS-implemented and we " + "don't support [Cached] or [StoreInSlot] " + "on JS-implemented interfaces" % + self.identifier.name, + [self.location, member.location]) if member.slotIndices is None: member.slotIndices = dict() member.slotIndices[self.identifier.name] = self.totalMembersInSlots @@ -1044,13 +1198,12 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): self._ownMembersInSlots += 1 if self.parent: - # Make sure we don't shadow any of the [Unforgeable] attributes on - # our ancestor interfaces. We don't have to worry about - # consequential interfaces here, because those have already been - # imported into the relevant .members lists. And we don't have to - # worry about anything other than our parent, because it has already - # imported its ancestors unforgeable attributes into its member - # list. + # Make sure we don't shadow any of the [Unforgeable] attributes on our + # ancestor interfaces. We don't have to worry about mixins here, because + # those have already been imported into the relevant .members lists. And + # we don't have to worry about anything other than our parent, because it + # has already imported its ancestors' unforgeable attributes into its + # member list. for unforgeableMember in (member for member in self.parent.members if (member.isAttr() or member.isMethod()) and member.isUnforgeable()): @@ -1087,10 +1240,13 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): testInterface = testInterface.parent # Ensure that there's at most one of each {named,indexed} - # {getter,setter,creator,deleter}, at most one stringifier, + # {getter,setter,deleter}, at most one stringifier, # and at most one legacycaller. Note that this last is not # quite per spec, but in practice no one overloads - # legacycallers. + # legacycallers. Also note that in practice we disallow + # indexed deleters, but it simplifies some other code to + # treat deleter analogously to getter/setter by + # prefixing it with "named". specialMembersSeen = {} for member in self.members: if not member.isMethod(): @@ -1100,21 +1256,16 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): memberType = "getters" elif member.isSetter(): memberType = "setters" - elif member.isCreator(): - memberType = "creators" elif member.isDeleter(): memberType = "deleters" elif member.isStringifier(): memberType = "stringifiers" - elif member.isJsonifier(): - memberType = "jsonifiers" elif member.isLegacycaller(): memberType = "legacycallers" else: continue - if (memberType != "stringifiers" and memberType != "legacycallers" and - memberType != "jsonifiers"): + if (memberType != "stringifiers" and memberType != "legacycallers"): if member.isNamed(): memberType = "named " + memberType else: @@ -1147,8 +1298,8 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): ancestor = ancestor.parent if self._isOnGlobalProtoChain: - # Make sure we have no named setters, creators, or deleters - for memberType in ["setter", "creator", "deleter"]: + # Make sure we have no named setters or deleters + for memberType in ["setter", "deleter"]: memberId = "named " + memberType + "s" if memberId in specialMembersSeen: raise WebIDLError("Interface with [Global] has a named %s" % @@ -1172,16 +1323,21 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): parent = parent.parent def validate(self): - # We don't support consequential unforgeable interfaces. Need to check - # this here, because in finish() an interface might not know yet that - # it's consequential. - if self.getExtendedAttribute("Unforgeable") and self.isConsequential(): - raise WebIDLError( - "%s is an unforgeable consequential interface" % - self.identifier.name, - [self.location] + - list(i.location for i in - (self.interfacesBasedOnSelf - {self}))) + + def checkDuplicateNames(member, name, attributeName): + for m in self.members: + if m.identifier.name == name: + raise WebIDLError("[%s=%s] has same name as interface member" % + (attributeName, name), + [member.location, m.location]) + if m.isMethod() and m != member and name in m.aliases: + raise WebIDLError("conflicting [%s=%s] definitions" % + (attributeName, name), + [member.location, m.location]) + if m.isAttr() and m != member and name in m.bindingAliases: + raise WebIDLError("conflicting [%s=%s] definitions" % + (attributeName, name), + [member.location, m.location]) # We also don't support inheriting from unforgeable interfaces. if self.getExtendedAttribute("Unforgeable") and self.hasChildInterfaces(): @@ -1192,6 +1348,12 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): self.identifier.name, locations) + ctor = self.ctor() + if ctor is not None: + ctor.validate() + for namedCtor in self.namedConstructors: + namedCtor.validate() + indexedGetter = None hasLengthAttribute = False for member in self.members: @@ -1278,23 +1440,22 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): raise WebIDLError("[Alias] must not be used on an " "[Unforgeable] operation", [member.location]) - for m in self.members: - if m.identifier.name == alias: - raise WebIDLError("[Alias=%s] has same name as " - "interface member" % alias, - [member.location, m.location]) - if m.isMethod() and m != member and alias in m.aliases: - raise WebIDLError("duplicate [Alias=%s] definitions" % - alias, - [member.location, m.location]) + + checkDuplicateNames(member, alias, "Alias") + + # Check that the name of a [BindingAlias] doesn't conflict with an + # interface member. + if member.isAttr(): + for bindingAlias in member.bindingAliases: + checkDuplicateNames(member, bindingAlias, "BindingAlias") + # Conditional exposure makes no sense for interfaces with no - # interface object, unless they're navigator properties. + # interface object. # And SecureContext makes sense for interfaces with no interface object, # since it is also propagated to interface members. if (self.isExposedConditionally(exclusions=["SecureContext"]) and - not self.hasInterfaceObject() and - not self.isNavigatorProperty()): + not self.hasInterfaceObject()): raise WebIDLError("Interface with no interface object is " "exposed conditionally", [self.location]) @@ -1308,7 +1469,7 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): if not indexedGetter: raise WebIDLError("Interface with value iterator does not " "support indexed properties", - [self.location]) + [self.location, iterableDecl.location]) if iterableDecl.valueType != indexedGetter.signatures()[0][0]: raise WebIDLError("Iterable type does not match indexed " @@ -1319,7 +1480,7 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): if not hasLengthAttribute: raise WebIDLError('Interface with value iterator does not ' 'have an integer-typed "length" attribute', - [self.location]) + [self.location, iterableDecl.location]) else: assert iterableDecl.isPairIterator() if indexedGetter: @@ -1328,15 +1489,10 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): [self.location, iterableDecl.location, indexedGetter.location]) - def isExternal(self): - return False - - def setIsConsequentialInterfaceOf(self, other): - self._consequential = True - self.interfacesBasedOnSelf.add(other) - - def isConsequential(self): - return self._consequential + if indexedGetter and not hasLengthAttribute: + raise WebIDLError('Interface with an indexed getter does not have ' + 'an integer-typed "length" attribute', + [self.location, indexedGetter.location]) def setCallback(self, value): self._callback = value @@ -1352,8 +1508,6 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): not self.isJSImplemented() and # Not inheriting from another interface not self.parent and - # No consequential interfaces - len(self.getConsequentialInterfaces()) == 0 and # No attributes of any kinds not any(m.isAttr() for m in self.members) and # There is at least one regular operation, and all regular @@ -1381,9 +1535,9 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): return (not self.isCallback() and not self.isNamespace() and self.getUserData('hasConcreteDescendant', False)) - def addImplementedInterface(self, implementedInterface): - assert(isinstance(implementedInterface, IDLInterface)) - self.implementedInterfaces.add(implementedInterface) + def addIncludedMixin(self, includedMixin): + assert(isinstance(includedMixin, IDLInterfaceMixin)) + self.includedMixins.add(includedMixin) def getInheritedInterfaces(self): """ @@ -1398,27 +1552,10 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): parentInterfaces.insert(0, self.parent) return parentInterfaces - def getConsequentialInterfaces(self): - assert(self._finished) - # The interfaces we implement directly - consequentialInterfaces = set(self.implementedInterfaces) - - # And their inherited interfaces - for iface in self.implementedInterfaces: - consequentialInterfaces |= set(iface.getInheritedInterfaces()) - - # And now collect up the consequential interfaces of all of those - temp = set() - for iface in consequentialInterfaces: - temp |= iface.getConsequentialInterfaces() - - return consequentialInterfaces | temp - def findInterfaceLoopPoint(self, otherInterface): """ - Finds an interface, amongst our ancestors and consequential interfaces, - that inherits from otherInterface or implements otherInterface - directly. If there is no such interface, returns None. + Finds an interface amongst our ancestors that inherits from otherInterface. + If there is no such interface, returns None. """ if self.parent: if self.parent == otherInterface: @@ -1426,37 +1563,13 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): loopPoint = self.parent.findInterfaceLoopPoint(otherInterface) if loopPoint: return loopPoint - if otherInterface in self.implementedInterfaces: - return self - for iface in self.implementedInterfaces: - loopPoint = iface.findInterfaceLoopPoint(otherInterface) - if loopPoint: - return loopPoint return None - def getExtendedAttribute(self, name): - return self._extendedAttrDict.get(name, None) - def setNonPartial(self, location, parent, members): assert not parent or isinstance(parent, IDLIdentifierPlaceholder) - if self._isKnownNonPartial: - raise WebIDLError("Two non-partial definitions for the " - "same %s" % - ("interface" if self.isInterface() - else "namespace"), - [location, self.location]) - self._isKnownNonPartial = True - # Now make it look like we were parsed at this new location, since - # that's the place where the interface is "really" defined - self.location = location + IDLInterfaceOrInterfaceMixinOrNamespace.setNonPartial(self, location, members) assert not self.parent self.parent = parent - # Put the new members at the beginning - self.members = members + self.members - - def addPartialInterface(self, partial): - assert self.identifier.name == partial.identifier.name - self._partialInterfaces.append(partial) def getJSImplementation(self): classId = self.getExtendedAttribute("JSImplementation") @@ -1469,47 +1582,14 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): def isJSImplemented(self): return bool(self.getJSImplementation()) - def isProbablyShortLivingObject(self): + def hasProbablyShortLivingWrapper(self): current = self while current: - if current.getExtendedAttribute("ProbablyShortLivingObject"): + if current.getExtendedAttribute("ProbablyShortLivingWrapper"): return True current = current.parent return False - def isNavigatorProperty(self): - naviProp = self.getExtendedAttribute("NavigatorProperty") - if not naviProp: - return False - assert len(naviProp) == 1 - assert isinstance(naviProp, list) - assert len(naviProp[0]) != 0 - return True - - def getNavigatorProperty(self): - naviProp = self.getExtendedAttribute("NavigatorProperty") - if not naviProp: - return None - assert len(naviProp) == 1 - assert isinstance(naviProp, list) - assert len(naviProp[0]) != 0 - conditionExtendedAttributes = self._extendedAttrDict.viewkeys() & IDLInterfaceOrNamespace.conditionExtendedAttributes - attr = IDLAttribute(self.location, - IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), naviProp[0]), - IDLUnresolvedType(self.location, IDLUnresolvedIdentifier(self.location, self.identifier.name)), - True, - extendedAttrDict={ a: self._extendedAttrDict[a] for a in conditionExtendedAttributes }, - navigatorObjectGetter=True) - attr._exposureGlobalNames = self._exposureGlobalNames - # We're abusing Constant a little bit here, because we need Cached. The - # getter will create a new object every time, but we're never going to - # clear the cached value. - extendedAttrs = [ IDLExtendedAttribute(self.location, ("Throws", )), - IDLExtendedAttribute(self.location, ("Cached", )), - IDLExtendedAttribute(self.location, ("Constant", )) ] - attr.addExtendedAttributes(extendedAttrs) - return attr - def hasChildInterfaces(self): return self._hasChildInterfaces @@ -1518,7 +1598,7 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): def _getDependentObjects(self): deps = set(self.members) - deps.update(self.implementedInterfaces) + deps.update(self.includedMixins) if self.parent: deps.add(self.parent) return deps @@ -1526,18 +1606,19 @@ class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): def hasMembersInSlots(self): return self._ownMembersInSlots != 0 - conditionExtendedAttributes = [ "Pref", "ChromeOnly", "Func", "AvailableIn", - "SecureContext", - "CheckAnyPermissions", - "CheckAllPermissions" ] + conditionExtendedAttributes = [ "Pref", "ChromeOnly", "Func", + "SecureContext" ] def isExposedConditionally(self, exclusions=[]): return any(((not a in exclusions) and self.getExtendedAttribute(a)) for a in self.conditionExtendedAttributes) class IDLInterface(IDLInterfaceOrNamespace): def __init__(self, location, parentScope, name, parent, members, - isKnownNonPartial): + isKnownNonPartial, classNameOverride=None, + toStringTag=None): IDLInterfaceOrNamespace.__init__(self, location, parentScope, name, - parent, members, isKnownNonPartial) + parent, members, isKnownNonPartial, + toStringTag) + self.classNameOverride = classNameOverride def __str__(self): return "Interface '%s'" % self.identifier.name @@ -1545,6 +1626,11 @@ class IDLInterface(IDLInterfaceOrNamespace): def isInterface(self): return True + def getClassName(self): + if self.classNameOverride: + return self.classNameOverride + return self.identifier.name + def addExtendedAttributes(self, attrs): for attr in attrs: identifier = attr.identifier() @@ -1561,86 +1647,46 @@ class IDLInterface(IDLInterfaceOrNamespace): raise WebIDLError("[NoInterfaceObject] must take no arguments", [attr.location]) - if self.ctor(): - raise WebIDLError("Constructor and NoInterfaceObject are incompatible", - [self.location]) - self._noInterfaceObject = True - elif identifier == "Constructor" or identifier == "NamedConstructor" or identifier == "ChromeConstructor": - if identifier == "Constructor" and not self.hasInterfaceObject(): - raise WebIDLError(str(identifier) + " and NoInterfaceObject are incompatible", - [self.location]) - - if identifier == "NamedConstructor" and not attr.hasValue(): + elif identifier == "NamedConstructor": + if not attr.hasValue(): raise WebIDLError("NamedConstructor must either take an identifier or take a named argument list", [attr.location]) - if identifier == "ChromeConstructor" and not self.hasInterfaceObject(): - raise WebIDLError(str(identifier) + " and NoInterfaceObject are incompatible", - [self.location]) args = attr.args() if attr.hasArgs() else [] - if self.identifier.name == "Promise": - promiseType = BuiltinTypes[IDLBuiltinType.Types.any] - else: - promiseType = None - retType = IDLWrapperType(self.location, self, promiseType) + retType = IDLWrapperType(self.location, self) - if identifier == "Constructor" or identifier == "ChromeConstructor": - name = "constructor" - allowForbidden = True - else: - name = attr.value() - allowForbidden = False - - methodIdentifier = IDLUnresolvedIdentifier(self.location, name, - allowForbidden=allowForbidden) - - method = IDLMethod(self.location, methodIdentifier, retType, - args, static=True) - # Constructors are always NewObject and are always - # assumed to be able to throw (since there's no way to - # indicate otherwise) and never have any other - # extended attributes. + method = IDLConstructor(attr.location, args, attr.value()) + method.reallyInit(self) + + # Named constructors are always assumed to be able to + # throw (since there's no way to indicate otherwise). method.addExtendedAttributes( - [IDLExtendedAttribute(self.location, ("NewObject",)), - IDLExtendedAttribute(self.location, ("Throws",))]) - if identifier == "ChromeConstructor": - method.addExtendedAttributes( - [IDLExtendedAttribute(self.location, ("ChromeOnly",))]) - - if identifier == "Constructor" or identifier == "ChromeConstructor": - method.resolve(self) - else: - # We need to detect conflicts for NamedConstructors across - # interfaces. We first call resolve on the parentScope, - # which will merge all NamedConstructors with the same - # identifier accross interfaces as overloads. - method.resolve(self.parentScope) - - # Then we look up the identifier on the parentScope. If the - # result is the same as the method we're adding then it - # hasn't been added as an overload and it's the first time - # we've encountered a NamedConstructor with that identifier. - # If the result is not the same as the method we're adding - # then it has been added as an overload and we need to check - # whether the result is actually one of our existing - # NamedConstructors. - newMethod = self.parentScope.lookupIdentifier(method.identifier) - if newMethod == method: - self.namedConstructors.append(method) - elif newMethod not in self.namedConstructors: - raise WebIDLError("NamedConstructor conflicts with a NamedConstructor of a different interface", - [method.location, newMethod.location]) - elif (identifier == "ArrayClass"): - if not attr.noArguments(): - raise WebIDLError("[ArrayClass] must take no arguments", - [attr.location]) - if self.parent: - raise WebIDLError("[ArrayClass] must not be specified on " - "an interface with inherited interfaces", - [attr.location, self.location]) + [IDLExtendedAttribute(self.location, ("Throws",))]) + + # We need to detect conflicts for NamedConstructors across + # interfaces. We first call resolve on the parentScope, + # which will merge all NamedConstructors with the same + # identifier accross interfaces as overloads. + method.resolve(self.parentScope) + + # Then we look up the identifier on the parentScope. If the + # result is the same as the method we're adding then it + # hasn't been added as an overload and it's the first time + # we've encountered a NamedConstructor with that identifier. + # If the result is not the same as the method we're adding + # then it has been added as an overload and we need to check + # whether the result is actually one of our existing + # NamedConstructors. + newMethod = self.parentScope.lookupIdentifier(method.identifier) + if newMethod == method: + self.namedConstructors.append(method) + elif newMethod not in self.namedConstructors: + raise WebIDLError("NamedConstructor conflicts with a " + "NamedConstructor of a different interface", + [method.location, newMethod.location]) elif (identifier == "ExceptionClass"): if not attr.noArguments(): raise WebIDLError("[ExceptionClass] must take no arguments", @@ -1656,24 +1702,21 @@ class IDLInterface(IDLInterfaceOrNamespace): self.globalNames = attr.args() else: self.globalNames = [self.identifier.name] - self.parentScope.globalNames.update(self.globalNames) - for globalName in self.globalNames: - self.parentScope.globalNameMapping[globalName].add(self.identifier.name) + self.parentScope.addIfaceGlobalNames(self.identifier.name, + self.globalNames) self._isOnGlobalProtoChain = True - elif identifier == "PrimaryGlobal": - if not attr.noArguments(): - raise WebIDLError("[PrimaryGlobal] must take no arguments", + elif identifier == "LegacyWindowAlias": + if attr.hasValue(): + self.legacyWindowAliases = [attr.value()] + elif attr.hasArgs(): + self.legacyWindowAliases = attr.args() + else: + raise WebIDLError("[%s] must either take an identifier " + "or take an identifier list" % identifier, [attr.location]) - if self.parentScope.primaryGlobalAttr is not None: - raise WebIDLError( - "[PrimaryGlobal] specified twice", - [attr.location, - self.parentScope.primaryGlobalAttr.location]) - self.parentScope.primaryGlobalAttr = attr - self.parentScope.primaryGlobalName = self.identifier.name - self.parentScope.globalNames.add(self.identifier.name) - self.parentScope.globalNameMapping[self.identifier.name].add(self.identifier.name) - self._isOnGlobalProtoChain = True + for alias in self.legacyWindowAliases: + unresolved = IDLUnresolvedIdentifier(attr.location, alias) + IDLObjectWithIdentifier(attr.location, self.parentScope, unresolved) elif identifier == "SecureContext": if not attr.noArguments(): raise WebIDLError("[%s] must take no arguments" % identifier, @@ -1690,11 +1733,12 @@ class IDLInterface(IDLInterfaceOrNamespace): identifier == "OverrideBuiltins" or identifier == "ChromeOnly" or identifier == "Unforgeable" or - identifier == "UnsafeInPrerendering" or identifier == "LegacyEventInit" or - identifier == "ProbablyShortLivingObject" or + identifier == "ProbablyShortLivingWrapper" or identifier == "LegacyUnenumerableNamedProperties" or - identifier == "NonOrdinaryGetPrototypeOf" or + identifier == "RunConstructorInCallerCompartment" or + identifier == "WantsEventListenerHooks" or + identifier == "Serializable" or identifier == "Abstract" or identifier == "Inline"): # Known extended attributes that do not take values @@ -1707,13 +1751,17 @@ class IDLInterface(IDLInterfaceOrNamespace): elif (identifier == "Pref" or identifier == "JSImplementation" or identifier == "HeaderFile" or - identifier == "NavigatorProperty" or identifier == "Func" or identifier == "Deprecated"): # Known extended attributes that take a string value if not attr.hasValue(): raise WebIDLError("[%s] must have a value" % identifier, [attr.location]) + elif identifier == "InstrumentedProps": + # Known extended attributes that take a list + if not attr.hasArgs(): + raise WebIDLError("[%s] must have arguments" % identifier, + [attr.location]) else: raise WebIDLError("Unknown extended attribute %s on interface" % identifier, [attr.location]) @@ -1721,11 +1769,36 @@ class IDLInterface(IDLInterfaceOrNamespace): attrlist = attr.listValue() self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True + def validate(self): + IDLInterfaceOrNamespace.validate(self) + if self.parent and self.isSerializable() and not self.parent.isSerializable(): + raise WebIDLError( + "Serializable interface inherits from non-serializable " + "interface. Per spec, that means the object should not be " + "serializable, so chances are someone made a mistake here " + "somewhere.", + [self.location, self.parent.location]) + + def isSerializable(self): + return self.getExtendedAttribute("Serializable") + + def setNonPartial(self, location, parent, members): + # Before we do anything else, finish initializing any constructors that + # might be in "members", so we don't have partially-initialized objects + # hanging around. We couldn't do it before now because we needed to have + # to have the IDLInterface on hand to properly set the return type. + for member in members: + if isinstance(member, IDLConstructor): + member.reallyInit(self) + + IDLInterfaceOrNamespace.setNonPartial(self, location, parent, members) + class IDLNamespace(IDLInterfaceOrNamespace): def __init__(self, location, parentScope, name, members, isKnownNonPartial): IDLInterfaceOrNamespace.__init__(self, location, parentScope, name, - None, members, isKnownNonPartial) + None, members, isKnownNonPartial, + toStringTag=None) def __str__(self): return "Namespace '%s'" % self.identifier.name @@ -1750,10 +1823,18 @@ class IDLNamespace(IDLInterfaceOrNamespace): if not attr.hasValue(): raise WebIDLError("[%s] must have a value" % identifier, [attr.location]) - elif identifier == "ProtoObjectHack": + elif (identifier == "ProtoObjectHack" or + identifier == "ChromeOnly"): if not attr.noArguments(): raise WebIDLError("[%s] must not have arguments" % identifier, [attr.location]) + elif (identifier == "Pref" or + identifier == "HeaderFile" or + identifier == "Func"): + # Known extended attributes that take a string value + if not attr.hasValue(): + raise WebIDLError("[%s] must have a value" % identifier, + [attr.location]) else: raise WebIDLError("Unknown extended attribute %s on namespace" % identifier, @@ -1762,6 +1843,9 @@ class IDLNamespace(IDLInterfaceOrNamespace): attrlist = attr.listValue() self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True + def isSerializable(self): + return False + class IDLDictionary(IDLObjectWithScope): def __init__(self, location, parentScope, name, parent, members): @@ -1772,6 +1856,10 @@ class IDLDictionary(IDLObjectWithScope): self.parent = parent self._finished = False self.members = list(members) + self._partialDictionaries = [] + self._extendedAttrDict = {} + self.needsConversionToJS = False + self.needsConversionFromJS = False IDLObjectWithScope.__init__(self, location, parentScope, name) @@ -1808,6 +1896,11 @@ class IDLDictionary(IDLObjectWithScope): # looking at them. self.parent.finish(scope) + # Now go ahead and merge in our partial dictionaries. + for partial in self._partialDictionaries: + partial.finish(scope) + self.members.extend(partial.members) + for member in self.members: member.resolve(self) if not member.isComplete(): @@ -1815,7 +1908,7 @@ class IDLDictionary(IDLObjectWithScope): assert member.type.isComplete() # Members of a dictionary are sorted in lexicographic order - self.members.sort(cmp=cmp, key=lambda x: x.identifier.name) + self.members.sort(key=lambda x: x.identifier.name) inheritedMembers = [] ancestor = self.parent @@ -1853,7 +1946,7 @@ class IDLDictionary(IDLObjectWithScope): if (memberType.nullable() or memberType.isSequence() or - memberType.isMozMap()): + memberType.isRecord()): return typeContainsDictionary(memberType.inner, dictionary) if memberType.isDictionary(): @@ -1900,8 +1993,34 @@ class IDLDictionary(IDLObjectWithScope): self.identifier.name, [member.location] + locations) + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + def addExtendedAttributes(self, attrs): - assert len(attrs) == 0 + for attr in attrs: + identifier = attr.identifier() + + if (identifier == "GenerateInitFromJSON" or + identifier == "GenerateInit"): + if not attr.noArguments(): + raise WebIDLError("[%s] must not have arguments" % identifier, + [attr.location]) + self.needsConversionFromJS = True + elif (identifier == "GenerateConversionToJS" or + identifier == "GenerateToJSON"): + if not attr.noArguments(): + raise WebIDLError("[%s] must not have arguments" % identifier, + [attr.location]) + # ToJSON methods require to-JS conversion, because we + # implement ToJSON by converting to a JS object and + # then using JSON.stringify. + self.needsConversionToJS = True + else: + raise WebIDLError("[%s] extended attribute not allowed on " + "dictionaries" % identifier, + [attr.location]) + + self._extendedAttrDict[identifier] = True def _getDependentObjects(self): deps = set(self.members) @@ -1909,6 +2028,10 @@ class IDLDictionary(IDLObjectWithScope): deps.add(self.parent) return deps + def addPartialDictionary(self, partial): + assert self.identifier.name == partial.identifier.name + self._partialDictionaries.append(partial) + class IDLEnum(IDLObjectWithIdentifier): def __init__(self, location, parentScope, name, values): @@ -1935,7 +2058,10 @@ class IDLEnum(IDLObjectWithIdentifier): return True def addExtendedAttributes(self, attrs): - assert len(attrs) == 0 + if len(attrs) != 0: + raise WebIDLError("There are no extended attributes that are " + "allowed on enums", + [attrs[0].location, self.location]) def _getDependentObjects(self): return set() @@ -1964,8 +2090,9 @@ class IDLType(IDLObject): 'domstring', 'bytestring', 'usvstring', + 'utf8string', + 'jsstring', 'object', - 'date', 'void', # Funny stuff 'interface', @@ -1974,16 +2101,25 @@ class IDLType(IDLObject): 'callback', 'union', 'sequence', - 'mozmap' + 'record', + 'promise', ) def __init__(self, location, name): IDLObject.__init__(self, location) self.name = name self.builtin = False + self.treatNullAsEmpty = False + self._clamp = False + self._enforceRange = False + self._allowShared = False + self._extendedAttrDict = {} def __eq__(self, other): - return other and self.builtin == other.builtin and self.name == other.name + return (other and self.builtin == other.builtin and self.name == other.name and + self._clamp == other.hasClamp() and self._enforceRange == other.hasEnforceRange() and + self.treatNullAsEmpty == other.treatNullAsEmpty and + self._allowShared == other.hasAllowShared()) def __ne__(self, other): return not self == other @@ -1991,6 +2127,14 @@ class IDLType(IDLObject): def __str__(self): return str(self.name) + def prettyName(self): + """ + A name that looks like what this type is named in the IDL spec. By default + this is just our .name, but types that have more interesting spec + representations should override this. + """ + return str(self.name) + def isType(self): return True @@ -2018,27 +2162,36 @@ class IDLType(IDLObject): def isUSVString(self): return False + def isUTF8String(self): + return False + + def isJSString(self): + return False + def isVoid(self): return self.name == "Void" def isSequence(self): return False - def isMozMap(self): + def isRecord(self): return False - def isArrayBuffer(self): + def isReadableStream(self): return False - def isArrayBufferView(self): + def isArrayBuffer(self): return False - def isSharedArrayBuffer(self): + def isArrayBufferView(self): return False def isTypedArray(self): return False + def isBufferSource(self): + return self.isArrayBuffer() or self.isArrayBufferView() or self.isTypedArray() + def isCallbackInterface(self): return False @@ -2054,12 +2207,9 @@ class IDLType(IDLObject): def isSpiderMonkeyInterface(self): """ Returns a boolean indicating whether this type is an 'interface' - type that is implemented in Spidermonkey. At the moment, this - only returns true for the types from the TypedArray spec. """ - return self.isInterface() and (self.isArrayBuffer() or - self.isArrayBufferView() or - self.isSharedArrayBuffer() or - self.isTypedArray()) + type that is implemented in SpiderMonkey. """ + return self.isInterface() and (self.isBufferSource() or + self.isReadableStream()) def isDictionary(self): return False @@ -2070,9 +2220,6 @@ class IDLType(IDLObject): def isAny(self): return self.tag() == IDLType.Tags.any - def isDate(self): - return self.tag() == IDLType.Tags.date - def isObject(self): return self.tag() == IDLType.Tags.object @@ -2092,9 +2239,18 @@ class IDLType(IDLObject): # Should only call this on float types assert self.isFloat() - def isSerializable(self): + def isJSONType(self): return False + def hasClamp(self): + return self._clamp + + def hasEnforceRange(self): + return self._enforceRange + + def hasAllowShared(self): + return self._allowShared + def tag(self): assert False # Override me! @@ -2106,8 +2262,14 @@ class IDLType(IDLObject): assert self.tag() == IDLType.Tags.callback return self.nullable() and self.inner.callback._treatNonObjectAsNull - def addExtendedAttributes(self, attrs): - assert len(attrs) == 0 + def withExtendedAttributes(self, attrs): + if len(attrs) > 0: + raise WebIDLError("Extended attributes on types only supported for builtins", + [attrs[0].location, self.location]) + return self + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) def resolveType(self, parentScope): pass @@ -2128,9 +2290,9 @@ class IDLUnresolvedType(IDLType): Unresolved types are interface types """ - def __init__(self, location, name, promiseInnerType=None): + def __init__(self, location, name, attrs=[]): IDLType.__init__(self, location, name) - self._promiseInnerType = promiseInnerType + self.extraTypeAttributes = attrs def isComplete(self): return False @@ -2145,30 +2307,30 @@ class IDLUnresolvedType(IDLType): assert obj if obj.isType(): - print obj + print(obj) assert not obj.isType() if obj.isTypedef(): assert self.name.name == obj.identifier.name typedefType = IDLTypedefType(self.location, obj.innerType, obj.identifier) assert not typedefType.isComplete() - return typedefType.complete(scope) + return typedefType.complete(scope).withExtendedAttributes(self.extraTypeAttributes) elif obj.isCallback() and not obj.isInterface(): assert self.name.name == obj.identifier.name return IDLCallbackType(obj.location, obj) - if self._promiseInnerType and not self._promiseInnerType.isComplete(): - self._promiseInnerType = self._promiseInnerType.complete(scope) - name = self.name.resolve(scope, None) - return IDLWrapperType(self.location, obj, self._promiseInnerType) + return IDLWrapperType(self.location, obj) + + def withExtendedAttributes(self, attrs): + return IDLUnresolvedType(self.location, self.name, attrs) def isDistinguishableFrom(self, other): raise TypeError("Can't tell whether an unresolved type is or is not " "distinguishable from other things") -class IDLParameterizedType(IDLType): +class IDLParametrizedType(IDLType): def __init__(self, location, name, innerType): IDLType.__init__(self, location, name) self.builtin = False @@ -2191,22 +2353,25 @@ class IDLParameterizedType(IDLType): return self.inner._getDependentObjects() -class IDLNullableType(IDLParameterizedType): +class IDLNullableType(IDLParametrizedType): def __init__(self, location, innerType): assert not innerType.isVoid() assert not innerType == BuiltinTypes[IDLBuiltinType.Types.any] - name = innerType.name - if innerType.isComplete(): - name += "OrNull" - IDLParameterizedType.__init__(self, location, name, innerType) + IDLParametrizedType.__init__(self, location, None, innerType) def __eq__(self, other): return isinstance(other, IDLNullableType) and self.inner == other.inner + def __hash__(self): + return hash(self.inner) + def __str__(self): return self.inner.__str__() + "OrNull" + def prettyName(self): + return self.inner.prettyName() + "?" + def nullable(self): return True @@ -2234,6 +2399,12 @@ class IDLNullableType(IDLParameterizedType): def isUSVString(self): return self.inner.isUSVString() + def isUTF8String(self): + return self.inner.isUTF8String() + + def isJSString(self): + return self.inner.isJSString() + def isFloat(self): return self.inner.isFloat() @@ -2249,8 +2420,11 @@ class IDLNullableType(IDLParameterizedType): def isSequence(self): return self.inner.isSequence() - def isMozMap(self): - return self.inner.isMozMap() + def isRecord(self): + return self.inner.isRecord() + + def isReadableStream(self): + return self.inner.isReadableStream() def isArrayBuffer(self): return self.inner.isArrayBuffer() @@ -2258,9 +2432,6 @@ class IDLNullableType(IDLParameterizedType): def isArrayBufferView(self): return self.inner.isArrayBufferView() - def isSharedArrayBuffer(self): - return self.inner.isSharedArrayBuffer() - def isTypedArray(self): return self.inner.isTypedArray() @@ -2271,7 +2442,9 @@ class IDLNullableType(IDLParameterizedType): return self.inner.isInterface() def isPromise(self): - return self.inner.isPromise() + # There is no such thing as a nullable Promise. + assert not self.inner.isPromise() + return False def isCallbackInterface(self): return self.inner.isCallbackInterface() @@ -2285,14 +2458,29 @@ class IDLNullableType(IDLParameterizedType): def isUnion(self): return self.inner.isUnion() - def isSerializable(self): - return self.inner.isSerializable() + def isJSONType(self): + return self.inner.isJSONType() + + def hasClamp(self): + return self.inner.hasClamp() + + def hasEnforceRange(self): + return self.inner.hasEnforceRange() + + def hasAllowShared(self): + return self.inner.hasAllowShared() + + def isComplete(self): + return self.name is not None def tag(self): return self.inner.tag() def complete(self, scope): - self.inner = self.inner.complete(scope) + if not self.inner.isComplete(): + self.inner = self.inner.complete(scope) + assert self.inner.isComplete() + if self.inner.nullable(): raise WebIDLError("The inner type of a nullable type must not be " "a nullable type", @@ -2302,23 +2490,36 @@ class IDLNullableType(IDLParameterizedType): raise WebIDLError("The inner type of a nullable type must not " "be a union type that itself has a nullable " "type as a member type", [self.location]) + if self.inner.isDOMString(): + if self.inner.treatNullAsEmpty: + raise WebIDLError("[TreatNullAs] not allowed on a nullable DOMString", + [self.location, self.inner.location]) self.name = self.inner.name + "OrNull" return self def isDistinguishableFrom(self, other): - if (other.nullable() or (other.isUnion() and other.hasNullableType) or - other.isDictionary()): + if (other.nullable() or + other.isDictionary() or + (other.isUnion() and + (other.hasNullableType or other.hasDictionaryType()))): # Can't tell which type null should become return False return self.inner.isDistinguishableFrom(other) + def withExtendedAttributes(self, attrs): + # See https://github.com/heycam/webidl/issues/827#issuecomment-565131350 + # Allowing extended attributes to apply to a nullable type is an intermediate solution. + # A potential longer term solution is to introduce a null type and get rid of nullables. + # For example, we could do `([Clamp] long or null) foo` in the future. + return IDLNullableType(self.location, self.inner.withExtendedAttributes(attrs)) -class IDLSequenceType(IDLParameterizedType): + +class IDLSequenceType(IDLParametrizedType): def __init__(self, location, parameterType): assert not parameterType.isVoid() - IDLParameterizedType.__init__(self, location, parameterType.name, parameterType) + IDLParametrizedType.__init__(self, location, parameterType.name, parameterType) # Need to set self.name up front if our inner type is already complete, # since in that case our .complete() won't be called. if self.inner.isComplete(): @@ -2327,9 +2528,15 @@ class IDLSequenceType(IDLParameterizedType): def __eq__(self, other): return isinstance(other, IDLSequenceType) and self.inner == other.inner + def __hash__(self): + return hash(self.inner) + def __str__(self): return self.inner.__str__() + "Sequence" + def prettyName(self): + return "sequence<%s>" % self.inner.prettyName() + def nullable(self): return False @@ -2348,6 +2555,12 @@ class IDLSequenceType(IDLParameterizedType): def isUSVString(self): return False + def isUTF8String(self): + return False + + def isJSString(self): + return False + def isVoid(self): return False @@ -2363,8 +2576,8 @@ class IDLSequenceType(IDLParameterizedType): def isEnum(self): return False - def isSerializable(self): - return self.inner.isSerializable() + def isJSONType(self): + return self.inner.isJSONType() def tag(self): return IDLType.Tags.sequence @@ -2381,36 +2594,45 @@ class IDLSequenceType(IDLParameterizedType): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) return (other.isPrimitive() or other.isString() or other.isEnum() or - other.isDate() or other.isInterface() or - other.isDictionary() or - other.isCallback() or other.isMozMap()) + other.isInterface() or other.isDictionary() or + other.isCallback() or other.isRecord()) -class IDLMozMapType(IDLParameterizedType): - def __init__(self, location, parameterType): - assert not parameterType.isVoid() +class IDLRecordType(IDLParametrizedType): + def __init__(self, location, keyType, valueType): + assert keyType.isString() + assert keyType.isComplete() + assert not valueType.isVoid() + + IDLParametrizedType.__init__(self, location, valueType.name, valueType) + self.keyType = keyType - IDLParameterizedType.__init__(self, location, parameterType.name, parameterType) # Need to set self.name up front if our inner type is already complete, # since in that case our .complete() won't be called. if self.inner.isComplete(): - self.name = self.inner.name + "MozMap" + self.name = self.keyType.name + self.inner.name + "Record" def __eq__(self, other): - return isinstance(other, IDLMozMapType) and self.inner == other.inner + return isinstance(other, IDLRecordType) and self.inner == other.inner def __str__(self): - return self.inner.__str__() + "MozMap" + return self.keyType.__str__() + self.inner.__str__() + "Record" - def isMozMap(self): + def prettyName(self): + return "record<%s, %s>" % (self.keyType.prettyName(), self.inner.prettyName()) + + def isRecord(self): return True + def isJSONType(self): + return self.inner.isJSONType() + def tag(self): - return IDLType.Tags.mozmap + return IDLType.Tags.record def complete(self, scope): self.inner = self.inner.complete(scope) - self.name = self.inner.name + "MozMap" + self.name = self.keyType.name + self.inner.name + "Record" return self def unroll(self): @@ -2426,7 +2648,7 @@ class IDLMozMapType(IDLParameterizedType): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) return (other.isPrimitive() or other.isString() or other.isEnum() or - other.isDate() or other.isNonCallbackInterface() or other.isSequence()) + other.isNonCallbackInterface() or other.isSequence()) def isExposedInAllOf(self, exposureSet): return self.inner.unroll().isExposedInAllOf(exposureSet) @@ -2448,14 +2670,17 @@ class IDLUnionType(IDLType): assert self.isComplete() return self.name.__hash__() + def prettyName(self): + return "(" + " or ".join(m.prettyName() for m in self.memberTypes) + ")" + def isVoid(self): return False def isUnion(self): return True - def isSerializable(self): - return all(m.isSerializable() for m in self.memberTypes) + def isJSONType(self): + return all(m.isJSONType() for m in self.memberTypes) def includesRestrictedFloat(self): return any(t.includesRestrictedFloat() for t in self.memberTypes) @@ -2479,6 +2704,9 @@ class IDLUnionType(IDLType): return typeName(type._identifier.object()) if isinstance(type, IDLObjectWithIdentifier): return typeName(type.identifier) + if isinstance(type, IDLBuiltinType) and type.hasAllowShared(): + assert type.isBufferSource() + return "MaybeShared" + type.name return type.name for (i, type) in enumerate(self.memberTypes): @@ -2602,14 +2830,26 @@ class IDLTypedefType(IDLType): def isUSVString(self): return self.inner.isUSVString() + def isUTF8String(self): + return self.inner.isUTF8String() + + def isJSString(self): + return self.inner.isJSString() + def isVoid(self): return self.inner.isVoid() + def isJSONType(self): + return self.inner.isJSONType() + def isSequence(self): return self.inner.isSequence() - def isMozMap(self): - return self.inner.isMozMap() + def isRecord(self): + return self.inner.isRecord() + + def isReadableStream(self): + return self.inner.isReadableStream() def isDictionary(self): return self.inner.isDictionary() @@ -2620,9 +2860,6 @@ class IDLTypedefType(IDLType): def isArrayBufferView(self): return self.inner.isArrayBufferView() - def isSharedArrayBuffer(self): - return self.inner.isSharedArrayBuffer() - def isTypedArray(self): return self.inner.isTypedArray() @@ -2658,12 +2895,17 @@ class IDLTypedefType(IDLType): def _getDependentObjects(self): return self.inner._getDependentObjects() + def withExtendedAttributes(self, attrs): + return IDLTypedefType(self.location, self.inner.withExtendedAttributes(attrs), self.name) + class IDLTypedef(IDLObjectWithIdentifier): def __init__(self, location, parentScope, innerType, name): + # Set self.innerType first, because IDLObjectWithIdentifier.__init__ + # will call our __str__, which wants to use it. + self.innerType = innerType identifier = IDLUnresolvedIdentifier(location, name) IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) - self.innerType = innerType def __str__(self): return "Typedef %s %s" % (self.identifier.name, self.innerType) @@ -2679,26 +2921,30 @@ class IDLTypedef(IDLObjectWithIdentifier): return True def addExtendedAttributes(self, attrs): - assert len(attrs) == 0 + if len(attrs) != 0: + raise WebIDLError("There are no extended attributes that are " + "allowed on typedefs", + [attrs[0].location, self.location]) def _getDependentObjects(self): return self.innerType._getDependentObjects() class IDLWrapperType(IDLType): - def __init__(self, location, inner, promiseInnerType=None): + def __init__(self, location, inner): IDLType.__init__(self, location, inner.identifier.name) self.inner = inner self._identifier = inner.identifier self.builtin = False - assert not promiseInnerType or inner.identifier.name == "Promise" - self._promiseInnerType = promiseInnerType def __eq__(self, other): return (isinstance(other, IDLWrapperType) and self._identifier == other._identifier and self.builtin == other.builtin) + def __hash__(self): + return hash((self._identifier, self.builtin)) + def __str__(self): return str(self.name) + " (Wrapper)" @@ -2720,6 +2966,12 @@ class IDLWrapperType(IDLType): def isUSVString(self): return False + def isUTF8String(self): + return False + + def isJSString(self): + return False + def isVoid(self): return False @@ -2742,23 +2994,25 @@ class IDLWrapperType(IDLType): def isEnum(self): return isinstance(self.inner, IDLEnum) - def isPromise(self): - return (isinstance(self.inner, IDLInterface) and - self.inner.identifier.name == "Promise") - - def promiseInnerType(self): - assert self.isPromise() - return self._promiseInnerType - - def isSerializable(self): + def isJSONType(self): if self.isInterface(): if self.inner.isExternal(): return False - return any(m.isMethod() and m.isJsonifier() for m in self.inner.members) + iface = self.inner + while iface: + if any(m.isMethod() and m.isToJSON() for m in iface.members): + return True + iface = iface.parent + return False elif self.isEnum(): return True elif self.isDictionary(): - return all(m.type.isSerializable() for m in self.inner.members) + dictionary = self.inner + while dictionary: + if not all(m.type.isJSONType() for m in dictionary.members): + return False + dictionary = dictionary.parent + return True else: raise WebIDLError("IDLWrapperType wraps type %s that we don't know if " "is serializable" % type(self.inner), [self.location]) @@ -2781,8 +3035,6 @@ class IDLWrapperType(IDLType): assert False def isDistinguishableFrom(self, other): - if self.isPromise(): - return False if other.isPromise(): return False if other.isUnion(): @@ -2792,11 +3044,11 @@ class IDLWrapperType(IDLType): if self.isEnum(): return (other.isPrimitive() or other.isInterface() or other.isObject() or other.isCallback() or other.isDictionary() or - other.isSequence() or other.isMozMap() or other.isDate()) + other.isSequence() or other.isRecord()) if self.isDictionary() and other.nullable(): return False if (other.isPrimitive() or other.isString() or other.isEnum() or - other.isDate() or other.isSequence()): + other.isSequence()): return True if self.isDictionary(): return other.isNonCallbackInterface() @@ -2814,7 +3066,7 @@ class IDLWrapperType(IDLType): (self.isNonCallbackInterface() or other.isNonCallbackInterface())) if (other.isDictionary() or other.isCallback() or - other.isMozMap()): + other.isRecord()): return self.isNonCallbackInterface() # Not much else |other| can be @@ -2826,13 +3078,10 @@ class IDLWrapperType(IDLType): return True iface = self.inner if iface.isExternal(): - # Let's say true, though ideally we'd only do this when - # exposureSet contains the primary global's name. + # Let's say true, so we don't have to implement exposure mixins on + # external interfaces and sprinkle [Exposed=Window] on every single + # external interface declaration. return True - if (self.isPromise() and - # Check the internal type - not self.promiseInnerType().unroll().isExposedInAllOf(exposureSet)): - return False return iface.exposureSet.issuperset(exposureSet) def _getDependentObjects(self): @@ -2860,6 +3109,48 @@ class IDLWrapperType(IDLType): return set() +class IDLPromiseType(IDLParametrizedType): + def __init__(self, location, innerType): + IDLParametrizedType.__init__(self, location, "Promise", innerType) + + def __eq__(self, other): + return (isinstance(other, IDLPromiseType) and + self.promiseInnerType() == other.promiseInnerType()) + + def __str__(self): + return self.inner.__str__() + "Promise" + + def prettyName(self): + return "Promise<%s>" % self.inner.prettyName() + + def isPromise(self): + return True + + def promiseInnerType(self): + return self.inner + + def tag(self): + return IDLType.Tags.promise + + def complete(self, scope): + self.inner = self.promiseInnerType().complete(scope) + return self + + def unroll(self): + # We do not unroll our inner. Just stop at ourselves. That + # lets us add headers for both ourselves and our inner as + # needed. + return self + + def isDistinguishableFrom(self, other): + # Promises are not distinguishable from anything. + return False + + def isExposedInAllOf(self, exposureSet): + # Check the internal type + return self.promiseInnerType().unroll().isExposedInAllOf(exposureSet) + + class IDLBuiltinType(IDLType): Types = enum( @@ -2884,13 +3175,13 @@ class IDLBuiltinType(IDLType): 'domstring', 'bytestring', 'usvstring', + 'utf8string', + 'jsstring', 'object', - 'date', 'void', # Funny stuff 'ArrayBuffer', 'ArrayBufferView', - 'SharedArrayBuffer', 'Int8Array', 'Uint8Array', 'Uint8ClampedArray', @@ -2899,7 +3190,8 @@ class IDLBuiltinType(IDLType): 'Int32Array', 'Uint32Array', 'Float32Array', - 'Float64Array' + 'Float64Array', + 'ReadableStream', ) TagLookup = { @@ -2920,12 +3212,12 @@ class IDLBuiltinType(IDLType): Types.domstring: IDLType.Tags.domstring, Types.bytestring: IDLType.Tags.bytestring, Types.usvstring: IDLType.Tags.usvstring, + Types.utf8string: IDLType.Tags.utf8string, + Types.jsstring: IDLType.Tags.jsstring, Types.object: IDLType.Tags.object, - Types.date: IDLType.Tags.date, Types.void: IDLType.Tags.void, Types.ArrayBuffer: IDLType.Tags.interface, Types.ArrayBufferView: IDLType.Tags.interface, - Types.SharedArrayBuffer: IDLType.Tags.interface, Types.Int8Array: IDLType.Tags.interface, Types.Uint8Array: IDLType.Tags.interface, Types.Uint8ClampedArray: IDLType.Tags.interface, @@ -2934,13 +3226,129 @@ class IDLBuiltinType(IDLType): Types.Int32Array: IDLType.Tags.interface, Types.Uint32Array: IDLType.Tags.interface, Types.Float32Array: IDLType.Tags.interface, - Types.Float64Array: IDLType.Tags.interface + Types.Float64Array: IDLType.Tags.interface, + Types.ReadableStream: IDLType.Tags.interface, + } + + PrettyNames = { + Types.byte: "byte", + Types.octet: "octet", + Types.short: "short", + Types.unsigned_short: "unsigned short", + Types.long: "long", + Types.unsigned_long: "unsigned long", + Types.long_long: "long long", + Types.unsigned_long_long: "unsigned long long", + Types.boolean: "boolean", + Types.unrestricted_float: "unrestricted float", + Types.float: "float", + Types.unrestricted_double: "unrestricted double", + Types.double: "double", + Types.any: "any", + Types.domstring: "DOMString", + Types.bytestring: "ByteString", + Types.usvstring: "USVString", + Types.utf8string: "USVString", # That's what it is in spec terms + Types.jsstring: "USVString", # Again, that's what it is in spec terms + Types.object: "object", + Types.void: "void", + Types.ArrayBuffer: "ArrayBuffer", + Types.ArrayBufferView: "ArrayBufferView", + Types.Int8Array: "Int8Array", + Types.Uint8Array: "Uint8Array", + Types.Uint8ClampedArray: "Uint8ClampedArray", + Types.Int16Array: "Int16Array", + Types.Uint16Array: "Uint16Array", + Types.Int32Array: "Int32Array", + Types.Uint32Array: "Uint32Array", + Types.Float32Array: "Float32Array", + Types.Float64Array: "Float64Array", + Types.ReadableStream: "ReadableStream", } - def __init__(self, location, name, type): + def __init__(self, location, name, type, clamp=False, enforceRange=False, treatNullAsEmpty=False, + allowShared=False, attrLocation=[]): + """ + The mutually exclusive clamp/enforceRange/treatNullAsEmpty/allowShared arguments are used + to create instances of this type with the appropriate attributes attached. Use .clamped(), + .rangeEnforced(), .withTreatNullAs() and .withAllowShared(). + + attrLocation is an array of source locations of these attributes for error reporting. + """ IDLType.__init__(self, location, name) self.builtin = True self._typeTag = type + self._clamped = None + self._rangeEnforced = None + self._withTreatNullAs = None + self._withAllowShared = None; + if self.isInteger(): + if clamp: + self._clamp = True + self.name = "Clamped" + self.name + self._extendedAttrDict["Clamp"] = True + elif enforceRange: + self._enforceRange = True + self.name = "RangeEnforced" + self.name + self._extendedAttrDict["EnforceRange"] = True + elif clamp or enforceRange: + raise WebIDLError("Non-integer types cannot be [Clamp] or [EnforceRange]", attrLocation) + if self.isDOMString() or self.isUTF8String(): + if treatNullAsEmpty: + self.treatNullAsEmpty = True + self.name = "NullIsEmpty" + self.name + self._extendedAttrDict["TreatNullAs"] = ["EmptyString"] + elif treatNullAsEmpty: + raise WebIDLError("Non-string types cannot be [TreatNullAs]", attrLocation) + if self.isBufferSource(): + if allowShared: + self._allowShared = True + self._extendedAttrDict["AllowShared"] = True + elif allowShared: + raise WebIDLError("Types that are not buffer source types cannot be [AllowShared]", attrLocation) + + def __str__(self): + if self._allowShared: + assert self.isBufferSource() + return "MaybeShared" + str(self.name) + return str(self.name) + + def __eq__(self, other): + return other and self.location == other.location and self.name == other.name and self._typeTag == other._typeTag + + def __hash__(self): + return hash((self.location, self.name, self._typeTag)) + + def prettyName(self): + return IDLBuiltinType.PrettyNames[self._typeTag] + + def clamped(self, attrLocation): + if not self._clamped: + self._clamped = IDLBuiltinType(self.location, self.name, + self._typeTag, clamp=True, + attrLocation=attrLocation) + return self._clamped + + def rangeEnforced(self, attrLocation): + if not self._rangeEnforced: + self._rangeEnforced = IDLBuiltinType(self.location, self.name, + self._typeTag, enforceRange=True, + attrLocation=attrLocation) + return self._rangeEnforced + + def withTreatNullAs(self, attrLocation): + if not self._withTreatNullAs: + self._withTreatNullAs = IDLBuiltinType(self.location, self.name, + self._typeTag, treatNullAsEmpty=True, + attrLocation=attrLocation) + return self._withTreatNullAs + + def withAllowShared(self, attrLocation): + if not self._withAllowShared: + self._withAllowShared = IDLBuiltinType(self.location, self.name, + self._typeTag, allowShared=True, + attrLocation=attrLocation) + return self._withAllowShared def isPrimitive(self): return self._typeTag <= IDLBuiltinType.Types.double @@ -2954,7 +3362,9 @@ class IDLBuiltinType(IDLType): def isString(self): return (self._typeTag == IDLBuiltinType.Types.domstring or self._typeTag == IDLBuiltinType.Types.bytestring or - self._typeTag == IDLBuiltinType.Types.usvstring) + self._typeTag == IDLBuiltinType.Types.usvstring or + self._typeTag == IDLBuiltinType.Types.utf8string or + self._typeTag == IDLBuiltinType.Types.jsstring) def isByteString(self): return self._typeTag == IDLBuiltinType.Types.bytestring @@ -2965,6 +3375,12 @@ class IDLBuiltinType(IDLType): def isUSVString(self): return self._typeTag == IDLBuiltinType.Types.usvstring + def isUTF8String(self): + return self._typeTag == IDLBuiltinType.Types.utf8string + + def isJSString(self): + return self._typeTag == IDLBuiltinType.Types.jsstring + def isInteger(self): return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long @@ -2974,21 +3390,21 @@ class IDLBuiltinType(IDLType): def isArrayBufferView(self): return self._typeTag == IDLBuiltinType.Types.ArrayBufferView - def isSharedArrayBuffer(self): - return self._typeTag == IDLBuiltinType.Types.SharedArrayBuffer - def isTypedArray(self): return (self._typeTag >= IDLBuiltinType.Types.Int8Array and self._typeTag <= IDLBuiltinType.Types.Float64Array) + def isReadableStream(self): + return self._typeTag == IDLBuiltinType.Types.ReadableStream + def isInterface(self): # TypedArray things are interface types per the TypedArray spec, # but we handle them as builtins because SpiderMonkey implements # all of it internally. return (self.isArrayBuffer() or self.isArrayBufferView() or - self.isSharedArrayBuffer() or - self.isTypedArray()) + self.isTypedArray() or + self.isReadableStream()) def isNonCallbackInterface(self): # All the interfaces we can be are non-callback @@ -3005,8 +3421,8 @@ class IDLBuiltinType(IDLType): return (self._typeTag == IDLBuiltinType.Types.unrestricted_float or self._typeTag == IDLBuiltinType.Types.unrestricted_double) - def isSerializable(self): - return self.isPrimitive() or self.isString() or self.isDate() + def isJSONType(self): + return self.isPrimitive() or self.isString() or self.isObject() def includesRestrictedFloat(self): return self.isFloat() and not self.isUnrestricted() @@ -3024,27 +3440,22 @@ class IDLBuiltinType(IDLType): return (other.isNumeric() or other.isString() or other.isEnum() or other.isInterface() or other.isObject() or other.isCallback() or other.isDictionary() or - other.isSequence() or other.isMozMap() or other.isDate()) + other.isSequence() or other.isRecord()) if self.isNumeric(): return (other.isBoolean() or other.isString() or other.isEnum() or other.isInterface() or other.isObject() or other.isCallback() or other.isDictionary() or - other.isSequence() or other.isMozMap() or other.isDate()) + other.isSequence() or other.isRecord()) if self.isString(): return (other.isPrimitive() or other.isInterface() or other.isObject() or other.isCallback() or other.isDictionary() or - other.isSequence() or other.isMozMap() or other.isDate()) + other.isSequence() or other.isRecord()) if self.isAny(): # Can't tell "any" apart from anything return False if self.isObject(): return other.isPrimitive() or other.isString() or other.isEnum() - if self.isDate(): - return (other.isPrimitive() or other.isString() or other.isEnum() or - other.isInterface() or other.isCallback() or - other.isDictionary() or other.isSequence() or - other.isMozMap()) if self.isVoid(): return not other.isVoid() # Not much else we could be! @@ -3052,12 +3463,12 @@ class IDLBuiltinType(IDLType): # Like interfaces, but we know we're not a callback return (other.isPrimitive() or other.isString() or other.isEnum() or other.isCallback() or other.isDictionary() or - other.isSequence() or other.isMozMap() or other.isDate() or + other.isSequence() or other.isRecord() or (other.isInterface() and ( # ArrayBuffer is distinguishable from everything # that's not an ArrayBuffer or a callback interface (self.isArrayBuffer() and not other.isArrayBuffer()) or - (self.isSharedArrayBuffer() and not other.isSharedArrayBuffer()) or + (self.isReadableStream() and not other.isReadableStream()) or # ArrayBufferView is distinguishable from everything # that's not an ArrayBufferView or typed array. (self.isArrayBufferView() and not other.isArrayBufferView() and @@ -3071,6 +3482,54 @@ class IDLBuiltinType(IDLType): def _getDependentObjects(self): return set() + def withExtendedAttributes(self, attrs): + ret = self + for attribute in attrs: + identifier = attribute.identifier() + if identifier == "Clamp": + if not attribute.noArguments(): + raise WebIDLError("[Clamp] must take no arguments", + [attribute.location]) + if ret.hasEnforceRange() or self._enforceRange: + raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive", + [self.location, attribute.location]) + ret = self.clamped([self.location, attribute.location]) + elif identifier == "EnforceRange": + if not attribute.noArguments(): + raise WebIDLError("[EnforceRange] must take no arguments", + [attribute.location]) + if ret.hasClamp() or self._clamp: + raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive", + [self.location, attribute.location]) + ret = self.rangeEnforced([self.location, attribute.location]) + elif identifier == "TreatNullAs": + if not (self.isDOMString() or self.isUTF8String()): + raise WebIDLError("[TreatNullAs] only allowed on DOMStrings and UTF8Strings", + [self.location, attribute.location]) + assert not self.nullable() + if not attribute.hasValue(): + raise WebIDLError("[TreatNullAs] must take an identifier argument", + [attribute.location]) + value = attribute.value() + if value != 'EmptyString': + raise WebIDLError("[TreatNullAs] must take the identifier " + "'EmptyString', not '%s'" % value, + [attribute.location]) + ret = self.withTreatNullAs([self.location, attribute.location]) + elif identifier == "AllowShared": + if not attribute.noArguments(): + raise WebIDLError("[AllowShared] must take no arguments", + [attribute.location]) + if not self.isBufferSource(): + raise WebIDLError("[AllowShared] only allowed on buffer source types", + [self.location, attribute.location]) + ret = self.withAllowShared([self.location, attribute.location]) + + else: + raise WebIDLError("Unhandled extended attribute on type", + [self.location, attribute.location]) + return ret + BuiltinTypes = { IDLBuiltinType.Types.byte: IDLBuiltinType(BuiltinLocation("<builtin type>"), "Byte", @@ -3123,12 +3582,15 @@ BuiltinTypes = { IDLBuiltinType.Types.usvstring: IDLBuiltinType(BuiltinLocation("<builtin type>"), "USVString", IDLBuiltinType.Types.usvstring), + IDLBuiltinType.Types.utf8string: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "UTF8String", + IDLBuiltinType.Types.utf8string), + IDLBuiltinType.Types.jsstring: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "JSString", + IDLBuiltinType.Types.jsstring), IDLBuiltinType.Types.object: IDLBuiltinType(BuiltinLocation("<builtin type>"), "Object", IDLBuiltinType.Types.object), - IDLBuiltinType.Types.date: - IDLBuiltinType(BuiltinLocation("<builtin type>"), "Date", - IDLBuiltinType.Types.date), IDLBuiltinType.Types.void: IDLBuiltinType(BuiltinLocation("<builtin type>"), "Void", IDLBuiltinType.Types.void), @@ -3138,9 +3600,6 @@ BuiltinTypes = { IDLBuiltinType.Types.ArrayBufferView: IDLBuiltinType(BuiltinLocation("<builtin type>"), "ArrayBufferView", IDLBuiltinType.Types.ArrayBufferView), - IDLBuiltinType.Types.SharedArrayBuffer: - IDLBuiltinType(BuiltinLocation("<builtin type>"), "SharedArrayBuffer", - IDLBuiltinType.Types.SharedArrayBuffer), IDLBuiltinType.Types.Int8Array: IDLBuiltinType(BuiltinLocation("<builtin type>"), "Int8Array", IDLBuiltinType.Types.Int8Array), @@ -3167,7 +3626,10 @@ BuiltinTypes = { IDLBuiltinType.Types.Float32Array), IDLBuiltinType.Types.Float64Array: IDLBuiltinType(BuiltinLocation("<builtin type>"), "Float64Array", - IDLBuiltinType.Types.Float64Array) + IDLBuiltinType.Types.Float64Array), + IDLBuiltinType.Types.ReadableStream: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "ReadableStream", + IDLBuiltinType.Types.ReadableStream), } @@ -3184,7 +3646,7 @@ integerTypeSizes = { def matchIntegerValueToType(value): - for type, extremes in integerTypeSizes.items(): + for type, extremes in list(integerTypeSizes.items()): (min, max) = extremes if value <= max and value >= min: return BuiltinTypes[type] @@ -3263,7 +3725,7 @@ class IDLValue(IDLObject): elif self.type.isString() and type.isEnum(): # Just keep our string, but make sure it's a valid value for this enum enum = type.unroll().inner - if self.value not in enum.values(): + if self.value not in list(enum.values()): raise WebIDLError("'%s' is not a valid default value for enum %s" % (self.value, enum.identifier.name), [location, enum.location]) @@ -3282,8 +3744,13 @@ class IDLValue(IDLObject): # extra normalization step. assert self.type.isDOMString() return self - elif self.type.isString() and type.isByteString(): - # Allow ByteStrings to use a default value like DOMString. + elif self.type.isDOMString() and type.treatNullAsEmpty: + # TreatNullAsEmpty is a different type for resolution reasons, + # however once you have a value it doesn't matter + return self + elif self.type.isString() and (type.isByteString() or type.isJSString() or type.isUTF8String()): + # Allow ByteStrings, UTF8String, and JSStrings to use a default + # value like DOMString. # No coercion is required as Codegen.py will handle the # extra steps. We want to make sure that our string contains # only valid characters, so we check that here. @@ -3312,8 +3779,6 @@ class IDLNullValue(IDLObject): def coerceToType(self, type, location): if (not isinstance(type, IDLNullableType) and not (type.isUnion() and type.hasNullableType) and - not (type.isUnion() and type.hasDictionaryType()) and - not type.isDictionary() and not type.isAny()): raise WebIDLError("Cannot coerce null value to type %s." % type, [location]) @@ -3362,6 +3827,35 @@ class IDLEmptySequenceValue(IDLObject): return set() +class IDLDefaultDictionaryValue(IDLObject): + def __init__(self, location): + IDLObject.__init__(self, location) + self.type = None + self.value = None + + def coerceToType(self, type, location): + if type.isUnion(): + # We use the flat member types here, because if we have a nullable + # member type, or a nested union, we want the type the value + # actually coerces to, not the nullable or nested union type. + for subtype in type.unroll().flatMemberTypes: + try: + return self.coerceToType(subtype, location) + except: + pass + + if not type.isDictionary(): + raise WebIDLError("Cannot coerce default dictionary value to type %s." % type, + [location]) + + defaultDictionaryValue = IDLDefaultDictionaryValue(self.location) + defaultDictionaryValue.type = type + return defaultDictionaryValue + + def _getDependentObjects(self): + return set() + + class IDLUndefinedValue(IDLObject): def __init__(self, location): IDLObject.__init__(self, location) @@ -3437,10 +3931,6 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins): return self._extendedAttrDict.get(name, None) def finish(self, scope): - # We better be exposed _somewhere_. - if (len(self._exposureGlobalNames) == 0): - print self.identifier.name - assert len(self._exposureGlobalNames) != 0 IDLExposureMixins.finish(self, scope) def validate(self): @@ -3456,6 +3946,14 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins): raise WebIDLError("A [NewObject] method is not idempotent, " "so it has to depend on something other than DOM state.", [self.location]) + if (self.getExtendedAttribute("Cached") or + self.getExtendedAttribute("StoreInSlot")): + raise WebIDLError("A [NewObject] attribute shouldnt be " + "[Cached] or [StoreInSlot], since the point " + "of those is to keep returning the same " + "thing across multiple calls, which is not " + "what [NewObject] does.", + [self.location]) def _setDependsOn(self, dependsOn): if self.dependsOn != "Everything": @@ -3483,6 +3981,11 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins): [self.location]) self.aliases.append(alias) + def _addBindingAlias(self, bindingAlias): + if bindingAlias in self.bindingAliases: + raise WebIDLError("Duplicate [BindingAlias=%s] on attribute" % bindingAlias, + [self.location]) + self.bindingAliases.append(bindingAlias) class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): @@ -3527,8 +4030,10 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): (member.identifier.name, self.maplikeOrSetlikeOrIterableType), [self.location, member.location]) - # Check that there are no disallowed non-method members - if (isAncestor or (member.isAttr() or member.isConst()) and + # Check that there are no disallowed non-method members. + # Ancestor members are always disallowed here; own members + # are disallowed only if they're non-methods. + if ((isAncestor or member.isAttr() or member.isConst()) and member.identifier.name in self.disallowedNonMethodNames): raise WebIDLError("Member '%s' conflicts " "with reserved %s method." % @@ -3601,6 +4106,11 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): if isIteratorAlias: method.addExtendedAttributes( [IDLExtendedAttribute(self.location, ("Alias", "@@iterator"))]) + # Methods generated for iterables should be enumerable, but the ones for + # maplike/setlike should not be. + if not self.isIterable(): + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("NonEnumerable",))]) members.append(method) def resolve(self, parentScope): @@ -3724,12 +4234,17 @@ class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase): specification during parsing. """ # Both maplike and setlike have a size attribute - members.append(IDLAttribute(self.location, - IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), "size"), - BuiltinTypes[IDLBuiltinType.Types.unsigned_long], - True, - maplikeOrSetlike=self)) + sizeAttr = IDLAttribute(self.location, + IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), "size"), + BuiltinTypes[IDLBuiltinType.Types.unsigned_long], + True, + maplikeOrSetlike=self) + # This should be non-enumerable. + sizeAttr.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("NonEnumerable",))]) + members.append(sizeAttr) self.reserved_ro_names = ["size"] + self.disallowedMemberNames.append("size") # object entries() self.addMethod("entries", members, False, BuiltinTypes[IDLBuiltinType.Types.object], @@ -3820,6 +4335,9 @@ class IDLConst(IDLInterfaceMember): if type.isDictionary(): raise WebIDLError("A constant cannot be of a dictionary type", [self.location]) + if type.isRecord(): + raise WebIDLError("A constant cannot be of a record type", + [self.location]) self.type = type self.value = value @@ -3860,7 +4378,9 @@ class IDLConst(IDLInterfaceMember): elif (identifier == "Pref" or identifier == "ChromeOnly" or identifier == "Func" or - identifier == "SecureContext"): + identifier == "SecureContext" or + identifier == "NonEnumerable" or + identifier == "NeedsWindowsUndef"): # Known attributes that we don't need to do anything with here pass else: @@ -3875,7 +4395,7 @@ class IDLConst(IDLInterfaceMember): class IDLAttribute(IDLInterfaceMember): def __init__(self, location, identifier, type, readonly, inherit=False, static=False, stringifier=False, maplikeOrSetlike=None, - extendedAttrDict=None, navigatorObjectGetter=False): + extendedAttrDict=None): IDLInterfaceMember.__init__(self, location, identifier, IDLInterfaceMember.Tags.Attr, extendedAttrDict=extendedAttrDict) @@ -3888,14 +4408,12 @@ class IDLAttribute(IDLInterfaceMember): self.lenientThis = False self._unforgeable = False self.stringifier = stringifier - self.enforceRange = False - self.clamp = False self.slotIndices = None assert maplikeOrSetlike is None or isinstance(maplikeOrSetlike, IDLMaplikeOrSetlike) self.maplikeOrSetlike = maplikeOrSetlike self.dependsOn = "Everything" self.affects = "Everything" - self.navigatorObjectGetter = navigatorObjectGetter + self.bindingAliases = [] if static and identifier.name == "prototype": raise WebIDLError("The identifier of a static attribute must not be 'prototype'", @@ -3925,14 +4443,18 @@ class IDLAttribute(IDLInterfaceMember): assert not isinstance(t.name, IDLUnresolvedIdentifier) self.type = t + if self.readonly and (self.type.hasClamp() or self.type.hasEnforceRange() or + self.type.hasAllowShared() or self.type.treatNullAsEmpty): + raise WebIDLError("A readonly attribute cannot be [Clamp] or [EnforceRange] or [AllowShared]", + [self.location]) if self.type.isDictionary() and not self.getExtendedAttribute("Cached"): raise WebIDLError("An attribute cannot be of a dictionary type", [self.location]) if self.type.isSequence() and not self.getExtendedAttribute("Cached"): raise WebIDLError("A non-cached attribute cannot be of a sequence " "type", [self.location]) - if self.type.isMozMap() and not self.getExtendedAttribute("Cached"): - raise WebIDLError("A non-cached attribute cannot be of a MozMap " + if self.type.isRecord() and not self.getExtendedAttribute("Cached"): + raise WebIDLError("A non-cached attribute cannot be of a record " "type", [self.location]) if self.type.isUnion(): for f in self.type.unroll().flatMemberTypes: @@ -3948,25 +4470,30 @@ class IDLAttribute(IDLInterfaceMember): "one of its member types's member " "types, and so on) is a sequence " "type", [self.location, f.location]) - if f.isMozMap(): + if f.isRecord(): raise WebIDLError("An attribute cannot be of a union " "type if one of its member types (or " "one of its member types's member " - "types, and so on) is a MozMap " + "types, and so on) is a record " "type", [self.location, f.location]) if not self.type.isInterface() and self.getExtendedAttribute("PutForwards"): raise WebIDLError("An attribute with [PutForwards] must have an " "interface type as its type", [self.location]) - if not self.type.isInterface() and self.getExtendedAttribute("SameObject"): + if (not self.type.isInterface() and + self.getExtendedAttribute("SameObject")): raise WebIDLError("An attribute with [SameObject] must have an " "interface type as its type", [self.location]) + if self.type.isPromise() and not self.readonly: + raise WebIDLError("Promise-returning attributes must be readonly", + [self.location]) + def validate(self): def typeContainsChromeOnlyDictionaryMember(type): if (type.nullable() or type.isSequence() or - type.isMozMap()): + type.isRecord()): return typeContainsChromeOnlyDictionaryMember(type.inner) if type.isUnion(): @@ -4012,27 +4539,42 @@ class IDLAttribute(IDLInterfaceMember): [self.location, location]) if self.getExtendedAttribute("Frozen"): if (not self.type.isSequence() and not self.type.isDictionary() and - not self.type.isMozMap()): + not self.type.isRecord()): raise WebIDLError("[Frozen] is only allowed on " "sequence-valued, dictionary-valued, and " - "MozMap-valued attributes", + "record-valued attributes", [self.location]) if not self.type.unroll().isExposedInAllOf(self.exposureSet): raise WebIDLError("Attribute returns a type that is not exposed " "everywhere where the attribute is exposed", [self.location]) + if self.getExtendedAttribute("CEReactions"): + if self.readonly: + raise WebIDLError("[CEReactions] is not allowed on " + "readonly attributes", + [self.location]) def handleExtendedAttribute(self, attr): identifier = attr.identifier() - if identifier == "SetterThrows" and self.readonly: + if ((identifier == "SetterThrows" or identifier == "SetterCanOOM" or + identifier == "SetterNeedsSubjectPrincipal") + and self.readonly): raise WebIDLError("Readonly attributes must not be flagged as " - "[SetterThrows]", + "[%s]" % identifier, [self.location]) - elif (((identifier == "Throws" or identifier == "GetterThrows") and + elif identifier == "BindingAlias": + if not attr.hasValue(): + raise WebIDLError("[BindingAlias] takes an identifier or string", + [attr.location]) + self._addBindingAlias(attr.value()) + elif (((identifier == "Throws" or identifier == "GetterThrows" or + identifier == "CanOOM" or identifier == "GetterCanOOM") and self.getExtendedAttribute("StoreInSlot")) or (identifier == "StoreInSlot" and (self.getExtendedAttribute("Throws") or - self.getExtendedAttribute("GetterThrows")))): + self.getExtendedAttribute("GetterThrows") or + self.getExtendedAttribute("CanOOM") or + self.getExtendedAttribute("GetterCanOOM")))): raise WebIDLError("Throwing things can't be [StoreInSlot]", [attr.location]) elif identifier == "LenientThis": @@ -4066,6 +4608,10 @@ class IDLAttribute(IDLInterfaceMember): if not self.readonly: raise WebIDLError("[PutForwards] is only allowed on readonly " "attributes", [attr.location, self.location]) + if self.type.isPromise(): + raise WebIDLError("[PutForwards] is not allowed on " + "Promise-typed attributes", + [attr.location, self.location]) if self.isStatic(): raise WebIDLError("[PutForwards] is only allowed on non-static " "attributes", [attr.location, self.location]) @@ -4083,6 +4629,10 @@ class IDLAttribute(IDLInterfaceMember): if not self.readonly: raise WebIDLError("[Replaceable] is only allowed on readonly " "attributes", [attr.location, self.location]) + if self.type.isPromise(): + raise WebIDLError("[Replaceable] is not allowed on " + "Promise-typed attributes", + [attr.location, self.location]) if self.isStatic(): raise WebIDLError("[Replaceable] is only allowed on non-static " "attributes", [attr.location, self.location]) @@ -4097,6 +4647,10 @@ class IDLAttribute(IDLInterfaceMember): if not self.readonly: raise WebIDLError("[LenientSetter] is only allowed on readonly " "attributes", [attr.location, self.location]) + if self.type.isPromise(): + raise WebIDLError("[LenientSetter] is not allowed on " + "Promise-typed attributes", + [attr.location, self.location]) if self.isStatic(): raise WebIDLError("[LenientSetter] is only allowed on non-static " "attributes", [attr.location, self.location]) @@ -4116,16 +4670,6 @@ class IDLAttribute(IDLInterfaceMember): raise WebIDLError("[LenientFloat] used on an attribute with a " "non-restricted-float type", [attr.location, self.location]) - elif identifier == "EnforceRange": - if self.readonly: - raise WebIDLError("[EnforceRange] used on a readonly attribute", - [attr.location, self.location]) - self.enforceRange = True - elif identifier == "Clamp": - if self.readonly: - raise WebIDLError("[Clamp] used on a readonly attribute", - [attr.location, self.location]) - self.clamp = True elif identifier == "StoreInSlot": if self.getExtendedAttribute("Cached"): raise WebIDLError("[StoreInSlot] and [Cached] must not be " @@ -4138,7 +4682,7 @@ class IDLAttribute(IDLInterfaceMember): [attr.location, self.location]) elif (identifier == "CrossOriginReadable" or identifier == "CrossOriginWritable"): - if not attr.noArguments() and identifier == "CrossOriginReadable": + if not attr.noArguments(): raise WebIDLError("[%s] must take no arguments" % identifier, [attr.location]) if self.isStatic(): @@ -4191,18 +4735,30 @@ class IDLAttribute(IDLInterfaceMember): raise WebIDLError("[Unscopable] is only allowed on non-static " "attributes and operations", [attr.location, self.location]) + elif identifier == "CEReactions": + if not attr.noArguments(): + raise WebIDLError("[CEReactions] must take no arguments", + [attr.location]) elif (identifier == "Pref" or identifier == "Deprecated" or identifier == "SetterThrows" or identifier == "Throws" or identifier == "GetterThrows" or + identifier == "SetterCanOOM" or + identifier == "CanOOM" or + identifier == "GetterCanOOM" or identifier == "ChromeOnly" or identifier == "Func" or identifier == "SecureContext" or identifier == "Frozen" or identifier == "NewObject" or - identifier == "UnsafeInPrerendering" or - identifier == "BinaryName"): + identifier == "NeedsSubjectPrincipal" or + identifier == "SetterNeedsSubjectPrincipal" or + identifier == "GetterNeedsSubjectPrincipal" or + identifier == "NeedsCallerType" or + identifier == "ReturnValueNeedsContainsHack" or + identifier == "BinaryName" or + identifier == "NonEnumerable"): # Known attributes that we don't need to do anything with here pass else: @@ -4215,10 +4771,6 @@ class IDLAttribute(IDLInterfaceMember): self.type.resolveType(parentScope) IDLObjectWithIdentifier.resolve(self, parentScope) - def addExtendedAttributes(self, attrs): - attrs = self.checkForStringHandlingExtendedAttributes(attrs) - IDLInterfaceMember.addExtendedAttributes(self, attrs) - def hasLenientThis(self): return self.lenientThis @@ -4236,9 +4788,43 @@ class IDLAttribute(IDLInterfaceMember): def _getDependentObjects(self): return set([self.type]) + def expand(self, members): + assert self.stringifier + if not self.type.isDOMString() and not self.type.isUSVString(): + raise WebIDLError("The type of a stringifer attribute must be " + "either DOMString or USVString", + [self.location]) + identifier = IDLUnresolvedIdentifier(self.location, "__stringifier", + allowDoubleUnderscore=True) + method = IDLMethod(self.location, + identifier, + returnType=self.type, arguments=[], + stringifier=True, underlyingAttr=self) + allowedExtAttrs = ["Throws", "NeedsSubjectPrincipal", "Pure"] + # Safe to ignore these as they are only meaningful for attributes + attributeOnlyExtAttrs = [ + "CEReactions", + "CrossOriginWritable", + "SetterThrows", + ] + for (key, value) in list(self._extendedAttrDict.items()): + if key in allowedExtAttrs: + if value is not True: + raise WebIDLError("[%s] with a value is currently " + "unsupported in stringifier attributes, " + "please file a bug to add support" % key, + [self.location]) + method.addExtendedAttributes([IDLExtendedAttribute(self.location, (key,))]) + elif not key in attributeOnlyExtAttrs: + raise WebIDLError("[%s] is currently unsupported in " + "stringifier attributes, please file a bug " + "to add support" % key, + [self.location]) + members.append(method) + class IDLArgument(IDLObjectWithIdentifier): - def __init__(self, location, identifier, type, optional=False, defaultValue=None, variadic=False, dictionaryMember=False): + def __init__(self, location, identifier, type, optional=False, defaultValue=None, variadic=False, dictionaryMember=False, allowTypeAttributes=False): IDLObjectWithIdentifier.__init__(self, location, None, identifier) assert isinstance(type, IDLType) @@ -4249,41 +4835,25 @@ class IDLArgument(IDLObjectWithIdentifier): self.variadic = variadic self.dictionaryMember = dictionaryMember self._isComplete = False - self.enforceRange = False - self.clamp = False self._allowTreatNonCallableAsNull = False self._extendedAttrDict = {} + self.allowTypeAttributes = allowTypeAttributes assert not variadic or optional assert not variadic or not defaultValue def addExtendedAttributes(self, attrs): - attrs = self.checkForStringHandlingExtendedAttributes( - attrs, - isDictionaryMember=self.dictionaryMember, - isOptional=self.optional) for attribute in attrs: identifier = attribute.identifier() - if identifier == "Clamp": - if not attribute.noArguments(): - raise WebIDLError("[Clamp] must take no arguments", - [attribute.location]) - if self.enforceRange: - raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive", - [self.location]) - self.clamp = True - elif identifier == "EnforceRange": - if not attribute.noArguments(): - raise WebIDLError("[EnforceRange] must take no arguments", - [attribute.location]) - if self.clamp: - raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive", - [self.location]) - self.enforceRange = True + if self.allowTypeAttributes and (identifier == "EnforceRange" or identifier == "Clamp" or + identifier == "TreatNullAs" or identifier == "AllowShared"): + self.type = self.type.withExtendedAttributes([attribute]) elif identifier == "TreatNonCallableAsNull": self._allowTreatNonCallableAsNull = True elif (self.dictionaryMember and - (identifier == "ChromeOnly" or identifier == "Func")): + (identifier == "ChromeOnly" or + identifier == "Func" or + identifier == "Pref")): if not self.optional: raise WebIDLError("[%s] must not be used on a required " "dictionary member" % identifier, @@ -4315,13 +4885,7 @@ class IDLArgument(IDLObjectWithIdentifier): assert not isinstance(type.name, IDLUnresolvedIdentifier) self.type = type - if ((self.type.isDictionary() or - self.type.isUnion() and self.type.unroll().hasDictionaryType()) and - self.optional and not self.defaultValue and not self.variadic): - # Default optional non-variadic dictionaries to null, - # for simplicity, so the codegen doesn't have to special-case this. - self.defaultValue = IDLNullValue(self.location) - elif self.type.isAny(): + if self.type.isAny(): assert (self.defaultValue is None or isinstance(self.defaultValue, IDLNullValue)) # optional 'any' values always have a default value @@ -4330,6 +4894,8 @@ class IDLArgument(IDLObjectWithIdentifier): # codegen doesn't have to special-case this. self.defaultValue = IDLUndefinedValue(self.location) + if self.dictionaryMember and self.type.treatNullAsEmpty: + raise WebIDLError("Dictionary members cannot be [TreatNullAs]", [self.location]) # Now do the coercing thing; this needs to happen after the # above creation of a default value. if self.defaultValue: @@ -4351,7 +4917,7 @@ class IDLArgument(IDLObjectWithIdentifier): class IDLCallback(IDLObjectWithScope): - def __init__(self, location, parentScope, identifier, returnType, arguments): + def __init__(self, location, parentScope, identifier, returnType, arguments, isConstructor): assert isinstance(returnType, IDLType) self._returnType = returnType @@ -4366,10 +4932,15 @@ class IDLCallback(IDLObjectWithScope): self._treatNonCallableAsNull = False self._treatNonObjectAsNull = False + self._isRunScriptBoundary = False + self._isConstructor = isConstructor def isCallback(self): return True + def isConstructor(self): + return self._isConstructor + def signatures(self): return [(self._returnType, self._arguments)] @@ -4402,7 +4973,16 @@ class IDLCallback(IDLObjectWithScope): if attr.identifier() == "TreatNonCallableAsNull": self._treatNonCallableAsNull = True elif attr.identifier() == "TreatNonObjectAsNull": + if self._isConstructor: + raise WebIDLError("[TreatNonObjectAsNull] is not supported " + "on constructors", [self.location]) self._treatNonObjectAsNull = True + elif attr.identifier() == "MOZ_CAN_RUN_SCRIPT_BOUNDARY": + if self._isConstructor: + raise WebIDLError("[MOZ_CAN_RUN_SCRIPT_BOUNDARY] is not " + "permitted on constructors", + [self.location]) + self._isRunScriptBoundary = True else: unhandledAttrs.append(attr) if self._treatNonCallableAsNull and self._treatNonObjectAsNull: @@ -4414,6 +4994,9 @@ class IDLCallback(IDLObjectWithScope): def _getDependentObjects(self): return set([self._returnType] + self._arguments) + def isRunScriptBoundary(self): + return self._isRunScriptBoundary; + class IDLCallbackType(IDLType): def __init__(self, location, callback): @@ -4433,8 +5016,7 @@ class IDLCallbackType(IDLType): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) return (other.isPrimitive() or other.isString() or other.isEnum() or - other.isNonCallbackInterface() or other.isDate() or - other.isSequence()) + other.isNonCallbackInterface() or other.isSequence()) def _getDependentObjects(self): return self.callback._getDependentObjects() @@ -4460,13 +5042,15 @@ class IDLMethodOverload: deps.add(self.returnType) return deps + def includesRestrictedFloatArgument(self): + return any(arg.type.includesRestrictedFloat() for arg in self.arguments) + class IDLMethod(IDLInterfaceMember, IDLScope): Special = enum( 'Getter', 'Setter', - 'Creator', 'Deleter', 'LegacyCaller', base=IDLInterfaceMember.Special @@ -4479,10 +5063,11 @@ class IDLMethod(IDLInterfaceMember, IDLScope): ) def __init__(self, location, identifier, returnType, arguments, - static=False, getter=False, setter=False, creator=False, + static=False, getter=False, setter=False, deleter=False, specialType=NamedOrIndexed.Neither, - legacycaller=False, stringifier=False, jsonifier=False, - maplikeOrSetlikeOrIterable=None): + legacycaller=False, stringifier=False, + maplikeOrSetlikeOrIterable=None, + underlyingAttr=None): # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up. IDLInterfaceMember.__init__(self, location, identifier, IDLInterfaceMember.Tags.Method) @@ -4500,18 +5085,16 @@ class IDLMethod(IDLInterfaceMember, IDLScope): self._getter = getter assert isinstance(setter, bool) self._setter = setter - assert isinstance(creator, bool) - self._creator = creator assert isinstance(deleter, bool) self._deleter = deleter assert isinstance(legacycaller, bool) self._legacycaller = legacycaller assert isinstance(stringifier, bool) self._stringifier = stringifier - assert isinstance(jsonifier, bool) - self._jsonifier = jsonifier assert maplikeOrSetlikeOrIterable is None or isinstance(maplikeOrSetlikeOrIterable, IDLMaplikeOrSetlikeOrIterableBase) self.maplikeOrSetlikeOrIterable = maplikeOrSetlikeOrIterable + self._htmlConstructor = False + self.underlyingAttr = underlyingAttr self._specialType = specialType self._unforgeable = False self.dependsOn = "Everything" @@ -4538,7 +5121,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope): assert not arguments[0].optional and not arguments[0].variadic assert not self._getter or not overload.returnType.isVoid() - if self._setter or self._creator: + if self._setter: assert len(self._overloads) == 1 arguments = self._overloads[0].arguments assert len(arguments) == 2 @@ -4551,13 +5134,8 @@ class IDLMethod(IDLInterfaceMember, IDLScope): assert len(self._overloads) == 1 overload = self._overloads[0] assert len(overload.arguments) == 0 - assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.domstring] - - if self._jsonifier: - assert len(self._overloads) == 1 - overload = self._overloads[0] - assert len(overload.arguments) == 0 - assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.object] + if not self.underlyingAttr: + assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.domstring] def isStatic(self): return self._static @@ -4571,9 +5149,6 @@ class IDLMethod(IDLInterfaceMember, IDLScope): def isSetter(self): return self._setter - def isCreator(self): - return self._creator - def isDeleter(self): return self._deleter @@ -4593,8 +5168,11 @@ class IDLMethod(IDLInterfaceMember, IDLScope): def isStringifier(self): return self._stringifier - def isJsonifier(self): - return self._jsonifier + def isToJSON(self): + return self.identifier.name == "toJSON" + + def isDefaultToJSON(self): + return self.isToJSON() and self.getExtendedAttribute("Default") def isMaplikeOrSetlikeOrIterableMethod(self): """ @@ -4606,11 +5184,12 @@ class IDLMethod(IDLInterfaceMember, IDLScope): def isSpecial(self): return (self.isGetter() or self.isSetter() or - self.isCreator() or self.isDeleter() or self.isLegacycaller() or - self.isStringifier() or - self.isJsonifier()) + self.isStringifier()) + + def isHTMLConstructor(self): + return self._htmlConstructor def hasOverloads(self): return self._hasOverloads @@ -4637,10 +5216,25 @@ class IDLMethod(IDLInterfaceMember, IDLScope): def addOverload(self, method): assert len(method._overloads) == 1 - if self._extendedAttrDict != method ._extendedAttrDict: - raise WebIDLError("Extended attributes differ on different " - "overloads of %s" % method.identifier, - [self.location, method.location]) + if self._extendedAttrDict != method._extendedAttrDict: + extendedAttrDiff = set(self._extendedAttrDict.keys()) ^ set(method._extendedAttrDict.keys()) + + if extendedAttrDiff == { "LenientFloat" }: + if "LenientFloat" not in self._extendedAttrDict: + for overload in self._overloads: + if overload.includesRestrictedFloatArgument(): + raise WebIDLError("Restricted float behavior differs on different " + "overloads of %s" % method.identifier, + [overload.location, method.location]) + self._extendedAttrDict["LenientFloat"] = method._extendedAttrDict["LenientFloat"] + elif method._overloads[0].includesRestrictedFloatArgument(): + raise WebIDLError("Restricted float behavior differs on different " + "overloads of %s" % method.identifier, + [self.location, method.location]) + else: + raise WebIDLError("Extended attributes differ on different " + "overloads of %s" % method.identifier, + [self.location, method.location]) self._overloads.extend(method._overloads) @@ -4659,14 +5253,12 @@ class IDLMethod(IDLInterfaceMember, IDLScope): assert not method.isGetter() assert not self.isSetter() assert not method.isSetter() - assert not self.isCreator() - assert not method.isCreator() assert not self.isDeleter() assert not method.isDeleter() assert not self.isStringifier() assert not method.isStringifier() - assert not self.isJsonifier() - assert not method.isJsonifier() + assert not self.isHTMLConstructor() + assert not method.isHTMLConstructor() return self @@ -4735,7 +5327,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope): assert argument.type.isComplete() if ((argument.type.isDictionary() and - argument.type.inner.canBeEmpty())or + argument.type.unroll().inner.canBeEmpty()) or (argument.type.isUnion() and argument.type.unroll().hasPossiblyEmptyDictionaryType())): # Optional dictionaries and unions containing optional @@ -4743,19 +5335,33 @@ class IDLMethod(IDLInterfaceMember, IDLScope): # optional arguments must be optional. if (not argument.optional and all(arg.optional for arg in arguments[idx+1:])): - raise WebIDLError("Dictionary argument or union " - "argument containing a dictionary " - "not followed by a required argument " + raise WebIDLError("Dictionary argument without any " + "required fields or union argument " + "containing such dictionary not " + "followed by a required argument " "must be optional", [argument.location]) - # An argument cannot be a Nullable Dictionary - if argument.type.nullable(): - raise WebIDLError("An argument cannot be a nullable " - "dictionary or nullable union " - "containing a dictionary", + if (not argument.defaultValue and + all(arg.optional for arg in arguments[idx+1:])): + raise WebIDLError("Dictionary argument without any " + "required fields or union argument " + "containing such dictionary not " + "followed by a required argument " + "must have a default value", [argument.location]) + # An argument cannot be a nullable dictionary or a + # nullable union containing a dictionary. + if (argument.type.nullable() and + (argument.type.isDictionary() or + (argument.type.isUnion() and + argument.type.unroll().hasDictionaryType()))): + raise WebIDLError("An argument cannot be a nullable " + "dictionary or nullable union " + "containing a dictionary", + [argument.location]) + # Only the last argument can be variadic if variadicArgument: raise WebIDLError("Variadic argument is not last argument", @@ -4786,6 +5392,19 @@ class IDLMethod(IDLInterfaceMember, IDLScope): " methods on JS-implemented classes only.", [self.location]) + # Ensure that toJSON methods satisfy the spec constraints on them. + if self.identifier.name == "toJSON": + if len(self.signatures()) != 1: + raise WebIDLError("toJSON method has multiple overloads", + [self._overloads[0].location, + self._overloads[1].location]) + if len(self.signatures()[0][1]) != 0: + raise WebIDLError("toJSON method has arguments", + [self.location]) + if not self.signatures()[0][0].isJSONType(): + raise WebIDLError("toJSON method has non-JSON return type", + [self.location]) + def overloadsForArgCount(self, argc): return [overload for overload in self._overloads if len(overload.arguments) == argc or @@ -4831,13 +5450,14 @@ class IDLMethod(IDLInterfaceMember, IDLScope): def handleExtendedAttribute(self, attr): identifier = attr.identifier() - if identifier == "GetterThrows": + if (identifier == "GetterThrows" or + identifier == "SetterThrows" or + identifier == "GetterCanOOM" or + identifier == "SetterCanOOM" or + identifier == "SetterNeedsSubjectPrincipal" or + identifier == "GetterNeedsSubjectPrincipal"): raise WebIDLError("Methods must not be flagged as " - "[GetterThrows]", - [attr.location, self.location]) - elif identifier == "SetterThrows": - raise WebIDLError("Methods must not be flagged as " - "[SetterThrows]", + "[%s]" % identifier, [attr.location, self.location]) elif identifier == "Unforgeable": if self.isStatic(): @@ -4858,12 +5478,12 @@ class IDLMethod(IDLInterfaceMember, IDLScope): [attr.location, self.location]) elif identifier == "LenientFloat": # This is called before we've done overload resolution - assert len(self.signatures()) == 1 - sig = self.signatures()[0] - if not sig[0].isVoid(): + overloads = self._overloads + assert len(overloads) == 1 + if not overloads[0].returnType.isVoid(): raise WebIDLError("[LenientFloat] used on a non-void method", [attr.location, self.location]) - if not any(arg.type.includesRestrictedFloat() for arg in sig[1]): + if not overloads[0].includesRestrictedFloatArgument(): raise WebIDLError("[LenientFloat] used on an operation with no " "restricted float type arguments", [attr.location, self.location]) @@ -4875,6 +5495,10 @@ class IDLMethod(IDLInterfaceMember, IDLScope): if not attr.noArguments(): raise WebIDLError("[%s] must take no arguments" % identifier, [attr.location]) + if identifier == "CrossOriginCallable" and self.isStatic(): + raise WebIDLError("[CrossOriginCallable] is only allowed on non-static " + "attributes", + [attr.location, self.location]) elif identifier == "Pure": if not attr.noArguments(): raise WebIDLError("[Pure] must take no arguments", @@ -4909,16 +5533,42 @@ class IDLMethod(IDLInterfaceMember, IDLScope): raise WebIDLError("[Unscopable] is only allowed on non-static " "attributes and operations", [attr.location, self.location]) + elif identifier == "CEReactions": + if not attr.noArguments(): + raise WebIDLError("[CEReactions] must take no arguments", + [attr.location]) + + if self.isSpecial() and not self.isSetter() and not self.isDeleter(): + raise WebIDLError("[CEReactions] is only allowed on operation, " + "attribute, setter, and deleter", + [attr.location, self.location]) + elif identifier == "Default": + if not attr.noArguments(): + raise WebIDLError("[Default] must take no arguments", + [attr.location]) + + if not self.isToJSON(): + raise WebIDLError("[Default] is only allowed on toJSON operations", + [attr.location, self.location]) + + if self.signatures()[0][0] != BuiltinTypes[IDLBuiltinType.Types.object]: + raise WebIDLError("The return type of the default toJSON " + "operation must be 'object'", + [attr.location, self.location]) elif (identifier == "Throws" or + identifier == "CanOOM" or identifier == "NewObject" or identifier == "ChromeOnly" or - identifier == "UnsafeInPrerendering" or identifier == "Pref" or identifier == "Deprecated" or identifier == "Func" or identifier == "SecureContext" or identifier == "BinaryName" or - identifier == "StaticClassOverride"): + identifier == "NeedsSubjectPrincipal" or + identifier == "NeedsCallerType" or + identifier == "StaticClassOverride" or + identifier == "NonEnumerable" or + identifier == "Unexposed"): # Known attributes that we don't need to do anything with here pass else: @@ -4939,49 +5589,110 @@ class IDLMethod(IDLInterfaceMember, IDLScope): return deps -class IDLImplementsStatement(IDLObject): - def __init__(self, location, implementor, implementee): +class IDLConstructor(IDLMethod): + def __init__(self, location, args, name): + # We can't actually init our IDLMethod yet, because we do not know the + # return type yet. Just save the info we have for now and we will init + # it later. + self._initLocation = location + self._initArgs = args + self._initName = name + self._inited = False + self._initExtendedAttrs = [] + + def addExtendedAttributes(self, attrs): + if self._inited: + return IDLMethod.addExtendedAttributes(self, attrs) + self._initExtendedAttrs.extend(attrs) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if (identifier == "BinaryName" or + identifier == "ChromeOnly" or + identifier == "NewObject" or + identifier == "SecureContext" or + identifier == "Throws" or + identifier == "Func" or + identifier == "Pref"): + IDLMethod.handleExtendedAttribute(self, attr) + elif identifier == "HTMLConstructor": + if not attr.noArguments(): + raise WebIDLError("[HTMLConstructor] must take no arguments", + [attr.location]) + # We shouldn't end up here for named constructors. + assert(self.identifier.name == "constructor") + + if any(len(sig[1]) != 0 for sig in self.signatures()): + raise WebIDLError("[HTMLConstructor] must not be applied to a " + "constructor operation that has arguments.", + [attr.location]) + self._htmlConstructor = True + else: + raise WebIDLError("Unknown extended attribute %s on method" % identifier, + [attr.location]) + + def reallyInit(self, parentInterface): + name = self._initName + location = self._initLocation + identifier = IDLUnresolvedIdentifier(location, name, allowForbidden=True) + retType = IDLWrapperType(parentInterface.location, parentInterface) + IDLMethod.__init__(self, location, identifier, retType, self._initArgs, + static=True) + self._inited = True; + # Propagate through whatever extended attributes we already had + self.addExtendedAttributes(self._initExtendedAttrs) + self._initExtendedAttrs = [] + # Constructors are always NewObject. Whether they throw or not is + # indicated by [Throws] annotations in the usual way. + self.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("NewObject",))]) + + +class IDLIncludesStatement(IDLObject): + def __init__(self, location, interface, mixin): IDLObject.__init__(self, location) - self.implementor = implementor - self.implementee = implementee + self.interface = interface + self.mixin = mixin self._finished = False def finish(self, scope): if self._finished: return - assert(isinstance(self.implementor, IDLIdentifierPlaceholder)) - assert(isinstance(self.implementee, IDLIdentifierPlaceholder)) - implementor = self.implementor.finish(scope) - implementee = self.implementee.finish(scope) - # NOTE: we depend on not setting self.implementor and - # self.implementee here to keep track of the original + self._finished = True + assert(isinstance(self.interface, IDLIdentifierPlaceholder)) + assert(isinstance(self.mixin, IDLIdentifierPlaceholder)) + interface = self.interface.finish(scope) + mixin = self.mixin.finish(scope) + # NOTE: we depend on not setting self.interface and + # self.mixin here to keep track of the original # locations. - if not isinstance(implementor, IDLInterface): - raise WebIDLError("Left-hand side of 'implements' is not an " - "interface", - [self.implementor.location]) - if implementor.isCallback(): - raise WebIDLError("Left-hand side of 'implements' is a callback " - "interface", - [self.implementor.location]) - if not isinstance(implementee, IDLInterface): - raise WebIDLError("Right-hand side of 'implements' is not an " + if not isinstance(interface, IDLInterface): + raise WebIDLError("Left-hand side of 'includes' is not an " "interface", - [self.implementee.location]) - if implementee.isCallback(): - raise WebIDLError("Right-hand side of 'implements' is a callback " + [self.interface.location, interface.location]) + if interface.isCallback(): + raise WebIDLError("Left-hand side of 'includes' is a callback " "interface", - [self.implementee.location]) - implementor.addImplementedInterface(implementee) - self.implementor = implementor - self.implementee = implementee + [self.interface.location, interface.location]) + if not isinstance(mixin, IDLInterfaceMixin): + raise WebIDLError("Right-hand side of 'includes' is not an " + "interface mixin", + [self.mixin.location, mixin.location]) + + mixin.actualExposureGlobalNames.update(interface._exposureGlobalNames) + + interface.addIncludedMixin(mixin) + self.interface = interface + self.mixin = mixin def validate(self): pass def addExtendedAttributes(self, attrs): - assert len(attrs) == 0 - + if len(attrs) != 0: + raise WebIDLError("There are no extended attributes that are " + "allowed on includes statements", + [attrs[0].location, self.location]) class IDLExtendedAttribute(IDLObject): """ @@ -5028,6 +5739,7 @@ class Tokenizer(object): "FLOATLITERAL", "IDENTIFIER", "STRING", + "COMMENTS", "WHITESPACE", "OTHER" ] @@ -5051,7 +5763,7 @@ class Tokenizer(object): return t def t_IDENTIFIER(self, t): - r'[A-Z_a-z][0-9A-Z_a-z-]*' + r'[_-]?[A-Za-z][0-9A-Z_a-z-]*' t.type = self.keywords.get(t.value, 'IDENTIFIER') return t @@ -5060,8 +5772,12 @@ class Tokenizer(object): t.value = t.value[1:-1] return t + def t_COMMENTS(self, t): + r'(\/\*(.|\n)*?\*\/)|(\/\/.*)' + pass + def t_WHITESPACE(self, t): - r'[\t\n\r ]+|[\t\n\r ]*((//[^\n]*|/\*.*?\*/)[\t\n\r ]*)+' + r'[\t\n\r ]+' pass def t_ELLIPSIS(self, t): @@ -5075,22 +5791,21 @@ class Tokenizer(object): return t keywords = { - "module": "MODULE", "interface": "INTERFACE", "partial": "PARTIAL", + "mixin": "MIXIN", "dictionary": "DICTIONARY", "exception": "EXCEPTION", "enum": "ENUM", "callback": "CALLBACK", "typedef": "TYPEDEF", - "implements": "IMPLEMENTS", + "includes": "INCLUDES", "const": "CONST", "null": "NULL", "true": "TRUE", "false": "FALSE", "serializer": "SERIALIZER", "stringifier": "STRINGIFIER", - "jsonifier": "JSONIFIER", "unrestricted": "UNRESTRICTED", "attribute": "ATTRIBUTE", "readonly": "READONLY", @@ -5098,16 +5813,16 @@ class Tokenizer(object): "static": "STATIC", "getter": "GETTER", "setter": "SETTER", - "creator": "CREATOR", "deleter": "DELETER", "legacycaller": "LEGACYCALLER", "optional": "OPTIONAL", "...": "ELLIPSIS", "::": "SCOPE", - "Date": "DATE", "DOMString": "DOMSTRING", "ByteString": "BYTESTRING", "USVString": "USVSTRING", + "JSString": "JSSTRING", + "UTF8String": "UTF8STRING", "any": "ANY", "boolean": "BOOLEAN", "byte": "BYTE", @@ -5119,7 +5834,7 @@ class Tokenizer(object): "Promise": "PROMISE", "required": "REQUIRED", "sequence": "SEQUENCE", - "MozMap": "MOZMAP", + "record": "RECORD", "short": "SHORT", "unsigned": "UNSIGNED", "void": "VOID", @@ -5137,15 +5852,18 @@ class Tokenizer(object): "<": "LT", ">": "GT", "ArrayBuffer": "ARRAYBUFFER", - "SharedArrayBuffer": "SHAREDARRAYBUFFER", "or": "OR", "maplike": "MAPLIKE", "setlike": "SETLIKE", "iterable": "ITERABLE", - "namespace": "NAMESPACE" + "namespace": "NAMESPACE", + "ReadableStream": "READABLESTREAM", + "constructor": "CONSTRUCTOR", + "symbol": "SYMBOL", + "async": "ASYNC", } - tokens.extend(keywords.values()) + tokens.extend(list(keywords.values())) def t_error(self, t): raise WebIDLError("Unrecognized Input", @@ -5154,23 +5872,21 @@ class Tokenizer(object): lexpos=self.lexer.lexpos, filename=self.filename)]) - def __init__(self, outputdir, lexer=None): + def __init__(self, lexer=None): if lexer: self.lexer = lexer else: - self.lexer = lex.lex(object=self, - outputdir=outputdir, - lextab='webidllex', - reflags=re.DOTALL) + self.lexer = lex.lex(object=self) class SqueakyCleanLogger(object): errorWhitelist = [ - # Web IDL defines the WHITESPACE token, but doesn't actually + # Web IDL defines the WHITESPACE and COMMENTS token, but doesn't actually # use it ... so far. "Token 'WHITESPACE' defined, but not used", - # And that means we have an unused token - "There is 1 unused token", + "Token 'COMMENTS' defined, but not used", + # And that means we have unused tokens + "There are 2 unused tokens", # Web IDL defines a OtherOrComma rule that's only used in # ExtendedAttributeInner, which we don't use yet. "Rule 'OtherOrComma' defined, but not used", @@ -5238,21 +5954,21 @@ class Parser(Tokenizer): def p_Definition(self, p): """ - Definition : CallbackOrInterface + Definition : CallbackOrInterfaceOrMixin | Namespace | Partial | Dictionary | Exception | Enum | Typedef - | ImplementsStatement + | IncludesStatement """ p[0] = p[1] assert p[1] # We might not have implemented something ... - def p_CallbackOrInterfaceCallback(self, p): + def p_CallbackOrInterfaceOrMixinCallback(self, p): """ - CallbackOrInterface : CALLBACK CallbackRestOrInterface + CallbackOrInterfaceOrMixin : CALLBACK CallbackRestOrInterface """ if p[2].isInterface(): assert isinstance(p[2], IDLInterface) @@ -5260,16 +5976,17 @@ class Parser(Tokenizer): p[0] = p[2] - def p_CallbackOrInterfaceInterface(self, p): + def p_CallbackOrInterfaceOrMixinInterfaceOrMixin(self, p): """ - CallbackOrInterface : Interface + CallbackOrInterfaceOrMixin : INTERFACE InterfaceOrMixin """ - p[0] = p[1] + p[0] = p[2] def p_CallbackRestOrInterface(self, p): """ CallbackRestOrInterface : CallbackRest - | Interface + | CallbackConstructorRest + | CallbackInterface """ assert p[1] p[0] = p[1] @@ -5277,9 +5994,10 @@ class Parser(Tokenizer): def handleNonPartialObject(self, location, identifier, constructor, constructorArgs, nonPartialArgs): """ - This handles non-partial objects (interfaces and namespaces) by - checking for an existing partial object, and promoting it to - non-partial as needed. The return value is the non-partial object. + This handles non-partial objects (interfaces, namespaces and + dictionaries) by checking for an existing partial object, and promoting + it to non-partial as needed. The return value is the non-partial + object. constructorArgs are all the args for the constructor except the last one: isKnownNonPartial. @@ -5301,7 +6019,7 @@ class Parser(Tokenizer): [location, existingObj.location]) existingObj.setNonPartial(*nonPartialArgs) return existingObj - except Exception, ex: + except Exception as ex: if isinstance(ex, WebIDLError): raise ex pass @@ -5309,14 +6027,27 @@ class Parser(Tokenizer): # True for isKnownNonPartial return constructor(*(constructorArgs + [True])) - def p_Interface(self, p): + def p_InterfaceOrMixin(self, p): + """ + InterfaceOrMixin : InterfaceRest + | MixinRest + """ + p[0] = p[1] + + def p_CallbackInterface(self, p): + """ + CallbackInterface : INTERFACE InterfaceRest """ - Interface : INTERFACE IDENTIFIER Inheritance LBRACE InterfaceMembers RBRACE SEMICOLON + p[0] = p[2] + + def p_InterfaceRest(self, p): + """ + InterfaceRest : IDENTIFIER Inheritance LBRACE InterfaceMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) - identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) - members = p[5] - parent = p[3] + identifier = IDLUnresolvedIdentifier(location, p[1]) + members = p[4] + parent = p[2] p[0] = self.handleNonPartialObject( location, identifier, IDLInterface, @@ -5325,10 +6056,10 @@ class Parser(Tokenizer): def p_InterfaceForwardDecl(self, p): """ - Interface : INTERFACE IDENTIFIER SEMICOLON + InterfaceRest : IDENTIFIER SEMICOLON """ location = self.getLocation(p, 1) - identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + identifier = IDLUnresolvedIdentifier(location, p[1]) try: if self.globalScope()._lookupIdentifier(identifier): @@ -5339,13 +6070,26 @@ class Parser(Tokenizer): "%s and %s" % (identifier.name, p[0]), [location, p[0].location]) return - except Exception, ex: + except Exception as ex: if isinstance(ex, WebIDLError): raise ex pass p[0] = IDLExternalInterface(location, self.globalScope(), identifier) + def p_MixinRest(self, p): + """ + MixinRest : MIXIN IDENTIFIER LBRACE MixinMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[4] + + p[0] = self.handleNonPartialObject( + location, identifier, IDLInterfaceMixin, + [location, self.globalScope(), identifier, members], + [location, members]) + def p_Namespace(self, p): """ Namespace : NAMESPACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON @@ -5365,10 +6109,16 @@ class Parser(Tokenizer): """ p[0] = p[2] + def p_PartialDefinitionInterface(self, p): + """ + PartialDefinition : INTERFACE PartialInterfaceOrPartialMixin + """ + p[0] = p[2] + def p_PartialDefinition(self, p): """ - PartialDefinition : PartialInterface - | PartialNamespace + PartialDefinition : PartialNamespace + | PartialDictionary """ p[0] = p[1] @@ -5376,17 +6126,17 @@ class Parser(Tokenizer): nonPartialConstructorArgs, partialConstructorArgs): """ - This handles partial objects (interfaces and namespaces) by checking for - an existing non-partial object, and adding ourselves to it as needed. - The return value is our partial object. For now we just use - IDLPartialInterfaceOrNamespace for partial objects. + This handles partial objects (interfaces, namespaces and dictionaries) + by checking for an existing non-partial object, and adding ourselves to + it as needed. The return value is our partial object. We use + IDLPartialInterfaceOrNamespace for partial interfaces or namespaces, + and IDLPartialDictionary for partial dictionaries. nonPartialConstructorArgs are all the args for the non-partial constructor except the last two: members and isKnownNonPartial. - partialConstructorArgs are the arguments for the - IDLPartialInterfaceOrNamespace constructor, except the last one (the - non-partial object). + partialConstructorArgs are the arguments for the partial object + constructor, except the last one (the non-partial object). """ # The name of the class starts with "IDL", so strip that off. # Also, starts with a capital letter after that, so nix that @@ -5402,7 +6152,7 @@ class Parser(Tokenizer): "non-%s object" % (prettyname, prettyname), [location, nonPartialObject.location]) - except Exception, ex: + except Exception as ex: if isinstance(ex, WebIDLError): raise ex pass @@ -5410,24 +6160,55 @@ class Parser(Tokenizer): if not nonPartialObject: nonPartialObject = nonPartialConstructor( # No members, False for isKnownNonPartial - *(nonPartialConstructorArgs + [[], False])) - partialInterface = IDLPartialInterfaceOrNamespace( - *(partialConstructorArgs + [nonPartialObject])) - return partialInterface + *(nonPartialConstructorArgs), members=[], isKnownNonPartial=False) + + partialObject = None + if isinstance(nonPartialObject, IDLDictionary): + partialObject = IDLPartialDictionary( + *(partialConstructorArgs + [nonPartialObject])) + elif isinstance(nonPartialObject, (IDLInterface, IDLInterfaceMixin, IDLNamespace)): + partialObject = IDLPartialInterfaceOrNamespace( + *(partialConstructorArgs + [nonPartialObject])) + else: + raise WebIDLError("Unknown partial object type %s" % + type(partialObject), + [location]) + + return partialObject - def p_PartialInterface(self, p): + def p_PartialInterfaceOrPartialMixin(self, p): """ - PartialInterface : INTERFACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON + PartialInterfaceOrPartialMixin : PartialInterfaceRest + | PartialMixinRest + """ + p[0] = p[1] + + def p_PartialInterfaceRest(self, p): + """ + PartialInterfaceRest : IDENTIFIER LBRACE PartialInterfaceMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) - identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) - members = p[4] + identifier = IDLUnresolvedIdentifier(location, p[1]) + members = p[3] p[0] = self.handlePartialObject( location, identifier, IDLInterface, [location, self.globalScope(), identifier, None], [location, identifier, members]) + def p_PartialMixinRest(self, p): + """ + PartialMixinRest : MIXIN IDENTIFIER LBRACE MixinMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[4] + + p[0] = self.handlePartialObject( + location, identifier, IDLInterfaceMixin, + [location, self.globalScope(), identifier], + [location, identifier, members]) + def p_PartialNamespace(self, p): """ PartialNamespace : NAMESPACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON @@ -5441,6 +6222,19 @@ class Parser(Tokenizer): [location, self.globalScope(), identifier], [location, identifier, members]) + def p_PartialDictionary(self, p): + """ + PartialDictionary : DICTIONARY IDENTIFIER LBRACE DictionaryMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[4] + + p[0] = self.handlePartialObject( + location, identifier, IDLDictionary, + [location, self.globalScope(), identifier], + [location, identifier, members]) + def p_Inheritance(self, p): """ Inheritance : COLON ScopedName @@ -5457,7 +6251,7 @@ class Parser(Tokenizer): """ InterfaceMembers : ExtendedAttributeList InterfaceMember InterfaceMembers """ - p[0] = [p[2]] if p[2] else [] + p[0] = [p[2]] assert not p[1] or p[2] p[2].addExtendedAttributes(p[1]) @@ -5472,8 +6266,64 @@ class Parser(Tokenizer): def p_InterfaceMember(self, p): """ - InterfaceMember : Const - | AttributeOrOperationOrMaplikeOrSetlikeOrIterable + InterfaceMember : PartialInterfaceMember + | Constructor + """ + p[0] = p[1] + + def p_Constructor(self, p): + """ + Constructor : CONSTRUCTOR LPAREN ArgumentList RPAREN SEMICOLON + """ + p[0] = IDLConstructor(self.getLocation(p, 1), p[3], "constructor") + + def p_PartialInterfaceMembers(self, p): + """ + PartialInterfaceMembers : ExtendedAttributeList PartialInterfaceMember PartialInterfaceMembers + """ + p[0] = [p[2]] + + assert not p[1] or p[2] + p[2].addExtendedAttributes(p[1]) + + p[0].extend(p[3]) + + def p_PartialInterfaceMembersEmpty(self, p): + """ + PartialInterfaceMembers : + """ + p[0] = [] + + def p_PartialInterfaceMember(self, p): + """ + PartialInterfaceMember : Const + | AttributeOrOperationOrMaplikeOrSetlikeOrIterable + """ + p[0] = p[1] + + + def p_MixinMembersEmpty(self, p): + """ + MixinMembers : + """ + p[0] = [] + + def p_MixinMembers(self, p): + """ + MixinMembers : ExtendedAttributeList MixinMember MixinMembers + """ + p[0] = [p[2]] + + assert not p[1] or p[2] + p[2].addExtendedAttributes(p[1]) + + p[0].extend(p[3]) + + def p_MixinMember(self, p): + """ + MixinMember : Const + | Attribute + | Operation """ p[0] = p[1] @@ -5495,31 +6345,42 @@ class Parser(Tokenizer): # We're at the end of the list p[0] = [] return - # Add our extended attributes p[2].addExtendedAttributes(p[1]) p[0] = [p[2]] p[0].extend(p[3]) - def p_DictionaryMember(self, p): + def p_DictionaryMemberRequired(self, p): """ - DictionaryMember : Required Type IDENTIFIER Default SEMICOLON + DictionaryMember : REQUIRED TypeWithExtendedAttributes IDENTIFIER SEMICOLON """ - # These quack a lot like optional arguments, so just treat them that way. + # These quack a lot like required arguments, so just treat them that way. t = p[2] assert isinstance(t, IDLType) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3]) - defaultValue = p[4] - optional = not p[1] - - if not optional and defaultValue: - raise WebIDLError("Required dictionary members can't have a default value.", - [self.getLocation(p, 4)]) p[0] = IDLArgument(self.getLocation(p, 3), identifier, t, - optional=optional, - defaultValue=defaultValue, variadic=False, + optional=False, + defaultValue=None, variadic=False, dictionaryMember=True) + def p_DictionaryMember(self, p): + """ + DictionaryMember : Type IDENTIFIER Default SEMICOLON + """ + # These quack a lot like optional arguments, so just treat them that way. + t = p[1] + assert isinstance(t, IDLType) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + defaultValue = p[3] + + # Any attributes that precede this may apply to the type, so + # we configure the argument to forward type attributes down instead of producing + # a parse error + p[0] = IDLArgument(self.getLocation(p, 2), identifier, t, + optional=True, + defaultValue=defaultValue, variadic=False, + dictionaryMember=True, allowTypeAttributes=True) + def p_Default(self, p): """ Default : EQUALS DefaultValue @@ -5534,12 +6395,23 @@ class Parser(Tokenizer): """ DefaultValue : ConstValue | LBRACKET RBRACKET + | LBRACE RBRACE """ if len(p) == 2: p[0] = p[1] else: - assert len(p) == 3 # Must be [] - p[0] = IDLEmptySequenceValue(self.getLocation(p, 1)) + assert len(p) == 3 # Must be [] or {} + if p[1] == "[": + p[0] = IDLEmptySequenceValue(self.getLocation(p, 1)) + else: + assert p[1] == "{" + p[0] = IDLDefaultDictionaryValue(self.getLocation(p, 1)) + + def p_DefaultValueNull(self, p): + """ + DefaultValue : NULL + """ + p[0] = IDLNullValue(self.getLocation(p, 1)) def p_Exception(self, p): """ @@ -5596,7 +6468,15 @@ class Parser(Tokenizer): """ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) p[0] = IDLCallback(self.getLocation(p, 1), self.globalScope(), - identifier, p[3], p[5]) + identifier, p[3], p[5], isConstructor=False) + + def p_CallbackConstructorRest(self, p): + """ + CallbackConstructorRest : CONSTRUCTOR IDENTIFIER EQUALS ReturnType LPAREN ArgumentList RPAREN SEMICOLON + """ + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + p[0] = IDLCallback(self.getLocation(p, 2), self.globalScope(), + identifier, p[4], p[6], isConstructor=True) def p_ExceptionMembers(self, p): """ @@ -5607,21 +6487,20 @@ class Parser(Tokenizer): def p_Typedef(self, p): """ - Typedef : TYPEDEF Type IDENTIFIER SEMICOLON + Typedef : TYPEDEF TypeWithExtendedAttributes IDENTIFIER SEMICOLON """ typedef = IDLTypedef(self.getLocation(p, 1), self.globalScope(), p[2], p[3]) p[0] = typedef - def p_ImplementsStatement(self, p): + def p_IncludesStatement(self, p): """ - ImplementsStatement : ScopedName IMPLEMENTS ScopedName SEMICOLON + IncludesStatement : ScopedName INCLUDES ScopedName SEMICOLON """ - assert(p[2] == "implements") - implementor = IDLIdentifierPlaceholder(self.getLocation(p, 1), p[1]) - implementee = IDLIdentifierPlaceholder(self.getLocation(p, 3), p[3]) - p[0] = IDLImplementsStatement(self.getLocation(p, 1), implementor, - implementee) + assert(p[2] == "includes") + interface = IDLIdentifierPlaceholder(self.getLocation(p, 1), p[1]) + mixin = IDLIdentifierPlaceholder(self.getLocation(p, 3), p[3]) + p[0] = IDLIncludesStatement(self.getLocation(p, 1), interface, mixin) def p_Const(self, p): """ @@ -5670,12 +6549,6 @@ class Parser(Tokenizer): stringType = BuiltinTypes[IDLBuiltinType.Types.domstring] p[0] = IDLValue(location, stringType, p[1]) - def p_ConstValueNull(self, p): - """ - ConstValue : NULL - """ - p[0] = IDLNullValue(self.getLocation(p, 1)) - def p_BooleanLiteralTrue(self, p): """ BooleanLiteral : TRUE @@ -5700,8 +6573,8 @@ class Parser(Tokenizer): def p_Iterable(self, p): """ - Iterable : ITERABLE LT Type GT SEMICOLON - | ITERABLE LT Type COMMA Type GT SEMICOLON + Iterable : ITERABLE LT TypeWithExtendedAttributes GT SEMICOLON + | ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON """ location = self.getLocation(p, 2) identifier = IDLUnresolvedIdentifier(location, "__iterable", @@ -5717,7 +6590,7 @@ class Parser(Tokenizer): def p_Setlike(self, p): """ - Setlike : ReadOnly SETLIKE LT Type GT SEMICOLON + Setlike : ReadOnly SETLIKE LT TypeWithExtendedAttributes GT SEMICOLON """ readonly = p[1] maplikeOrSetlikeType = p[2] @@ -5731,7 +6604,7 @@ class Parser(Tokenizer): def p_Maplike(self, p): """ - Maplike : ReadOnly MAPLIKE LT Type COMMA Type GT SEMICOLON + Maplike : ReadOnly MAPLIKE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON """ readonly = p[1] maplikeOrSetlikeType = p[2] @@ -5769,7 +6642,7 @@ class Parser(Tokenizer): def p_AttributeRest(self, p): """ - AttributeRest : ReadOnly ATTRIBUTE Type AttributeName SEMICOLON + AttributeRest : ReadOnly ATTRIBUTE TypeWithExtendedAttributes AttributeName SEMICOLON """ location = self.getLocation(p, 2) readonly = p[1] @@ -5812,13 +6685,12 @@ class Parser(Tokenizer): getter = True if IDLMethod.Special.Getter in p[1] else False setter = True if IDLMethod.Special.Setter in p[1] else False - creator = True if IDLMethod.Special.Creator in p[1] else False deleter = True if IDLMethod.Special.Deleter in p[1] else False legacycaller = True if IDLMethod.Special.LegacyCaller in p[1] else False if getter or deleter: - if setter or creator: - raise WebIDLError("getter and deleter are incompatible with setter and creator", + if setter: + raise WebIDLError("getter and deleter are incompatible with setter", [self.getLocation(p, 1)]) (returnType, identifier, arguments) = p[2] @@ -5837,6 +6709,9 @@ class Parser(Tokenizer): specialType = IDLMethod.NamedOrIndexed.Named elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]: specialType = IDLMethod.NamedOrIndexed.Indexed + if deleter: + raise WebIDLError("There is no such thing as an indexed deleter.", + [self.getLocation(p, 1)]) else: raise WebIDLError("%s has wrong argument type (must be DOMString or UnsignedLong)" % ("getter" if getter else "deleter"), @@ -5850,10 +6725,9 @@ class Parser(Tokenizer): if returnType.isVoid(): raise WebIDLError("getter cannot have void return type", [self.getLocation(p, 2)]) - if setter or creator: + if setter: if len(arguments) != 2: - raise WebIDLError("%s has wrong number of arguments" % - ("setter" if setter else "creator"), + raise WebIDLError("setter has wrong number of arguments", [self.getLocation(p, 2)]) argType = arguments[0].type if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]: @@ -5861,18 +6735,15 @@ class Parser(Tokenizer): elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]: specialType = IDLMethod.NamedOrIndexed.Indexed else: - raise WebIDLError("%s has wrong argument type (must be DOMString or UnsignedLong)" % - ("setter" if setter else "creator"), + raise WebIDLError("settter has wrong argument type (must be DOMString or UnsignedLong)", [arguments[0].location]) if arguments[0].optional or arguments[0].variadic: - raise WebIDLError("%s cannot have %s argument" % - ("setter" if setter else "creator", - "optional" if arguments[0].optional else "variadic"), + raise WebIDLError("setter cannot have %s argument" % + ("optional" if arguments[0].optional else "variadic"), [arguments[0].location]) if arguments[1].optional or arguments[1].variadic: - raise WebIDLError("%s cannot have %s argument" % - ("setter" if setter else "creator", - "optional" if arguments[1].optional else "variadic"), + raise WebIDLError("setter cannot have %s argument" % + ("optional" if arguments[1].optional else "variadic"), [arguments[1].location]) if stringifier: @@ -5885,7 +6756,7 @@ class Parser(Tokenizer): # identifier might be None. This is only permitted for special methods. if not identifier: - if (not getter and not setter and not creator and + if (not getter and not setter and not deleter and not legacycaller and not stringifier): raise WebIDLError("Identifier required for non-special methods", [self.getLocation(p, 2)]) @@ -5893,19 +6764,18 @@ class Parser(Tokenizer): location = BuiltinLocation("<auto-generated-identifier>") identifier = IDLUnresolvedIdentifier( location, - "__%s%s%s%s%s%s%s" % + "__%s%s%s%s%s%s" % ("named" if specialType == IDLMethod.NamedOrIndexed.Named else "indexed" if specialType == IDLMethod.NamedOrIndexed.Indexed else "", "getter" if getter else "", "setter" if setter else "", "deleter" if deleter else "", - "creator" if creator else "", "legacycaller" if legacycaller else "", "stringifier" if stringifier else ""), allowDoubleUnderscore=True) method = IDLMethod(self.getLocation(p, 2), identifier, returnType, arguments, - static=static, getter=getter, setter=setter, creator=creator, + static=static, getter=getter, setter=setter, deleter=deleter, specialType=specialType, legacycaller=legacycaller, stringifier=stringifier) p[0] = method @@ -5924,19 +6794,6 @@ class Parser(Tokenizer): stringifier=True) p[0] = method - def p_Jsonifier(self, p): - """ - Operation : JSONIFIER SEMICOLON - """ - identifier = IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), - "__jsonifier", allowDoubleUnderscore=True) - method = IDLMethod(self.getLocation(p, 1), - identifier, - returnType=BuiltinTypes[IDLBuiltinType.Types.object], - arguments=[], - jsonifier=True) - p[0] = method - def p_QualifierStatic(self, p): """ Qualifier : STATIC @@ -5981,12 +6838,6 @@ class Parser(Tokenizer): """ p[0] = IDLMethod.Special.Setter - def p_SpecialCreator(self, p): - """ - Special : CREATOR - """ - p[0] = IDLMethod.Special.Creator - def p_SpecialDeleter(self, p): """ Special : DELETER @@ -6045,95 +6896,105 @@ class Parser(Tokenizer): def p_Argument(self, p): """ - Argument : ExtendedAttributeList Optional Type Ellipsis ArgumentName Default + Argument : ExtendedAttributeList ArgumentRest """ - t = p[3] + p[0] = p[2] + p[0].addExtendedAttributes(p[1]) + + def p_ArgumentRestOptional(self, p): + """ + ArgumentRest : OPTIONAL TypeWithExtendedAttributes ArgumentName Default + """ + t = p[2] assert isinstance(t, IDLType) - identifier = IDLUnresolvedIdentifier(self.getLocation(p, 5), p[5]) + # Arg names can be reserved identifiers + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3], + allowForbidden=True) - optional = p[2] - variadic = p[4] - defaultValue = p[6] + defaultValue = p[4] - if not optional and defaultValue: - raise WebIDLError("Mandatory arguments can't have a default value.", - [self.getLocation(p, 6)]) # We can't test t.isAny() here and give it a default value as needed, # since at this point t is not a fully resolved type yet (e.g. it might # be a typedef). We'll handle the 'any' case in IDLArgument.complete. - if variadic: - if optional: - raise WebIDLError("Variadic arguments should not be marked optional.", - [self.getLocation(p, 2)]) - optional = variadic + p[0] = IDLArgument(self.getLocation(p, 3), identifier, t, True, defaultValue, False) - p[0] = IDLArgument(self.getLocation(p, 5), identifier, t, optional, defaultValue, variadic) - p[0].addExtendedAttributes(p[1]) + def p_ArgumentRest(self, p): + """ + ArgumentRest : Type Ellipsis ArgumentName + """ + t = p[1] + assert isinstance(t, IDLType) + # Arg names can be reserved identifiers + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3], + allowForbidden=True) + + variadic = p[2] + + # We can't test t.isAny() here and give it a default value as needed, + # since at this point t is not a fully resolved type yet (e.g. it might + # be a typedef). We'll handle the 'any' case in IDLArgument.complete. + + # variadic implies optional + # Any attributes that precede this may apply to the type, so + # we configure the argument to forward type attributes down instead of producing + # a parse error + p[0] = IDLArgument(self.getLocation(p, 3), identifier, t, variadic, None, variadic, allowTypeAttributes=True) def p_ArgumentName(self, p): """ ArgumentName : IDENTIFIER - | ATTRIBUTE - | CALLBACK - | CONST - | CREATOR - | DELETER - | DICTIONARY - | ENUM - | EXCEPTION - | GETTER - | IMPLEMENTS - | INHERIT - | INTERFACE - | ITERABLE - | LEGACYCALLER - | MAPLIKE - | PARTIAL - | REQUIRED - | SERIALIZER - | SETLIKE - | SETTER - | STATIC - | STRINGIFIER - | JSONIFIER - | TYPEDEF - | UNRESTRICTED - | NAMESPACE + | ArgumentNameKeyword """ p[0] = p[1] - def p_AttributeName(self, p): - """ - AttributeName : IDENTIFIER - | REQUIRED + def p_ArgumentNameKeyword(self, p): + """ + ArgumentNameKeyword : ASYNC + | ATTRIBUTE + | CALLBACK + | CONST + | CONSTRUCTOR + | DELETER + | DICTIONARY + | ENUM + | EXCEPTION + | GETTER + | INCLUDES + | INHERIT + | INTERFACE + | ITERABLE + | LEGACYCALLER + | MAPLIKE + | MIXIN + | NAMESPACE + | PARTIAL + | READONLY + | REQUIRED + | SERIALIZER + | SETLIKE + | SETTER + | STATIC + | STRINGIFIER + | TYPEDEF + | UNRESTRICTED """ p[0] = p[1] - def p_Optional(self, p): - """ - Optional : OPTIONAL - """ - p[0] = True - - def p_OptionalEmpty(self, p): - """ - Optional : - """ - p[0] = False - - def p_Required(self, p): + def p_AttributeName(self, p): """ - Required : REQUIRED + AttributeName : IDENTIFIER + | AttributeNameKeyword """ - p[0] = True + p[0] = p[1] - def p_RequiredEmpty(self, p): + def p_AttributeNameKeyword(self, p): """ - Required : + AttributeNameKeyword : ASYNC + | REQUIRED """ - p[0] = False + p[0] = p[1] def p_Ellipsis(self, p): """ @@ -6218,43 +7079,32 @@ class Parser(Tokenizer): | EQUALS | GT | QUESTIONMARK - | DATE | DOMSTRING | BYTESTRING | USVSTRING + | UTF8STRING + | JSSTRING + | PROMISE | ANY - | ATTRIBUTE | BOOLEAN | BYTE - | LEGACYCALLER - | CONST - | CREATOR - | DELETER | DOUBLE - | EXCEPTION | FALSE | FLOAT - | GETTER - | IMPLEMENTS - | INHERIT - | INTERFACE | LONG - | MODULE | NULL | OBJECT | OCTET + | OR | OPTIONAL + | RECORD | SEQUENCE - | MOZMAP - | SETTER | SHORT - | STATIC - | STRINGIFIER - | JSONIFIER + | SYMBOL | TRUE - | TYPEDEF | UNSIGNED | VOID + | ArgumentNameKeyword """ pass @@ -6277,9 +7127,15 @@ class Parser(Tokenizer): """ p[0] = self.handleNullable(p[1], p[2]) - def p_SingleTypeNonAnyType(self, p): + def p_TypeWithExtendedAttributes(self, p): """ - SingleType : NonAnyType + TypeWithExtendedAttributes : ExtendedAttributeList Type + """ + p[0] = p[2].withExtendedAttributes(p[1]) + + def p_SingleTypeDistinguishableType(self, p): + """ + SingleType : DistinguishableType """ p[0] = p[1] @@ -6289,6 +7145,14 @@ class Parser(Tokenizer): """ p[0] = BuiltinTypes[IDLBuiltinType.Types.any] + # Note: Promise<void> is allowed, so we want to parametrize on ReturnType, + # not Type. Promise types can't be null, hence no "Null" in there. + def p_SingleTypePromiseType(self, p): + """ + SingleType : PROMISE LT ReturnType GT + """ + p[0] = IDLPromiseType(self.getLocation(p, 1), p[3]) + def p_UnionType(self, p): """ UnionType : LPAREN UnionMemberType OR UnionMemberType UnionMemberTypes RPAREN @@ -6297,11 +7161,11 @@ class Parser(Tokenizer): types.extend(p[5]) p[0] = IDLUnionType(self.getLocation(p, 1), types) - def p_UnionMemberTypeNonAnyType(self, p): + def p_UnionMemberTypeDistinguishableType(self, p): """ - UnionMemberType : NonAnyType + UnionMemberType : ExtendedAttributeList DistinguishableType """ - p[0] = p[1] + p[0] = p[2].withExtendedAttributes(p[1]) def p_UnionMemberType(self, p): """ @@ -6322,55 +7186,50 @@ class Parser(Tokenizer): """ p[0] = [] - def p_NonAnyType(self, p): + def p_DistinguishableType(self, p): """ - NonAnyType : PrimitiveOrStringType Null - | ARRAYBUFFER Null - | SHAREDARRAYBUFFER Null - | OBJECT Null + DistinguishableType : PrimitiveType Null + | ARRAYBUFFER Null + | READABLESTREAM Null + | OBJECT Null """ if p[1] == "object": type = BuiltinTypes[IDLBuiltinType.Types.object] elif p[1] == "ArrayBuffer": type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer] - elif p[1] == "SharedArrayBuffer": - type = BuiltinTypes[IDLBuiltinType.Types.SharedArrayBuffer] + elif p[1] == "ReadableStream": + type = BuiltinTypes[IDLBuiltinType.Types.ReadableStream] else: type = BuiltinTypes[p[1]] p[0] = self.handleNullable(type, p[2]) - def p_NonAnyTypeSequenceType(self, p): + def p_DistinguishableTypeStringType(self, p): """ - NonAnyType : SEQUENCE LT Type GT Null + DistinguishableType : StringType Null """ - innerType = p[3] - type = IDLSequenceType(self.getLocation(p, 1), innerType) - p[0] = self.handleNullable(type, p[5]) + p[0] = self.handleNullable(p[1], p[2]) - # Note: Promise<void> is allowed, so we want to parametrize on - # ReturnType, not Type. Also, we want this to end up picking up - # the Promise interface for now, hence the games with IDLUnresolvedType. - def p_NonAnyTypePromiseType(self, p): + def p_DistinguishableTypeSequenceType(self, p): """ - NonAnyType : PROMISE LT ReturnType GT Null + DistinguishableType : SEQUENCE LT TypeWithExtendedAttributes GT Null """ innerType = p[3] - promiseIdent = IDLUnresolvedIdentifier(self.getLocation(p, 1), "Promise") - type = IDLUnresolvedType(self.getLocation(p, 1), promiseIdent, p[3]) + type = IDLSequenceType(self.getLocation(p, 1), innerType) p[0] = self.handleNullable(type, p[5]) - def p_NonAnyTypeMozMapType(self, p): + def p_DistinguishableTypeRecordType(self, p): """ - NonAnyType : MOZMAP LT Type GT Null + DistinguishableType : RECORD LT StringType COMMA TypeWithExtendedAttributes GT Null """ - innerType = p[3] - type = IDLMozMapType(self.getLocation(p, 1), innerType) - p[0] = self.handleNullable(type, p[5]) + keyType = p[3] + valueType = p[5] + type = IDLRecordType(self.getLocation(p, 1), keyType, valueType) + p[0] = self.handleNullable(type, p[7]) - def p_NonAnyTypeScopedName(self, p): + def p_DistinguishableTypeScopedName(self, p): """ - NonAnyType : ScopedName Null + DistinguishableType : ScopedName Null """ assert isinstance(p[1], IDLUnresolvedIdentifier) @@ -6400,95 +7259,104 @@ class Parser(Tokenizer): type = IDLUnresolvedType(self.getLocation(p, 1), p[1]) p[0] = self.handleNullable(type, p[2]) - def p_NonAnyTypeDate(self, p): - """ - NonAnyType : DATE Null - """ - p[0] = self.handleNullable(BuiltinTypes[IDLBuiltinType.Types.date], - p[2]) - def p_ConstType(self, p): """ - ConstType : PrimitiveOrStringType Null + ConstType : PrimitiveType """ - type = BuiltinTypes[p[1]] - p[0] = self.handleNullable(type, p[2]) + p[0] = BuiltinTypes[p[1]] def p_ConstTypeIdentifier(self, p): """ - ConstType : IDENTIFIER Null + ConstType : IDENTIFIER """ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) - type = IDLUnresolvedType(self.getLocation(p, 1), identifier) - p[0] = self.handleNullable(type, p[2]) + p[0] = IDLUnresolvedType(self.getLocation(p, 1), identifier) - def p_PrimitiveOrStringTypeUint(self, p): + def p_PrimitiveTypeUint(self, p): """ - PrimitiveOrStringType : UnsignedIntegerType + PrimitiveType : UnsignedIntegerType """ p[0] = p[1] - def p_PrimitiveOrStringTypeBoolean(self, p): + def p_PrimitiveTypeBoolean(self, p): """ - PrimitiveOrStringType : BOOLEAN + PrimitiveType : BOOLEAN """ p[0] = IDLBuiltinType.Types.boolean - def p_PrimitiveOrStringTypeByte(self, p): + def p_PrimitiveTypeByte(self, p): """ - PrimitiveOrStringType : BYTE + PrimitiveType : BYTE """ p[0] = IDLBuiltinType.Types.byte - def p_PrimitiveOrStringTypeOctet(self, p): + def p_PrimitiveTypeOctet(self, p): """ - PrimitiveOrStringType : OCTET + PrimitiveType : OCTET """ p[0] = IDLBuiltinType.Types.octet - def p_PrimitiveOrStringTypeFloat(self, p): + def p_PrimitiveTypeFloat(self, p): """ - PrimitiveOrStringType : FLOAT + PrimitiveType : FLOAT """ p[0] = IDLBuiltinType.Types.float - def p_PrimitiveOrStringTypeUnrestictedFloat(self, p): + def p_PrimitiveTypeUnrestictedFloat(self, p): """ - PrimitiveOrStringType : UNRESTRICTED FLOAT + PrimitiveType : UNRESTRICTED FLOAT """ p[0] = IDLBuiltinType.Types.unrestricted_float - def p_PrimitiveOrStringTypeDouble(self, p): + def p_PrimitiveTypeDouble(self, p): """ - PrimitiveOrStringType : DOUBLE + PrimitiveType : DOUBLE """ p[0] = IDLBuiltinType.Types.double - def p_PrimitiveOrStringTypeUnrestictedDouble(self, p): + def p_PrimitiveTypeUnrestictedDouble(self, p): """ - PrimitiveOrStringType : UNRESTRICTED DOUBLE + PrimitiveType : UNRESTRICTED DOUBLE """ p[0] = IDLBuiltinType.Types.unrestricted_double - def p_PrimitiveOrStringTypeDOMString(self, p): + def p_StringType(self, p): + """ + StringType : BuiltinStringType + """ + p[0] = BuiltinTypes[p[1]] + + def p_BuiltinStringTypeDOMString(self, p): """ - PrimitiveOrStringType : DOMSTRING + BuiltinStringType : DOMSTRING """ p[0] = IDLBuiltinType.Types.domstring - def p_PrimitiveOrStringTypeBytestring(self, p): + def p_BuiltinStringTypeBytestring(self, p): """ - PrimitiveOrStringType : BYTESTRING + BuiltinStringType : BYTESTRING """ p[0] = IDLBuiltinType.Types.bytestring - def p_PrimitiveOrStringTypeUSVString(self, p): + def p_BuiltinStringTypeUSVString(self, p): """ - PrimitiveOrStringType : USVSTRING + BuiltinStringType : USVSTRING """ p[0] = IDLBuiltinType.Types.usvstring + def p_BuiltinStringTypeUTF8String(self, p): + """ + BuiltinStringType : UTF8STRING + """ + p[0] = IDLBuiltinType.Types.utf8string + + def p_BuiltinStringTypeJSString(self, p): + """ + BuiltinStringType : JSSTRING + """ + p[0] = IDLBuiltinType.Types.jsstring + def p_UnsignedIntegerTypeUnsigned(self, p): """ UnsignedIntegerType : UNSIGNED IntegerType @@ -6622,7 +7490,13 @@ class Parser(Tokenizer): IdentifierList : IDENTIFIER Identifiers """ idents = list(p[2]) - idents.insert(0, p[1]) + # This is only used for identifier-list-valued extended attributes, and if + # we're going to restrict to IDENTIFIER here we should at least allow + # escaping with leading '_' as usual for identifiers. + ident = p[1] + if ident[0] == '_': + ident = ident[1:] + idents.insert(0, ident) p[0] = idents def p_IdentifiersList(self, p): @@ -6630,7 +7504,13 @@ class Parser(Tokenizer): Identifiers : COMMA IDENTIFIER Identifiers """ idents = list(p[3]) - idents.insert(0, p[2]) + # This is only used for identifier-list-valued extended attributes, and if + # we're going to restrict to IDENTIFIER here we should at least allow + # escaping with leading '_' as usual for identifiers. + ident = p[2] + if ident[0] == '_': + ident = ident[1:] + idents.insert(0, ident) p[0] = idents def p_IdentifiersEmpty(self, p): @@ -6647,36 +7527,16 @@ class Parser(Tokenizer): raise WebIDLError("invalid syntax", [Location(self.lexer, p.lineno, p.lexpos, self._filename)]) def __init__(self, outputdir='', lexer=None): - Tokenizer.__init__(self, outputdir, lexer) + Tokenizer.__init__(self, lexer) logger = SqueakyCleanLogger() try: - self.parser = yacc.yacc(module=self, - outputdir=outputdir, - tabmodule='webidlyacc', - errorlog=logger, - debug=False - # Pickling the grammar is a speedup in - # some cases (older Python?) but a - # significant slowdown in others. - # We're not pickling for now, until it - # becomes a speedup again. - # , picklefile='WebIDLGrammar.pkl' - ) + self.parser = yacc.yacc(module=self, errorlog=logger, debug=False) finally: logger.reportGrammarErrors() self._globalScope = IDLScope(BuiltinLocation("<Global Scope>"), None, None) - # To make our test harness work, pretend like we have a primary global already. - # Note that we _don't_ set _globalScope.primaryGlobalAttr, - # so we'll still be able to detect multiple PrimaryGlobal extended attributes. - self._globalScope.primaryGlobalName = "FakeTestPrimaryGlobal" - self._globalScope.globalNames.add("FakeTestPrimaryGlobal") - self._globalScope.globalNameMapping["FakeTestPrimaryGlobal"].add("FakeTestPrimaryGlobal") - # And we add the special-cased "System" global name, which - # doesn't have any corresponding interfaces. - self._globalScope.globalNames.add("System") - self._globalScope.globalNameMapping["System"].add("BackstagePass") + self._installBuiltins(self._globalScope) self._productions = [] @@ -6689,8 +7549,8 @@ class Parser(Tokenizer): def _installBuiltins(self, scope): assert isinstance(scope, IDLScope) - # xrange omits the last value. - for x in xrange(IDLBuiltinType.Types.ArrayBuffer, IDLBuiltinType.Types.Float64Array + 1): + # range omits the last value. + for x in range(IDLBuiltinType.Types.ArrayBuffer, IDLBuiltinType.Types.Float64Array + 1): builtin = BuiltinTypes[x] name = builtin.name typedef = IDLTypedef(BuiltinLocation("<builtin type>"), scope, builtin, name) @@ -6703,12 +7563,12 @@ class Parser(Tokenizer): return type def parse(self, t, filename=None): - self.lexer.input(t) + self._filename = filename + self.lexer.input(t.decode(encoding = 'utf-8')) # for tok in iter(self.lexer.token, None): # print tok - self._filename = filename self._productions.extend(self.parser.parse(lexer=self.lexer, tracking=True)) self._filename = None @@ -6719,23 +7579,9 @@ class Parser(Tokenizer): for p in self._productions: if isinstance(p, IDLInterface): interfaceStatements.append(p) - if p.identifier.name == "Navigator": - navigatorInterface = p iterableIteratorIface = None for iface in interfaceStatements: - navigatorProperty = iface.getNavigatorProperty() - if navigatorProperty: - # We're generating a partial interface to add a readonly - # property to the Navigator interface for every interface - # annotated with NavigatorProperty. - partialInterface = IDLPartialInterfaceOrNamespace( - iface.location, - IDLUnresolvedIdentifier(iface.location, "Navigator"), - [ navigatorProperty ], - navigatorInterface) - self._productions.append(partialInterface) - iterable = None # We haven't run finish() on the interface yet, so we don't know # whether our interface is maplike/setlike/iterable or not. This @@ -6755,9 +7601,12 @@ class Parser(Tokenizer): nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")]) itr_ident = IDLUnresolvedIdentifier(iface.location, iface.identifier.name + "Iterator") + toStringTag = iface.identifier.name + " Iterator" itr_iface = IDLInterface(iface.location, self.globalScope(), itr_ident, None, [nextMethod], - isKnownNonPartial=True) + isKnownNonPartial=True, + classNameOverride=toStringTag, + toStringTag=toStringTag) itr_iface.addExtendedAttributes([simpleExtendedAttr("NoInterfaceObject")]) # Make sure the exposure set for the iterator interface is the # same as the exposure set for the iterable interface, because @@ -6771,14 +7620,14 @@ class Parser(Tokenizer): self._productions.append(itr_iface) iterable.iteratorType = IDLWrapperType(iface.location, itr_iface) - # Then, finish all the IDLImplementsStatements. In particular, we - # have to make sure we do those before we do the IDLInterfaces. + # Make sure we finish IDLIncludesStatements before we finish the + # IDLInterfaces. # XXX khuey hates this bit and wants to nuke it from orbit. - implementsStatements = [p for p in self._productions if - isinstance(p, IDLImplementsStatement)] + includesStatements = [p for p in self._productions if + isinstance(p, IDLIncludesStatement)] otherStatements = [p for p in self._productions if - not isinstance(p, IDLImplementsStatement)] - for production in implementsStatements: + not isinstance(p, IDLIncludesStatement)] + for production in includesStatements: production.finish(self.globalScope()) for production in otherStatements: production.finish(self.globalScope()) @@ -6831,14 +7680,14 @@ def main(): f = open(fullPath, 'rb') lines = f.readlines() f.close() - print fullPath + print(fullPath) parser.parse(''.join(lines), fullPath) parser.finish() - except WebIDLError, e: + except WebIDLError as e: if options.verbose_errors: traceback.print_exc() else: - print e + print(e) if __name__ == '__main__': main() diff --git a/components/script/dom/bindings/codegen/parser/abstract.patch b/components/script/dom/bindings/codegen/parser/abstract.patch index a8e2ddcf759..180e345b61b 100644 --- a/components/script/dom/bindings/codegen/parser/abstract.patch +++ b/components/script/dom/bindings/codegen/parser/abstract.patch @@ -1,11 +1,11 @@ --- WebIDL.py +++ WebIDL.py -@@ -1416,7 +1416,8 @@ - identifier == "LegacyEventInit" or - identifier == "ProbablyShortLivingObject" or +@@ -1883,7 +1883,8 @@ class IDLInterface(IDLInterfaceOrNamespace): identifier == "LegacyUnenumerableNamedProperties" or -- identifier == "NonOrdinaryGetPrototypeOf"): -+ identifier == "NonOrdinaryGetPrototypeOf" or + identifier == "RunConstructorInCallerCompartment" or + identifier == "WantsEventListenerHooks" or +- identifier == "Serializable"): ++ identifier == "Serializable" or + identifier == "Abstract"): # Known extended attributes that do not take values if not attr.noArguments(): diff --git a/components/script/dom/bindings/codegen/parser/callback-location.patch b/components/script/dom/bindings/codegen/parser/callback-location.patch index fac5d035801..b7a308df631 100644 --- a/components/script/dom/bindings/codegen/parser/callback-location.patch +++ b/components/script/dom/bindings/codegen/parser/callback-location.patch @@ -1,17 +1,15 @@ -diff --git a/components/script/dom/bindings/codegen/parser/WebIDL.py b/components/script/dom/bindings/codegen/parser/WebIDL.py -index da32340..81c52b7 100644 --- WebIDL.py +++ WebIDL.py -@@ -2170,7 +2170,7 @@ class IDLUnresolvedType(IDLType): - return typedefType.complete(scope) +@@ -2283,7 +2283,7 @@ class IDLUnresolvedType(IDLType): + return typedefType.complete(scope).withExtendedAttributes(self.extraTypeAttributes) elif obj.isCallback() and not obj.isInterface(): assert self.name.name == obj.identifier.name - return IDLCallbackType(self.location, obj) + return IDLCallbackType(obj.location, obj) - - if self._promiseInnerType and not self._promiseInnerType.isComplete(): - self._promiseInnerType = self._promiseInnerType.complete(scope) -@@ -6521,7 +6521,7 @@ class Parser(Tokenizer): + + name = self.name.resolve(scope, None) + return IDLWrapperType(self.location, obj) +@@ -6854,7 +6854,7 @@ class Parser(Tokenizer): type = IDLTypedefType(self.getLocation(p, 1), obj.innerType, obj.identifier.name) elif obj.isCallback() and not obj.isInterface(): @@ -19,4 +17,4 @@ index da32340..81c52b7 100644 + type = IDLCallbackType(obj.location, obj) else: type = IDLWrapperType(self.getLocation(p, 1), p[1]) - p[0] = self.handleModifiers(type, p[2]) + p[0] = self.handleNullable(type, p[2]) diff --git a/components/script/dom/bindings/codegen/parser/debug.patch b/components/script/dom/bindings/codegen/parser/debug.patch index ca391c38273..a4f8739000d 100644 --- a/components/script/dom/bindings/codegen/parser/debug.patch +++ b/components/script/dom/bindings/codegen/parser/debug.patch @@ -1,6 +1,6 @@ --- WebIDL.py +++ WebIDL.py -@@ -6823,7 +6823,8 @@ class Parser(Tokenizer): +@@ -7382,7 +7382,8 @@ class Parser(Tokenizer): self.parser = yacc.yacc(module=self, outputdir=outputdir, tabmodule='webidlyacc', diff --git a/components/script/dom/bindings/codegen/parser/ext-attribute-no-value-error.patch b/components/script/dom/bindings/codegen/parser/ext-attribute-no-value-error.patch new file mode 100644 index 00000000000..210134d8ca6 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/ext-attribute-no-value-error.patch @@ -0,0 +1,11 @@ +--- WebIDL.py ++++ WebIDL.py +@@ -3490,7 +3490,7 @@ class IDLBuiltinType(IDLType): + [self.location, attribute.location]) + assert not self.nullable() + if not attribute.hasValue(): +- raise WebIDLError("[TreatNullAs] must take an identifier argument" ++ raise WebIDLError("[TreatNullAs] must take an identifier argument", + [attribute.location]) + value = attribute.value() + if value != 'EmptyString': diff --git a/components/script/dom/bindings/codegen/parser/inline.patch b/components/script/dom/bindings/codegen/parser/inline.patch index 5d1056d2b58..46971ce5067 100644 --- a/components/script/dom/bindings/codegen/parser/inline.patch +++ b/components/script/dom/bindings/codegen/parser/inline.patch @@ -1,12 +1,12 @@ --- WebIDL.py +++ WebIDL.py -@@ -1695,7 +1695,8 @@ class IDLInterface(IDLInterfaceOrNamespace): - identifier == "ProbablyShortLivingObject" or - identifier == "LegacyUnenumerableNamedProperties" or - identifier == "NonOrdinaryGetPrototypeOf" or +@@ -1884,7 +1884,8 @@ class IDLInterface(IDLInterfaceOrNamespace): + identifier == "RunConstructorInCallerCompartment" or + identifier == "WantsEventListenerHooks" or + identifier == "Serializable" or - identifier == "Abstract"): + identifier == "Abstract" or + identifier == "Inline"): # Known extended attributes that do not take values if not attr.noArguments(): - raise WebIDLError("[%s] must take no arguments" % identifier,
\ No newline at end of file + raise WebIDLError("[%s] must take no arguments" % identifier, diff --git a/components/script/dom/bindings/codegen/parser/pref-main-thread.patch b/components/script/dom/bindings/codegen/parser/pref-main-thread.patch deleted file mode 100644 index 7be2dcbfc5e..00000000000 --- a/components/script/dom/bindings/codegen/parser/pref-main-thread.patch +++ /dev/null @@ -1,28 +0,0 @@ ---- WebIDL.py -+++ WebIDL.py -@@ -1239,12 +1239,6 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins): - alias, - [member.location, m.location]) - -- if (self.getExtendedAttribute("Pref") and -- self._exposureGlobalNames != set([self.parentScope.primaryGlobalName])): -- raise WebIDLError("[Pref] used on an interface that is not %s-only" % -- self.parentScope.primaryGlobalName, -- [self.location]) -- - # Conditional exposure makes no sense for interfaces with no - # interface object, unless they're navigator properties. - # And SecureContext makes sense for interfaces with no interface object, -@@ -3459,12 +3453,6 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins): - IDLExposureMixins.finish(self, scope) - - def validate(self): -- if (self.getExtendedAttribute("Pref") and -- self.exposureSet != set([self._globalScope.primaryGlobalName])): -- raise WebIDLError("[Pref] used on an interface member that is not " -- "%s-only" % self._globalScope.primaryGlobalName, -- [self.location]) -- - if self.isAttr() or self.isMethod(): - if self.affects == "Everything" and self.dependsOn != "Everything": - raise WebIDLError("Interface member is flagged as affecting " diff --git a/components/script/dom/bindings/codegen/parser/runtests.py b/components/script/dom/bindings/codegen/parser/runtests.py index b8d45ef31b5..0599bf55fec 100644 --- a/components/script/dom/bindings/codegen/parser/runtests.py +++ b/components/script/dom/bindings/codegen/parser/runtests.py @@ -1,6 +1,6 @@ # 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 http://mozilla.org/MPL/2.0/. +# file, You can obtain one at https://mozilla.org/MPL/2.0/. import os, sys import glob @@ -62,7 +62,7 @@ def run_tests(tests, verbose): harness.start() try: _test.WebIDLTest.__call__(WebIDL.Parser(), harness) - except Exception, ex: + except Exception as ex: print("TEST-UNEXPECTED-FAIL | Unhandled exception in test %s: %s" % (testpath, ex)) traceback.print_exc() finally: diff --git a/components/script/dom/bindings/codegen/parser/tests/test_argument_keywords.py b/components/script/dom/bindings/codegen/parser/tests/test_argument_keywords.py new file mode 100644 index 00000000000..e190f617e26 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_argument_keywords.py @@ -0,0 +1,17 @@ +def WebIDLTest(parser, harness): + parser.parse(""" + interface Foo { + void foo(object constructor); + }; + """) + + results = parser.finish() + harness.check(len(results), 1, "Should have an interface"); + iface = results[0]; + harness.check(len(iface.members), 1, "Should have an operation"); + operation = iface.members[0]; + harness.check(len(operation.signatures()), 1, "Should have one signature"); + (retval, args) = operation.signatures()[0]; + harness.check(len(args), 1, "Should have an argument"); + harness.check(args[0].identifier.name, "constructor", + "Should have an identifier named 'constructor'"); diff --git a/components/script/dom/bindings/codegen/parser/tests/test_attr.py b/components/script/dom/bindings/codegen/parser/tests/test_attr.py index ad7aabc1918..35f680aaa82 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_attr.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_attr.py @@ -133,7 +133,7 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should not allow [SetterThrows] on readonly attributes") @@ -146,7 +146,7 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should spell [Throws] correctly") @@ -159,7 +159,7 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should not allow [SameObject] on attributes not of interface type") @@ -172,6 +172,6 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(not threw, "Should allow [SameObject] on attributes of interface type") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_attributes_on_types.py b/components/script/dom/bindings/codegen/parser/tests/test_attributes_on_types.py new file mode 100644 index 00000000000..ff08791d16f --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_attributes_on_types.py @@ -0,0 +1,415 @@ +# Import the WebIDL module, so we can do isinstance checks and whatnot +import WebIDL + +def WebIDLTest(parser, harness): + # Basic functionality + threw = False + try: + parser.parse(""" + typedef [EnforceRange] long Foo; + typedef [Clamp] long Bar; + typedef [TreatNullAs=EmptyString] DOMString Baz; + dictionary A { + required [EnforceRange] long a; + required [Clamp] long b; + [ChromeOnly, EnforceRange] long c; + Foo d; + }; + interface B { + attribute Foo typedefFoo; + attribute [EnforceRange] long foo; + attribute [Clamp] long bar; + attribute [TreatNullAs=EmptyString] DOMString baz; + void method([EnforceRange] long foo, [Clamp] long bar, + [TreatNullAs=EmptyString] DOMString baz); + void method2(optional [EnforceRange] long foo, optional [Clamp] long bar, + optional [TreatNullAs=EmptyString] DOMString baz); + }; + interface C { + attribute [EnforceRange] long? foo; + attribute [Clamp] long? bar; + void method([EnforceRange] long? foo, [Clamp] long? bar); + void method2(optional [EnforceRange] long? foo, optional [Clamp] long? bar); + }; + interface Setlike { + setlike<[Clamp] long>; + }; + interface Maplike { + maplike<[Clamp] long, [EnforceRange] long>; + }; + interface Iterable { + iterable<[Clamp] long, [EnforceRange] long>; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(not threw, "Should not have thrown on parsing normal") + if not threw: + harness.check(results[0].innerType.hasEnforceRange(), True, "Foo is [EnforceRange]") + harness.check(results[1].innerType.hasClamp(), True, "Bar is [Clamp]") + harness.check(results[2].innerType.treatNullAsEmpty, True, "Baz is [TreatNullAs=EmptyString]") + A = results[3] + harness.check(A.members[0].type.hasEnforceRange(), True, "A.a is [EnforceRange]") + harness.check(A.members[1].type.hasClamp(), True, "A.b is [Clamp]") + harness.check(A.members[2].type.hasEnforceRange(), True, "A.c is [EnforceRange]") + harness.check(A.members[3].type.hasEnforceRange(), True, "A.d is [EnforceRange]") + B = results[4] + harness.check(B.members[0].type.hasEnforceRange(), True, "B.typedefFoo is [EnforceRange]") + harness.check(B.members[1].type.hasEnforceRange(), True, "B.foo is [EnforceRange]") + harness.check(B.members[2].type.hasClamp(), True, "B.bar is [Clamp]") + harness.check(B.members[3].type.treatNullAsEmpty, True, "B.baz is [TreatNullAs=EmptyString]") + method = B.members[4].signatures()[0][1] + harness.check(method[0].type.hasEnforceRange(), True, "foo argument of method is [EnforceRange]") + harness.check(method[1].type.hasClamp(), True, "bar argument of method is [Clamp]") + harness.check(method[2].type.treatNullAsEmpty, True, "baz argument of method is [TreatNullAs=EmptyString]") + method2 = B.members[5].signatures()[0][1] + harness.check(method[0].type.hasEnforceRange(), True, "foo argument of method2 is [EnforceRange]") + harness.check(method[1].type.hasClamp(), True, "bar argument of method2 is [Clamp]") + harness.check(method[2].type.treatNullAsEmpty, True, "baz argument of method2 is [TreatNullAs=EmptyString]") + C = results[5] + harness.ok(C.members[0].type.nullable(), "C.foo is nullable") + harness.ok(C.members[0].type.hasEnforceRange(), "C.foo has [EnforceRange]") + harness.ok(C.members[1].type.nullable(), "C.bar is nullable") + harness.ok(C.members[1].type.hasClamp(), "C.bar has [Clamp]") + method = C.members[2].signatures()[0][1] + harness.ok(method[0].type.nullable(), "foo argument of method is nullable") + harness.ok(method[0].type.hasEnforceRange(), "foo argument of method has [EnforceRange]") + harness.ok(method[1].type.nullable(), "bar argument of method is nullable") + harness.ok(method[1].type.hasClamp(), "bar argument of method has [Clamp]") + method2 = C.members[3].signatures()[0][1] + harness.ok(method2[0].type.nullable(), "foo argument of method2 is nullable") + harness.ok(method2[0].type.hasEnforceRange(), "foo argument of method2 has [EnforceRange]") + harness.ok(method2[1].type.nullable(), "bar argument of method2 is nullable") + harness.ok(method2[1].type.hasClamp(), "bar argument of method2 has [Clamp]") + + # Test [AllowShared] + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [AllowShared] ArrayBufferView Foo; + dictionary A { + required [AllowShared] ArrayBufferView a; + [ChromeOnly, AllowShared] ArrayBufferView b; + Foo c; + }; + interface B { + attribute Foo typedefFoo; + attribute [AllowShared] ArrayBufferView foo; + void method([AllowShared] ArrayBufferView foo); + void method2(optional [AllowShared] ArrayBufferView foo); + }; + interface C { + attribute [AllowShared] ArrayBufferView? foo; + void method([AllowShared] ArrayBufferView? foo); + void method2(optional [AllowShared] ArrayBufferView? foo); + }; + interface Setlike { + setlike<[AllowShared] ArrayBufferView>; + }; + interface Maplike { + maplike<[Clamp] long, [AllowShared] ArrayBufferView>; + }; + interface Iterable { + iterable<[Clamp] long, [AllowShared] ArrayBufferView>; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(not threw, "Should not have thrown on parsing normal") + if not threw: + harness.ok(results[0].innerType.hasAllowShared(), "Foo is [AllowShared]") + A = results[1] + harness.ok(A.members[0].type.hasAllowShared(), "A.a is [AllowShared]") + harness.ok(A.members[1].type.hasAllowShared(), "A.b is [AllowShared]") + harness.ok(A.members[2].type.hasAllowShared(), "A.c is [AllowShared]") + B = results[2] + harness.ok(B.members[0].type.hasAllowShared(), "B.typedefFoo is [AllowShared]") + harness.ok(B.members[1].type.hasAllowShared(), "B.foo is [AllowShared]") + method = B.members[2].signatures()[0][1] + harness.ok(method[0].type.hasAllowShared(), "foo argument of method is [AllowShared]") + method2 = B.members[3].signatures()[0][1] + harness.ok(method2[0].type.hasAllowShared(), "foo argument of method2 is [AllowShared]") + C = results[3] + harness.ok(C.members[0].type.nullable(), "C.foo is nullable") + harness.ok(C.members[0].type.hasAllowShared(), "C.foo is [AllowShared]") + method = C.members[1].signatures()[0][1] + harness.ok(method[0].type.nullable(), "foo argument of method is nullable") + harness.ok(method[0].type.hasAllowShared(), "foo argument of method is [AllowShared]") + method2 = C.members[2].signatures()[0][1] + harness.ok(method2[0].type.nullable(), "foo argument of method2 is nullable") + harness.ok(method2[0].type.hasAllowShared(), "foo argument of method2 is [AllowShared]") + + ATTRIBUTES = [("[Clamp]", "long"), ("[EnforceRange]", "long"), + ("[TreatNullAs=EmptyString]", "DOMString"), ("[AllowShared]", "ArrayBufferView")] + TEMPLATES = [ + ("required dictionary members", """ + dictionary Foo { + %s required %s foo; + }; + """), + ("optional arguments", """ + interface Foo { + void foo(%s optional %s foo); + }; + """), + ("typedefs", """ + %s typedef %s foo; + """), + ("attributes", """ + interface Foo { + %s attribute %s foo; + }; + """), + ("readonly attributes", """ + interface Foo { + readonly attribute %s %s foo; + }; + """), + ("readonly unresolved attributes", """ + interface Foo { + readonly attribute Bar baz; + }; + typedef %s %s Bar; + """), + ("method", """ + interface Foo { + %s %s foo(); + }; + """), + ("interface",""" + %s + interface Foo { + attribute %s foo; + }; + """), + ("partial interface",""" + interface Foo { + void foo(); + }; + %s + partial interface Foo { + attribute %s bar; + }; + """), + ("interface mixin",""" + %s + interface mixin Foo { + attribute %s foo; + }; + """), + ("namespace",""" + %s + namespace Foo { + attribute %s foo; + }; + """), + ("partial namespace",""" + namespace Foo { + void foo(); + }; + %s + partial namespace Foo { + attribute %s bar; + }; + """), + ("dictionary",""" + %s + dictionary Foo { + %s foo; + }; + """) + ]; + + for (name, template) in TEMPLATES: + parser = parser.reset() + threw = False + try: + parser.parse(template % ("", "long")) + parser.finish() + except: + threw = True + harness.ok(not threw, "Template for %s parses without attributes" % name) + for (attribute, type) in ATTRIBUTES: + parser = parser.reset() + threw = False + try: + parser.parse(template % (attribute, type)) + parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow %s on %s" % (attribute, name)) + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [Clamp, EnforceRange] long Foo; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange]") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [EnforceRange, Clamp] long Foo; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange]") + + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [Clamp] long Foo; + typedef [EnforceRange] Foo bar; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange] via typedefs") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [EnforceRange] long Foo; + typedef [Clamp] Foo bar; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange] via typedefs") + + TYPES = ["DOMString", "unrestricted float", "float", "unrestricted double", "double"] + + for type in TYPES: + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [Clamp] %s Foo; + """ % type) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow [Clamp] on %s" % type) + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [EnforceRange] %s Foo; + """ % type) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow [EnforceRange] on %s" % type) + + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [TreatNullAs=EmptyString] long Foo; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow [TreatNullAs] on long") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [TreatNullAs=EmptyString] JSString Foo; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow [TreatNullAs] on JSString") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [TreatNullAs=EmptyString] DOMString? Foo; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow [TreatNullAs] on nullable DOMString") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [AllowShared] DOMString Foo; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "[AllowShared] only allowed on buffer source types") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef [AllowShared=something] ArrayBufferView Foo; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "[AllowShared] must take no arguments") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + void foo([Clamp] Bar arg); + }; + typedef long Bar; + """) + results = parser.finish() + except: + threw = True + harness.ok(not threw, "Should allow type attributes on unresolved types") + harness.check(results[0].members[0].signatures()[0][1][0].type.hasClamp(), True, + "Unresolved types with type attributes should correctly resolve with attributes") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + void foo(Bar arg); + }; + typedef [Clamp] long Bar; + """) + results = parser.finish() + except: + threw = True + harness.ok(not threw, "Should allow type attributes on typedefs") + harness.check(results[0].members[0].signatures()[0][1][0].type.hasClamp(), True, + "Unresolved types that resolve to typedefs with attributes should correctly resolve with attributes") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_callback.py b/components/script/dom/bindings/codegen/parser/tests/test_callback.py index 4dfda1c3c76..c304d085ce5 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_callback.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_callback.py @@ -32,3 +32,6 @@ def WebIDLTest(parser, harness): harness.ok(not isinstance(t, WebIDL.IDLWrapperType), "Attr has the right type") harness.ok(isinstance(t, WebIDL.IDLNullableType), "Attr has the right type") harness.ok(t.isCallback(), "Attr has the right type") + + callback = results[1] + harness.ok(not callback.isConstructor(), "callback is not constructor") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_callback_constructor.py b/components/script/dom/bindings/codegen/parser/tests/test_callback_constructor.py new file mode 100644 index 00000000000..4999deef623 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_callback_constructor.py @@ -0,0 +1,63 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestCallbackConstructor { + attribute CallbackConstructorType? constructorAttribute; + }; + + callback constructor CallbackConstructorType = TestCallbackConstructor (unsigned long arg); + """) + + results = parser.finish() + + harness.ok(True, "TestCallbackConstructor interface parsed without error.") + harness.check(len(results), 2, "Should be two productions.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(iface.identifier.QName(), "::TestCallbackConstructor", "Interface has the right QName") + harness.check(iface.identifier.name, "TestCallbackConstructor", "Interface has the right name") + harness.check(len(iface.members), 1, "Expect %s members" % 1) + + attr = iface.members[0] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), + "Should be an IDLAttribute") + harness.ok(attr.isAttr(), "Should be an attribute") + harness.ok(not attr.isMethod(), "Attr is not an method") + harness.ok(not attr.isConst(), "Attr is not a const") + harness.check(attr.identifier.QName(), "::TestCallbackConstructor::constructorAttribute", "Attr has the right QName") + harness.check(attr.identifier.name, "constructorAttribute", "Attr has the right name") + t = attr.type + harness.ok(not isinstance(t, WebIDL.IDLWrapperType), "Attr has the right type") + harness.ok(isinstance(t, WebIDL.IDLNullableType), "Attr has the right type") + harness.ok(t.isCallback(), "Attr has the right type") + + callback = results[1] + harness.ok(callback.isConstructor(), "Callback is constructor") + + parser.reset() + threw = False + try: + parser.parse(""" + [TreatNonObjectAsNull] + callback constructor CallbackConstructorType = object (); + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should throw on TreatNonObjectAsNull callback constructors") + + parser.reset() + threw = False + try: + parser.parse(""" + [MOZ_CAN_RUN_SCRIPT_BOUNDARY] + callback constructor CallbackConstructorType = object (); + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not permit MOZ_CAN_RUN_SCRIPT_BOUNDARY callback constructors") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_cereactions.py b/components/script/dom/bindings/codegen/parser/tests/test_cereactions.py new file mode 100644 index 00000000000..f726907c2fc --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_cereactions.py @@ -0,0 +1,133 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface Foo { + [CEReactions(DOMString a)] void foo(boolean arg2); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] with an argument") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + [CEReactions(DOMString b)] readonly attribute boolean bar; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] with an argument") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + [CEReactions] attribute boolean bar; + }; + """) + + results = parser.finish() + except Exception as e: + harness.ok(False, "Shouldn't have thrown for [CEReactions] used on writable attribute. %s" % e) + threw = True + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + [CEReactions] void foo(boolean arg2); + }; + """) + + results = parser.finish() + except Exception as e: + harness.ok(False, "Shouldn't have thrown for [CEReactions] used on regular operations. %s" % e) + threw = True + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + [CEReactions] readonly attribute boolean A; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] used on a readonly attribute") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [CEReactions] + interface Foo { + } + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown for [CEReactions] used on a interface") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + [CEReactions] getter any(DOMString name); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should have thrown for [CEReactions] used on a named getter") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + [CEReactions] legacycaller double compute(double x); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should have thrown for [CEReactions] used on a legacycaller") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + [CEReactions] stringifier DOMString (); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should have thrown for [CEReactions] used on a stringifier") + diff --git a/components/script/dom/bindings/codegen/parser/tests/test_conditional_dictionary_member.py b/components/script/dom/bindings/codegen/parser/tests/test_conditional_dictionary_member.py index 433b7e501a4..8420f2ee4e0 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_conditional_dictionary_member.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_conditional_dictionary_member.py @@ -44,8 +44,8 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, exception: - pass + except Exception as e: + exception = e harness.ok(exception, "Should have thrown.") harness.check(exception.message, @@ -70,8 +70,8 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, exception: - pass + except Exception as e: + exception = e harness.ok(exception, "Should have thrown (2).") harness.check(exception.message, @@ -100,8 +100,8 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, exception: - pass + except Exception as e: + exception = e harness.ok(exception, "Should have thrown (3).") harness.check(exception.message, diff --git a/components/script/dom/bindings/codegen/parser/tests/test_const.py b/components/script/dom/bindings/codegen/parser/tests/test_const.py index 80b6fb0e9c8..918f284a226 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_const.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_const.py @@ -12,9 +12,6 @@ expected = [ ("::TestConsts::ll", "ll", "LongLong", -8), ("::TestConsts::t", "t", "Boolean", True), ("::TestConsts::f", "f", "Boolean", False), - ("::TestConsts::n", "n", "BooleanOrNull", None), - ("::TestConsts::nt", "nt", "BooleanOrNull", True), - ("::TestConsts::nf", "nf", "BooleanOrNull", False), ("::TestConsts::fl", "fl", "Float", 0.2), ("::TestConsts::db", "db", "Double", 0.2), ("::TestConsts::ufl", "ufl", "UnrestrictedFloat", 0.2), @@ -39,9 +36,6 @@ def WebIDLTest(parser, harness): const long long ll = -010; const boolean t = true; const boolean f = false; - const boolean? n = null; - const boolean? nt = true; - const boolean? nf = false; const float fl = 0.2; const double db = 0.2; const unrestricted float ufl = 0.2; @@ -78,3 +72,16 @@ def WebIDLTest(parser, harness): "Const's value has the same type as the type") harness.check(const.value.value, value, "Const value has the right value.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestConsts { + const boolean? zero = 0; + }; + """) + parser.finish() + except: + threw = True + harness.ok(threw, "Nullable types are not allowed for consts.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_constructor.py b/components/script/dom/bindings/codegen/parser/tests/test_constructor.py index 348204c7dc1..83e1f4fc34f 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_constructor.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_constructor.py @@ -11,9 +11,9 @@ def WebIDLTest(parser, harness): harness.check(argument.variadic, variadic, "Argument has the right variadic value") def checkMethod(method, QName, name, signatures, - static=True, getter=False, setter=False, creator=False, - deleter=False, legacycaller=False, stringifier=False, - chromeOnly=False): + static=True, getter=False, setter=False, deleter=False, + legacycaller=False, stringifier=False, chromeOnly=False, + htmlConstructor=False, secureContext=False, pref=None, func=None): harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod") harness.ok(method.isMethod(), "Method is a method") @@ -24,12 +24,15 @@ def WebIDLTest(parser, harness): harness.check(method.isStatic(), static, "Method has the correct static value") harness.check(method.isGetter(), getter, "Method has the correct getter value") harness.check(method.isSetter(), setter, "Method has the correct setter value") - harness.check(method.isCreator(), creator, "Method has the correct creator value") harness.check(method.isDeleter(), deleter, "Method has the correct deleter value") harness.check(method.isLegacycaller(), legacycaller, "Method has the correct legacycaller value") harness.check(method.isStringifier(), stringifier, "Method has the correct stringifier value") harness.check(method.getExtendedAttribute("ChromeOnly") is not None, chromeOnly, "Method has the correct value for ChromeOnly") + harness.check(method.isHTMLConstructor(), htmlConstructor, "Method has the correct htmlConstructor value") harness.check(len(method.signatures()), len(signatures), "Method has the correct number of signatures") + harness.check(method.getExtendedAttribute("Pref"), pref, "Method has the correct pref value") + harness.check(method.getExtendedAttribute("Func"), func, "Method has the correct func value") + harness.check(method.getExtendedAttribute("SecureContext") is not None, secureContext, "Method has the correct SecureContext value") sigpairs = zip(method.signatures(), signatures) for (gotSignature, expectedSignature) in sigpairs: @@ -43,45 +46,116 @@ def WebIDLTest(parser, harness): (QName, name, type, optional, variadic) = expectedArgs[i] checkArgument(gotArgs[i], QName, name, type, optional, variadic) + def checkResults(results): + harness.check(len(results), 3, "Should be three productions") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.ok(isinstance(results[1], WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.ok(isinstance(results[2], WebIDL.IDLInterface), + "Should be an IDLInterface") + + checkMethod(results[0].ctor(), "::TestConstructorNoArgs::constructor", + "constructor", [("TestConstructorNoArgs (Wrapper)", [])]) + harness.check(len(results[0].members), 0, + "TestConstructorNoArgs should not have members") + checkMethod(results[1].ctor(), "::TestConstructorWithArgs::constructor", + "constructor", + [("TestConstructorWithArgs (Wrapper)", + [("::TestConstructorWithArgs::constructor::name", "name", "String", False, False)])]) + harness.check(len(results[1].members), 0, + "TestConstructorWithArgs should not have members") + checkMethod(results[2].ctor(), "::TestConstructorOverloads::constructor", + "constructor", + [("TestConstructorOverloads (Wrapper)", + [("::TestConstructorOverloads::constructor::foo", "foo", "Object", False, False)]), + ("TestConstructorOverloads (Wrapper)", + [("::TestConstructorOverloads::constructor::bar", "bar", "Boolean", False, False)])]) + harness.check(len(results[2].members), 0, + "TestConstructorOverloads should not have members") + parser.parse(""" - [Constructor] interface TestConstructorNoArgs { + constructor(); }; - [Constructor(DOMString name)] interface TestConstructorWithArgs { + constructor(DOMString name); }; - [Constructor(object foo), Constructor(boolean bar)] interface TestConstructorOverloads { + constructor(object foo); + constructor(boolean bar); + }; + """) + results = parser.finish() + checkResults(results) + + parser = parser.reset() + parser.parse(""" + interface TestPrefConstructor { + [Pref="dom.webidl.test1"] constructor(); }; """) results = parser.finish() - harness.check(len(results), 3, "Should be three productions") + harness.check(len(results), 1, "Should be one production") harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") - harness.ok(isinstance(results[1], WebIDL.IDLInterface), + + checkMethod(results[0].ctor(), "::TestPrefConstructor::constructor", + "constructor", [("TestPrefConstructor (Wrapper)", [])], + pref=["dom.webidl.test1"]) + + parser = parser.reset() + parser.parse(""" + interface TestChromeOnlyConstructor { + [ChromeOnly] constructor(); + }; + """) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), + "Should be an IDLInterface") + + checkMethod(results[0].ctor(), "::TestChromeOnlyConstructor::constructor", + "constructor", [("TestChromeOnlyConstructor (Wrapper)", [])], + chromeOnly=True) + + parser = parser.reset() + parser.parse(""" + interface TestSCConstructor { + [SecureContext] constructor(); + }; + """) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") - harness.ok(isinstance(results[2], WebIDL.IDLInterface), + + checkMethod(results[0].ctor(), "::TestSCConstructor::constructor", + "constructor", [("TestSCConstructor (Wrapper)", [])], + secureContext=True) + + parser = parser.reset() + parser.parse(""" + interface TestFuncConstructor { + [Func="Document::IsWebAnimationsEnabled"] constructor(); + }; + """) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") - checkMethod(results[0].ctor(), "::TestConstructorNoArgs::constructor", - "constructor", [("TestConstructorNoArgs (Wrapper)", [])]) - checkMethod(results[1].ctor(), "::TestConstructorWithArgs::constructor", - "constructor", - [("TestConstructorWithArgs (Wrapper)", - [("::TestConstructorWithArgs::constructor::name", "name", "String", False, False)])]) - checkMethod(results[2].ctor(), "::TestConstructorOverloads::constructor", - "constructor", - [("TestConstructorOverloads (Wrapper)", - [("::TestConstructorOverloads::constructor::foo", "foo", "Object", False, False)]), - ("TestConstructorOverloads (Wrapper)", - [("::TestConstructorOverloads::constructor::bar", "bar", "Boolean", False, False)])]) + checkMethod(results[0].ctor(), "::TestFuncConstructor::constructor", + "constructor", [("TestFuncConstructor (Wrapper)", [])], + func=["Document::IsWebAnimationsEnabled"]) parser = parser.reset() parser.parse(""" - [ChromeConstructor()] - interface TestChromeConstructor { + interface TestPrefChromeOnlySCFuncConstructor { + [ChromeOnly, Pref="dom.webidl.test1", SecureContext, Func="Document::IsWebAnimationsEnabled"] + constructor(); }; """) results = parser.finish() @@ -89,21 +163,262 @@ def WebIDLTest(parser, harness): harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface") - checkMethod(results[0].ctor(), "::TestChromeConstructor::constructor", - "constructor", [("TestChromeConstructor (Wrapper)", [])], - chromeOnly=True) + checkMethod(results[0].ctor(), "::TestPrefChromeOnlySCFuncConstructor::constructor", + "constructor", [("TestPrefChromeOnlySCFuncConstructor (Wrapper)", [])], + func=["Document::IsWebAnimationsEnabled"], pref=["dom.webidl.test1"], + chromeOnly=True, secureContext=True) + + parser = parser.reset() + parser.parse(""" + interface TestHTMLConstructor { + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), + "Should be an IDLInterface") + + checkMethod(results[0].ctor(), "::TestHTMLConstructor::constructor", + "constructor", [("TestHTMLConstructor (Wrapper)", [])], + htmlConstructor=True) parser = parser.reset() threw = False try: parser.parse(""" - [Constructor(), - ChromeConstructor(DOMString a)] - interface TestChromeConstructor { + interface TestChromeOnlyConstructor { + constructor() + [ChromeOnly] constructor(DOMString a); }; """) results = parser.finish() except: threw = True - harness.ok(threw, "Can't have both a Constructor and a ChromeConstructor") + harness.ok(threw, "Can't have both a constructor and a ChromeOnly constructor") + + # Test HTMLConstructor with argument + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestHTMLConstructorWithArgs { + [HTMLConstructor] constructor(DOMString a); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "HTMLConstructor should take no argument") + + # Test HTMLConstructor on a callback interface + parser = parser.reset() + threw = False + try: + parser.parse(""" + callback interface TestHTMLConstructorOnCallbackInterface { + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "HTMLConstructor can't be used on a callback interface") + + # Test HTMLConstructor and constructor operation + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestHTMLConstructorAndConstructor { + constructor(); + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Can't have both a constructor and a HTMLConstructor") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestHTMLConstructorAndConstructor { + [Throws] + constructor(); + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have both a throwing constructor and a HTMLConstructor") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestHTMLConstructorAndConstructor { + constructor(DOMString a); + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have both a HTMLConstructor and a constructor operation") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestHTMLConstructorAndConstructor { + [Throws] + constructor(DOMString a); + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have both a HTMLConstructor and a throwing constructor " + "operation") + + # Test HTMLConstructor and [ChromeOnly] constructor operation + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestHTMLConstructorAndConstructor { + [ChromeOnly] + constructor(); + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have both a ChromeOnly constructor and a HTMLConstructor") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestHTMLConstructorAndConstructor { + [Throws, ChromeOnly] + constructor(); + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have both a throwing chromeonly constructor and a " + "HTMLConstructor") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestHTMLConstructorAndConstructor { + [ChromeOnly] + constructor(DOMString a); + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have both a HTMLConstructor and a chromeonly constructor " + "operation") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestHTMLConstructorAndConstructor { + [Throws, ChromeOnly] + constructor(DOMString a); + [HTMLConstructor] constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have both a HTMLConstructor and a throwing chromeonly " + "constructor operation") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [NoInterfaceObject] + interface InterfaceWithoutInterfaceObject { + constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have a constructor operation on a [NoInterfaceObject] " + "interface") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface InterfaceWithPartial { + }; + + partial interface InterfaceWithPartial { + constructor(); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have a constructor operation on a partial interface") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface InterfaceWithMixin { + }; + + interface mixin Mixin { + constructor(); + }; + + InterfaceWithMixin includes Mixin + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Can't have a constructor operation on a mixin") + diff --git a/components/script/dom/bindings/codegen/parser/tests/test_constructor_global.py b/components/script/dom/bindings/codegen/parser/tests/test_constructor_global.py new file mode 100644 index 00000000000..b7eabb1e35b --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_constructor_global.py @@ -0,0 +1,63 @@ +import traceback + +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + [Global, Exposed=TestConstructorGlobal] + interface TestConstructorGlobal { + constructor(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [Global, Exposed=TestNamedConstructorGlobal, + NamedConstructor=FooBar] + interface TestNamedConstructorGlobal { + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [NamedConstructor=FooBar, Global, + Exposed=TestNamedConstructorGlobal] + interface TestNamedConstructorGlobal { + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [Global, Exposed=TestHTMLConstructorGlobal] + interface TestHTMLConstructorGlobal { + [HTMLConstructor] constructor(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py b/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py index 2b09ae71e69..24cc36066cd 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py @@ -2,8 +2,9 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - [Constructor, NoInterfaceObject] + [NoInterfaceObject] interface TestConstructorNoInterfaceObject { + constructor(); }; """) @@ -13,11 +14,23 @@ def WebIDLTest(parser, harness): harness.ok(threw, "Should have thrown.") + parser = parser.reset() + + parser.parse(""" + [NoInterfaceObject, NamedConstructor=FooBar] + interface TestNamedConstructorNoInterfaceObject { + }; + """) + + # Test HTMLConstructor and NoInterfaceObject + parser = parser.reset() + threw = False try: parser.parse(""" - [NoInterfaceObject, Constructor] - interface TestConstructorNoInterfaceObject { + [NoInterfaceObject] + interface TestHTMLConstructorNoInterfaceObject { + [HTMLConstructor] constructor(); }; """) @@ -26,11 +39,3 @@ def WebIDLTest(parser, harness): threw = True harness.ok(threw, "Should have thrown.") - - parser = parser.reset() - - parser.parse(""" - [NoInterfaceObject, NamedConstructor=FooBar] - interface TestNamedConstructorNoInterfaceObject { - }; - """) diff --git a/components/script/dom/bindings/codegen/parser/tests/test_date.py b/components/script/dom/bindings/codegen/parser/tests/test_date.py deleted file mode 100644 index 2bdfc95e14f..00000000000 --- a/components/script/dom/bindings/codegen/parser/tests/test_date.py +++ /dev/null @@ -1,15 +0,0 @@ -def WebIDLTest(parser, harness): - parser.parse(""" - interface WithDates { - attribute Date foo; - void bar(Date arg); - void baz(sequence<Date> arg); - }; - """) - - results = parser.finish() - harness.ok(results[0].members[0].type.isDate(), "Should have Date") - harness.ok(results[0].members[1].signatures()[0][1][0].type.isDate(), - "Should have Date argument") - harness.ok(not results[0].members[2].signatures()[0][1][0].type.isDate(), - "Should have non-Date argument") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py b/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py index 2c0fa61239d..3cad3022389 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py @@ -26,6 +26,31 @@ def WebIDLTest(parser, harness): harness.check(dict2.members[1].identifier.name, "child", "'a' really comes before 'c'") + # Test partial dictionary. + parser = parser.reset(); + parser.parse(""" + dictionary A { + long c; + long g; + }; + partial dictionary A { + long h; + long d; + }; + """) + results = parser.finish() + + dict1 = results[0]; + harness.check(len(dict1.members), 4, "Dict1 has four members") + harness.check(dict1.members[0].identifier.name, "c", + "c should be first") + harness.check(dict1.members[1].identifier.name, "d", + "d should come after c") + harness.check(dict1.members[2].identifier.name, "g", + "g should come after d") + harness.check(dict1.members[3].identifier.name, "h", + "h should be last") + # Now reset our parser parser = parser.reset() threw = False @@ -42,6 +67,24 @@ def WebIDLTest(parser, harness): harness.ok(threw, "Should not allow name duplication in a dictionary") + # Test no name duplication across normal and partial dictionary. + parser = parser.reset(); + threw = False + try: + parser.parse(""" + dictionary A { + long prop = 5; + }; + partial dictionary A { + long prop; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow name duplication across normal and partial dictionary") + # Now reset our parser again parser = parser.reset() threw = False @@ -131,6 +174,22 @@ def WebIDLTest(parser, harness): dictionary A { }; interface X { + void doFoo(optional A arg); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Trailing dictionary arg must have a default value") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + }; + interface X { void doFoo((A or DOMString) arg); }; """) @@ -148,6 +207,23 @@ def WebIDLTest(parser, harness): dictionary A { }; interface X { + void doFoo(optional (A or DOMString) arg); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Trailing union arg containing a dictionary must have a default value") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + }; + interface X { void doFoo(A arg1, optional long arg2); }; """) @@ -164,6 +240,22 @@ def WebIDLTest(parser, harness): dictionary A { }; interface X { + void doFoo(optional A arg1, optional long arg2); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Dictionary arg followed by optional arg must have default value") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + }; + interface X { void doFoo(A arg1, optional long arg2, long arg3); }; """) @@ -193,6 +285,24 @@ def WebIDLTest(parser, harness): "be optional") parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo(optional (A or DOMString) arg1, optional long arg2); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Union arg containing dictionary followed by optional arg must " + "have a default value") + + parser = parser.reset() parser.parse(""" dictionary A { }; @@ -210,14 +320,75 @@ def WebIDLTest(parser, harness): dictionary A { }; interface X { - void doFoo(optional A? arg1); + void doFoo(optional A? arg1 = {}); }; """) results = parser.finish() - except: - threw = True + except Exception as x: + threw = x + + harness.ok(threw, "Optional dictionary arg must not be nullable") + harness.ok("nullable" in str(threw), + "Must have the expected exception for optional nullable dictionary arg") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + required long x; + }; + interface X { + void doFoo(A? arg1); + }; + """) + results = parser.finish() + except Exception as x: + threw = x + + harness.ok(threw, "Required dictionary arg must not be nullable") + harness.ok("nullable" in str(threw), + "Must have the expected exception for required nullable " + "dictionary arg") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo(optional (A or long)? arg1 = {}); + }; + """) + results = parser.finish() + except Exception as x: + threw = x + + harness.ok(threw, "Dictionary arg must not be in an optional nullable union") + harness.ok("nullable" in str(threw), + "Must have the expected exception for optional nullable union " + "arg containing dictionary") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + required long x; + }; + interface X { + void doFoo((A or long)? arg1); + }; + """) + results = parser.finish() + except Exception as x: + threw = x - harness.ok(threw, "Dictionary arg must not be nullable") + harness.ok(threw, "Dictionary arg must not be in a required nullable union") + harness.ok("nullable" in str(threw), + "Must have the expected exception for required nullable union " + "arg containing dictionary") parser = parser.reset() threw = False @@ -226,14 +397,15 @@ def WebIDLTest(parser, harness): dictionary A { }; interface X { - void doFoo(optional (A or long)? arg1); + void doFoo(sequence<A?> arg1); }; """) results = parser.finish() except: threw = True - harness.ok(threw, "Dictionary arg must not be in a nullable union") + harness.ok(not threw, + "Nullable union should be allowed in a sequence argument") parser = parser.reset() threw = False @@ -283,7 +455,7 @@ def WebIDLTest(parser, harness): dictionary A { }; interface X { - void doFoo(optional A arg); + void doFoo(optional A arg = {}); }; """) results = parser.finish() @@ -294,13 +466,24 @@ def WebIDLTest(parser, harness): dictionary A { }; interface X { - void doFoo(optional (A or DOMString) arg); + void doFoo(optional (A or DOMString) arg = {}); }; """) results = parser.finish() harness.ok(True, "Union arg containing a dictionary should actually parse") parser = parser.reset() + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo(optional (A or DOMString) arg = "abc"); + }; + """) + results = parser.finish() + harness.ok(True, "Union arg containing a dictionary with string default should actually parse") + + parser = parser.reset() threw = False try: parser.parse(""" @@ -553,3 +736,17 @@ def WebIDLTest(parser, harness): threw = True harness.ok(threw, "Only unrestricted values can be initialized to NaN") + + parser = parser.reset(); + threw = False + try: + parser.parse(""" + dictionary Foo { + long module; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(not threw, "Should be able to use 'module' as a dictionary member name") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py b/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py index d7780c1ffa1..505b36468d6 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py @@ -3,13 +3,16 @@ def firstArgType(method): def WebIDLTest(parser, harness): parser.parse(""" + // Give our dictionary a required member so we don't need to + // mess with optional and default values. dictionary Dict { + required long member; }; callback interface Foo { }; interface Bar { // Bit of a pain to get things that have dictionary types - void passDict(optional Dict arg); + void passDict(Dict arg); void passFoo(Foo arg); void passNullableUnion((object? or DOMString) arg); void passNullable(Foo? arg); @@ -56,8 +59,6 @@ def WebIDLTest(parser, harness): void passKid(Kid arg); void passParent(Parent arg); void passGrandparent(Grandparent arg); - void passImplemented(Implemented arg); - void passImplementedParent(ImplementedParent arg); void passUnrelated1(Unrelated1 arg); void passUnrelated2(Unrelated2 arg); void passArrayBuffer(ArrayBuffer arg); @@ -67,9 +68,6 @@ def WebIDLTest(parser, harness): interface Kid : Parent {}; interface Parent : Grandparent {}; interface Grandparent {}; - interface Implemented : ImplementedParent {}; - Parent implements Implemented; - interface ImplementedParent {}; interface Unrelated1 {}; interface Unrelated2 {}; """) @@ -151,47 +149,50 @@ def WebIDLTest(parser, harness): # Now let's test our whole distinguishability table argTypes = [ "long", "short", "long?", "short?", "boolean", - "boolean?", "DOMString", "ByteString", "Enum", "Enum2", + "boolean?", "DOMString", "ByteString", "UTF8String", "Enum", "Enum2", "Interface", "Interface?", - "AncestorInterface", "UnrelatedInterface", - "ImplementedInterface", "CallbackInterface", + "AncestorInterface", "UnrelatedInterface", "CallbackInterface", "CallbackInterface?", "CallbackInterface2", - "object", "Callback", "Callback2", "optional Dict", - "optional Dict2", "sequence<long>", "sequence<short>", - "MozMap<object>", "MozMap<Dict>", "MozMap<long>", - "Date", "Date?", "any", - "Promise<any>", "Promise<any>?", - "USVString", "ArrayBuffer", "ArrayBufferView", "SharedArrayBuffer", - "Uint8Array", "Uint16Array" ] - # When we can parse Date and RegExp, we need to add them here. + "object", "Callback", "Callback2", "Dict", + "Dict2", "sequence<long>", "sequence<short>", + "record<DOMString, object>", + "record<USVString, Dict>", + "record<ByteString, long>", + "record<UTF8String, long>", + "any", "Promise<any>", "Promise<any>?", + "USVString", "JSString", "ArrayBuffer", "ArrayBufferView", + "Uint8Array", "Uint16Array", + "(long or Callback)", "(long or Dict)", + ] # Try to categorize things a bit to keep list lengths down def allBut(list1, list2): return [a for a in list1 if a not in list2 and (a != "any" and a != "Promise<any>" and a != "Promise<any>?")] + unions = [ "(long or Callback)", "(long or Dict)" ] numerics = [ "long", "short", "long?", "short?" ] booleans = [ "boolean", "boolean?" ] primitives = numerics + booleans - nonNumerics = allBut(argTypes, numerics) + nonNumerics = allBut(argTypes, numerics + unions) nonBooleans = allBut(argTypes, booleans) - strings = [ "DOMString", "ByteString", "Enum", "Enum2", "USVString" ] + strings = [ "DOMString", "ByteString", "Enum", "Enum2", "USVString", "JSString", "UTF8String" ] nonStrings = allBut(argTypes, strings) nonObjects = primitives + strings objects = allBut(argTypes, nonObjects ) bufferSourceTypes = ["ArrayBuffer", "ArrayBufferView", "Uint8Array", "Uint16Array"] - sharedBufferSourceTypes = ["SharedArrayBuffer"] interfaces = [ "Interface", "Interface?", "AncestorInterface", - "UnrelatedInterface", "ImplementedInterface" ] + bufferSourceTypes + sharedBufferSourceTypes - nullables = ["long?", "short?", "boolean?", "Interface?", - "CallbackInterface?", "optional Dict", "optional Dict2", - "Date?", "any", "Promise<any>?"] - dates = [ "Date", "Date?" ] + "UnrelatedInterface" ] + bufferSourceTypes + nullables = (["long?", "short?", "boolean?", "Interface?", + "CallbackInterface?", "Dict", "Dict2", + "Date?", "any", "Promise<any>?"] + + allBut(unions, [ "(long or Callback)" ])) sequences = [ "sequence<long>", "sequence<short>" ] - nonUserObjects = nonObjects + interfaces + dates + sequences + nonUserObjects = nonObjects + interfaces + sequences otherObjects = allBut(argTypes, nonUserObjects + ["object"]) notRelatedInterfaces = (nonObjects + ["UnrelatedInterface"] + - otherObjects + dates + sequences + bufferSourceTypes + sharedBufferSourceTypes) - mozMaps = [ "MozMap<object>", "MozMap<Dict>", "MozMap<long>" ] + otherObjects + sequences + bufferSourceTypes) + records = [ "record<DOMString, object>", "record<USVString, Dict>", + "record<ByteString, long>", "record<UTF8String, long>" ] # JSString not supported in records # Build a representation of the distinguishability table as a dict # of dicts, holding True values where needed, holes elsewhere. @@ -210,7 +211,9 @@ def WebIDLTest(parser, harness): setDistinguishable("boolean?", allBut(nonBooleans, nullables)) setDistinguishable("DOMString", nonStrings) setDistinguishable("ByteString", nonStrings) + setDistinguishable("UTF8String", nonStrings) setDistinguishable("USVString", nonStrings) + setDistinguishable("JSString", nonStrings) setDistinguishable("Enum", nonStrings) setDistinguishable("Enum2", nonStrings) setDistinguishable("Interface", notRelatedInterfaces) @@ -218,24 +221,23 @@ def WebIDLTest(parser, harness): setDistinguishable("AncestorInterface", notRelatedInterfaces) setDistinguishable("UnrelatedInterface", allBut(argTypes, ["object", "UnrelatedInterface"])) - setDistinguishable("ImplementedInterface", notRelatedInterfaces) setDistinguishable("CallbackInterface", nonUserObjects) setDistinguishable("CallbackInterface?", allBut(nonUserObjects, nullables)) setDistinguishable("CallbackInterface2", nonUserObjects) setDistinguishable("object", nonObjects) setDistinguishable("Callback", nonUserObjects) setDistinguishable("Callback2", nonUserObjects) - setDistinguishable("optional Dict", allBut(nonUserObjects, nullables)) - setDistinguishable("optional Dict2", allBut(nonUserObjects, nullables)) + setDistinguishable("Dict", allBut(nonUserObjects, nullables)) + setDistinguishable("Dict2", allBut(nonUserObjects, nullables)) setDistinguishable("sequence<long>", allBut(argTypes, sequences + ["object"])) setDistinguishable("sequence<short>", allBut(argTypes, sequences + ["object"])) - setDistinguishable("MozMap<object>", nonUserObjects) - setDistinguishable("MozMap<Dict>", nonUserObjects) - setDistinguishable("MozMap<long>", nonUserObjects) - setDistinguishable("Date", allBut(argTypes, dates + ["object"])) - setDistinguishable("Date?", allBut(argTypes, dates + nullables + ["object"])) + setDistinguishable("record<DOMString, object>", nonUserObjects) + setDistinguishable("record<USVString, Dict>", nonUserObjects) + # JSString not supported in records + setDistinguishable("record<ByteString, long>", nonUserObjects) + setDistinguishable("record<UTF8String, long>", nonUserObjects) setDistinguishable("any", []) setDistinguishable("Promise<any>", []) setDistinguishable("Promise<any>?", []) @@ -243,7 +245,10 @@ def WebIDLTest(parser, harness): setDistinguishable("ArrayBufferView", allBut(argTypes, ["ArrayBufferView", "Uint8Array", "Uint16Array", "object"])) setDistinguishable("Uint8Array", allBut(argTypes, ["ArrayBufferView", "Uint8Array", "object"])) setDistinguishable("Uint16Array", allBut(argTypes, ["ArrayBufferView", "Uint16Array", "object"])) - setDistinguishable("SharedArrayBuffer", allBut(argTypes, ["SharedArrayBuffer", "object"])) + setDistinguishable("(long or Callback)", + allBut(nonUserObjects, numerics)) + setDistinguishable("(long or Dict)", + allBut(nonUserObjects, numerics + nullables)) def areDistinguishable(type1, type2): return data[type1].get(type2, False) @@ -255,15 +260,14 @@ def WebIDLTest(parser, harness): interface Interface : AncestorInterface {}; interface AncestorInterface {}; interface UnrelatedInterface {}; - interface ImplementedInterface {}; - Interface implements ImplementedInterface; callback interface CallbackInterface {}; callback interface CallbackInterface2 {}; callback Callback = any(); callback Callback2 = long(short arg); - dictionary Dict {}; - dictionary Dict2 {}; - interface _Promise {}; + // Give our dictionaries required members so we don't need to + // mess with optional and default values. + dictionary Dict { required long member; }; + dictionary Dict2 { required long member; }; interface TestInterface {%s }; """ diff --git a/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py b/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py index 799f2e0e0ed..4874b3aafe6 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py @@ -30,20 +30,6 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - interface DuplicateQualifiers3 { - creator creator byte foo(unsigned long index, byte value); - }; - """) - - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown.") - - threw = False - try: - parser.parse(""" interface DuplicateQualifiers4 { deleter deleter byte foo(unsigned long index); }; @@ -68,17 +54,3 @@ def WebIDLTest(parser, harness): threw = True harness.ok(threw, "Should have thrown.") - - threw = False - try: - results = parser.parse(""" - interface DuplicateQualifiers6 { - creator setter creator byte foo(unsigned long index, byte value); - }; - """) - - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_empty_sequence_default_value.py b/components/script/dom/bindings/codegen/parser/tests/test_empty_sequence_default_value.py index 350ae72f022..a713266c88e 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_empty_sequence_default_value.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_empty_sequence_default_value.py @@ -10,7 +10,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception,x: + except Exception as x: threw = True harness.ok(threw, "Constant cannot have [] as a default value") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py b/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py index ca0674aec04..7afd15513c6 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py @@ -8,7 +8,7 @@ def WebIDLTest(parser, harness): try: parser.parse(input) results = parser.finish() - except WebIDL.WebIDLError, e: + except WebIDL.WebIDLError as e: threw = True lines = str(e).split('\n') diff --git a/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py b/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py index f11222e7a4d..70bb1883682 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py @@ -14,7 +14,7 @@ interface ?""" try: parser.parse(input) results = parser.finish() - except WebIDL.WebIDLError, e: + except WebIDL.WebIDLError as e: threw = True lines = str(e).split('\n') diff --git a/components/script/dom/bindings/codegen/parser/tests/test_exposed_extended_attribute.py b/components/script/dom/bindings/codegen/parser/tests/test_exposed_extended_attribute.py index 48957098bfe..e0241a56426 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_exposed_extended_attribute.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_exposed_extended_attribute.py @@ -2,9 +2,9 @@ import WebIDL def WebIDLTest(parser, harness): parser.parse(""" - [PrimaryGlobal] interface Foo {}; - [Global=(Bar1,Bar2)] interface Bar {}; - [Global=Baz2] interface Baz {}; + [Global, Exposed=Foo] interface Foo {}; + [Global=(Bar, Bar1,Bar2), Exposed=Bar] interface Bar {}; + [Global=(Baz, Baz2), Exposed=Baz] interface Baz {}; [Exposed=(Foo,Bar1)] interface Iface { @@ -51,10 +51,11 @@ def WebIDLTest(parser, harness): parser = parser.reset() parser.parse(""" - [PrimaryGlobal] interface Foo {}; - [Global=(Bar1,Bar2)] interface Bar {}; - [Global=Baz2] interface Baz {}; + [Global, Exposed=Foo] interface Foo {}; + [Global=(Bar, Bar1, Bar2), Exposed=Bar] interface Bar {}; + [Global=(Baz, Baz2), Exposed=Baz] interface Baz {}; + [Exposed=Foo] interface Iface2 { void method3(); }; @@ -80,9 +81,9 @@ def WebIDLTest(parser, harness): parser = parser.reset() parser.parse(""" - [PrimaryGlobal] interface Foo {}; - [Global=(Bar1,Bar2)] interface Bar {}; - [Global=Baz2] interface Baz {}; + [Global, Exposed=Foo] interface Foo {}; + [Global=(Bar, Bar1, Bar2), Exposed=Bar] interface Bar {}; + [Global=(Baz, Baz2), Exposed=Baz] interface Baz {}; [Exposed=Foo] interface Iface3 { @@ -90,11 +91,11 @@ def WebIDLTest(parser, harness): }; [Exposed=(Foo,Bar1)] - interface Mixin { + interface mixin Mixin { void method5(); }; - Iface3 implements Mixin; + Iface3 includes Mixin; """) results = parser.finish() harness.check(len(results), 6, "Should know about six things"); @@ -124,7 +125,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception,x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown on invalid Exposed value on interface.") @@ -140,7 +141,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception,x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown on invalid Exposed value on attribute.") @@ -156,7 +157,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception,x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown on invalid Exposed value on operation.") @@ -172,7 +173,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception,x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown on invalid Exposed value on constant.") @@ -181,8 +182,8 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - [Global] interface Foo {}; - [Global] interface Bar {}; + [Global, Exposed=Foo] interface Foo {}; + [Global, Exposed=Bar] interface Bar {}; [Exposed=Foo] interface Baz { @@ -192,31 +193,46 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception,x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown on member exposed where its interface is not.") parser = parser.reset() - threw = False - try: - parser.parse(""" - [Global] interface Foo {}; - [Global] interface Bar {}; + parser.parse(""" + [Global, Exposed=Foo] interface Foo {}; + [Global, Exposed=Bar] interface Bar {}; - [Exposed=Foo] - interface Baz { - void method(); - }; + [Exposed=Foo] + interface Baz { + void method(); + }; - [Exposed=Bar] - interface Mixin {}; + [Exposed=Bar] + interface mixin Mixin { + void otherMethod(); + }; - Baz implements Mixin; - """) + Baz includes Mixin; + """) + + results = parser.finish() + + harness.check(len(results), 5, "Should know about five things"); + iface = results[2] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should have an interface here"); + members = iface.members + harness.check(len(members), 2, "Should have two members") + + harness.ok(members[0].exposureSet == set(["Foo"]), + "method should have the right exposure set") + harness.ok(members[0]._exposureGlobalNames == set(["Foo"]), + "method should have the right exposure global names") + + harness.ok(members[1].exposureSet == set(["Bar"]), + "otherMethod should have the right exposure set") + harness.ok(members[1]._exposureGlobalNames == set(["Bar"]), + "otherMethod should have the right exposure global names") - results = parser.finish() - except Exception,x: - threw = True - harness.ok(threw, "Should have thrown on LHS of implements being exposed where RHS is not.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py b/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py index 85a70d98f2c..144c945bc10 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py @@ -56,9 +56,9 @@ def WebIDLTest(parser, harness): results = parser.finish() # Pull out the first argument out of the arglist of the first (and # only) signature. - harness.ok(results[0].members[0].signatures()[0][1][0].clamp, + harness.ok(results[0].members[0].signatures()[0][1][0].type.hasClamp(), "Should be clamped") - harness.ok(not results[0].members[1].signatures()[0][1][0].clamp, + harness.ok(not results[0].members[1].signatures()[0][1][0].type.hasClamp(), "Should not be clamped") parser = parser.reset() @@ -86,9 +86,9 @@ def WebIDLTest(parser, harness): results = parser.finish() # Pull out the first argument out of the arglist of the first (and # only) signature. - harness.ok(results[0].members[0].signatures()[0][1][0].enforceRange, + harness.ok(results[0].members[0].signatures()[0][1][0].type.hasEnforceRange(), "Should be enforceRange") - harness.ok(not results[0].members[1].signatures()[0][1][0].enforceRange, + harness.ok(not results[0].members[1].signatures()[0][1][0].type.hasEnforceRange(), "Should not be enforceRange") parser = parser.reset() diff --git a/components/script/dom/bindings/codegen/parser/tests/test_float_types.py b/components/script/dom/bindings/codegen/parser/tests/test_float_types.py index 718f09c114b..b7325cf9d26 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_float_types.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_float_types.py @@ -68,7 +68,7 @@ def WebIDLTest(parser, harness): long m(float arg); }; """) - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "[LenientFloat] only allowed on void methods") @@ -81,7 +81,7 @@ def WebIDLTest(parser, harness): void m(unrestricted float arg); }; """) - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "[LenientFloat] only allowed on methods with unrestricted float args") @@ -94,7 +94,7 @@ def WebIDLTest(parser, harness): void m(sequence<unrestricted float> arg); }; """) - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "[LenientFloat] only allowed on methods with unrestricted float args (2)") @@ -107,7 +107,7 @@ def WebIDLTest(parser, harness): void m((unrestricted float or FloatTypes) arg); }; """) - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "[LenientFloat] only allowed on methods with unrestricted float args (3)") @@ -120,6 +120,6 @@ def WebIDLTest(parser, harness): readonly attribute float foo; }; """) - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "[LenientFloat] only allowed on writable attributes") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_global_extended_attr.py b/components/script/dom/bindings/codegen/parser/tests/test_global_extended_attr.py index c752cecd298..28b79642d86 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_global_extended_attr.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_global_extended_attr.py @@ -1,9 +1,10 @@ def WebIDLTest(parser, harness): parser.parse(""" - [Global] + [Global, Exposed=Foo] interface Foo : Bar { getter any(DOMString name); }; + [Exposed=Foo] interface Bar {}; """) @@ -18,7 +19,7 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - [Global] + [Global, Exposed=Foo] interface Foo { getter any(DOMString name); setter void(DOMString name, any arg); @@ -36,25 +37,7 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - [Global] - interface Foo { - getter any(DOMString name); - creator void(DOMString name, any arg); - }; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, - "Should have thrown for [Global] used on an interface with a " - "named creator") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - [Global] + [Global, Exposed=Foo] interface Foo { getter any(DOMString name); deleter void(DOMString name); @@ -72,7 +55,7 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - [Global, OverrideBuiltins] + [Global, OverrideBuiltins, Exposed=Foo] interface Foo { }; """) @@ -88,10 +71,10 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - [Global] + [Global, Exposed=Foo] interface Foo : Bar { }; - [OverrideBuiltins] + [OverrideBuiltins, Exposed=Foo] interface Bar { }; """) @@ -107,9 +90,10 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - [Global] + [Global, Exposed=Foo] interface Foo { }; + [Exposed=Foo] interface Bar : Foo { }; """) diff --git a/components/script/dom/bindings/codegen/parser/tests/test_identifier_conflict.py b/components/script/dom/bindings/codegen/parser/tests/test_identifier_conflict.py new file mode 100644 index 00000000000..0e9a6654aa7 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_identifier_conflict.py @@ -0,0 +1,39 @@ +# Import the WebIDL module, so we can do isinstance checks and whatnot +import WebIDL + +def WebIDLTest(parser, harness): + try: + parser.parse(""" + enum Foo { "a" }; + interface Foo; + """) + results = parser.finish() + harness.ok(False, "Should fail to parse") + except Exception as e: + harness.ok("Name collision" in e.message, + "Should have name collision for interface") + + parser = parser.reset() + try: + parser.parse(""" + dictionary Foo { long x; }; + enum Foo { "a" }; + """) + results = parser.finish() + harness.ok(False, "Should fail to parse") + except Exception as e: + harness.ok("Name collision" in e.message, + "Should have name collision for dictionary") + + parser = parser.reset() + try: + parser.parse(""" + enum Foo { "a" }; + enum Foo { "b" }; + """) + results = parser.finish() + harness.ok(False, "Should fail to parse") + except Exception as e: + harness.ok("Multiple unresolvable definitions" in e.message, + "Should have name collision for dictionary") + diff --git a/components/script/dom/bindings/codegen/parser/tests/test_implements.py b/components/script/dom/bindings/codegen/parser/tests/test_implements.py deleted file mode 100644 index 04c47d92abe..00000000000 --- a/components/script/dom/bindings/codegen/parser/tests/test_implements.py +++ /dev/null @@ -1,216 +0,0 @@ -# Import the WebIDL module, so we can do isinstance checks and whatnot -import WebIDL - -def WebIDLTest(parser, harness): - # Basic functionality - threw = False - try: - parser.parse(""" - A implements B; - interface B { - attribute long x; - }; - interface A { - attribute long y; - }; - """) - results = parser.finish() - except: - threw = True - - harness.ok(not threw, "Should not have thrown on implements statement " - "before interfaces") - harness.check(len(results), 3, "We have three statements") - harness.ok(isinstance(results[1], WebIDL.IDLInterface), "B is an interface") - harness.check(len(results[1].members), 1, "B has one member") - A = results[2] - harness.ok(isinstance(A, WebIDL.IDLInterface), "A is an interface") - harness.check(len(A.members), 2, "A has two members") - harness.check(A.members[0].identifier.name, "y", "First member is 'y'") - harness.check(A.members[1].identifier.name, "x", "Second member is 'x'") - - # Duplicated member names not allowed - threw = False - try: - parser.parse(""" - C implements D; - interface D { - attribute long x; - }; - interface C { - attribute long x; - }; - """) - parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown on implemented interface duplicating " - "a name on base interface") - - # Same, but duplicated across implemented interfaces - threw = False - try: - parser.parse(""" - E implements F; - E implements G; - interface F { - attribute long x; - }; - interface G { - attribute long x; - }; - interface E {}; - """) - parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown on implemented interfaces " - "duplicating each other's member names") - - # Same, but duplicated across indirectly implemented interfaces - threw = False - try: - parser.parse(""" - H implements I; - H implements J; - I implements K; - interface K { - attribute long x; - }; - interface L { - attribute long x; - }; - interface I {}; - interface J : L {}; - interface H {}; - """) - parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown on indirectly implemented interfaces " - "duplicating each other's member names") - - # Same, but duplicated across an implemented interface and its parent - threw = False - try: - parser.parse(""" - M implements N; - interface O { - attribute long x; - }; - interface N : O { - attribute long x; - }; - interface M {}; - """) - parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown on implemented interface and its " - "ancestor duplicating member names") - - # Reset the parser so we can actually find things where we expect - # them in the list - parser = parser.reset() - - # Diamonds should be allowed - threw = False - try: - parser.parse(""" - P implements Q; - P implements R; - Q implements S; - R implements S; - interface Q {}; - interface R {}; - interface S { - attribute long x; - }; - interface P {}; - """) - results = parser.finish() - except: - threw = True - - harness.ok(not threw, "Diamond inheritance is fine") - harness.check(results[6].identifier.name, "S", "We should be looking at 'S'") - harness.check(len(results[6].members), 1, "S should have one member") - harness.check(results[6].members[0].identifier.name, "x", - "S's member should be 'x'") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - interface TestInterface { - }; - callback interface TestCallbackInterface { - }; - TestInterface implements TestCallbackInterface; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, - "Should not allow callback interfaces on the right-hand side " - "of 'implements'") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - interface TestInterface { - }; - callback interface TestCallbackInterface { - }; - TestCallbackInterface implements TestInterface; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, - "Should not allow callback interfaces on the left-hand side of " - "'implements'") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - interface TestInterface { - }; - dictionary Dict { - }; - Dict implements TestInterface; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, - "Should not allow non-interfaces on the left-hand side " - "of 'implements'") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - interface TestInterface { - }; - dictionary Dict { - }; - TestInterface implements Dict; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, - "Should not allow non-interfaces on the right-hand side " - "of 'implements'") - diff --git a/components/script/dom/bindings/codegen/parser/tests/test_interface.py b/components/script/dom/bindings/codegen/parser/tests/test_interface.py index e8ed67b54b3..47db3ae4cc9 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_interface.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_interface.py @@ -84,100 +84,6 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - interface A {}; - interface B {}; - A implements B; - B implements A; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should not allow cycles via implements") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - interface A {}; - interface C {}; - interface B {}; - A implements C; - C implements B; - B implements A; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should not allow indirect cycles via implements") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - interface A : B {}; - interface B {}; - B implements A; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should not allow inheriting from an interface that implements us") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - interface A : B {}; - interface B {}; - interface C {}; - B implements C; - C implements A; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should not allow inheriting from an interface that indirectly implements us") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - interface A : B {}; - interface B : C {}; - interface C {}; - C implements A; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should not allow indirectly inheriting from an interface that implements us") - - parser = parser.reset() - threw = False - try: - parser.parse(""" - interface A : B {}; - interface B : C {}; - interface C {}; - interface D {}; - C implements D; - D implements A; - """) - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should not allow indirectly inheriting from an interface that indirectly implements us") - - parser = parser.reset() - threw = False - try: - parser.parse(""" interface A; interface B : A {}; """) @@ -189,12 +95,12 @@ def WebIDLTest(parser, harness): parser = parser.reset() parser.parse(""" - [Constructor(long arg)] interface A { + constructor(); + constructor(long arg); readonly attribute boolean x; void foo(); }; - [Constructor] partial interface A { readonly attribute boolean y; void foo(long arg); @@ -219,13 +125,13 @@ def WebIDLTest(parser, harness): parser = parser.reset() parser.parse(""" - [Constructor] partial interface A { readonly attribute boolean y; void foo(long arg); }; - [Constructor(long arg)] interface A { + constructor(); + constructor(long arg); readonly attribute boolean x; void foo(); }; @@ -376,30 +282,89 @@ def WebIDLTest(parser, harness): "Should not allow unknown extended attributes on interfaces") parser = parser.reset() + parser.parse(""" + [Global, Exposed=Window] interface Window {}; + [Exposed=Window, LegacyWindowAlias=A] + interface B {}; + [Exposed=Window, LegacyWindowAlias=(C, D)] + interface E {}; + """); + results = parser.finish(); + harness.check(results[1].legacyWindowAliases, ["A"], + "Should support a single identifier") + harness.check(results[2].legacyWindowAliases, ["C", "D"], + "Should support an identifier list") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [LegacyWindowAlias] + interface A {}; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow [LegacyWindowAlias] with no value") + + parser = parser.reset() threw = False try: parser.parse(""" + [Exposed=Worker, LegacyWindowAlias=B] + interface A {}; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow [LegacyWindowAlias] without Window exposure") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [Global, Exposed=Window] interface Window {}; + [Exposed=Window] + interface A {}; + [Exposed=Window, LegacyWindowAlias=A] interface B {}; - [ArrayClass] - interface A : B { - }; """) results = parser.finish() except: threw = True harness.ok(threw, - "Should not allow [ArrayClass] on interfaces with parents") + "Should not allow [LegacyWindowAlias] to conflict with other identifiers") parser = parser.reset() threw = False try: parser.parse(""" - [ArrayClass] - interface A { - }; + [Global, Exposed=Window] interface Window {}; + [Exposed=Window, LegacyWindowAlias=A] + interface B {}; + [Exposed=Window] + interface A {}; """) results = parser.finish() except: threw = True - harness.ok(not threw, - "Should allow [ArrayClass] on interfaces without parents") + harness.ok(threw, + "Should not allow [LegacyWindowAlias] to conflict with other identifiers") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [Global, Exposed=Window] interface Window {}; + [Exposed=Window, LegacyWindowAlias=A] + interface B {}; + [Exposed=Window, LegacyWindowAlias=A] + interface C {}; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow [LegacyWindowAlias] to conflict with other identifiers") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_interface_maplikesetlikeiterable.py b/components/script/dom/bindings/codegen/parser/tests/test_interface_maplikesetlikeiterable.py index 159b50f84ff..e070adee7e6 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_interface_maplikesetlikeiterable.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_interface_maplikesetlikeiterable.py @@ -37,10 +37,10 @@ def WebIDLTest(parser, harness): p.finish() harness.ok(False, prefix + " - Interface passed when should've failed") - except WebIDL.WebIDLError, e: + except WebIDL.WebIDLError as e: harness.ok(True, prefix + " - Interface failed as expected") - except Exception, e: + except Exception as e: harness.ok(False, prefix + " - Interface failed but not as a WebIDLError exception: %s" % e) @@ -88,6 +88,8 @@ def WebIDLTest(parser, harness): disallowedNonMethodNames = ["clear", "delete"] mapDisallowedNonMethodNames = ["set"] + disallowedNonMethodNames setDisallowedNonMethodNames = ["add"] + disallowedNonMethodNames + unrelatedMembers = [("unrelatedAttribute", WebIDL.IDLAttribute), + ("unrelatedMethod", WebIDL.IDLMethod)] # # Simple Usage Tests @@ -99,86 +101,171 @@ def WebIDLTest(parser, harness): iterable<long>; readonly attribute unsigned long length; getter long(unsigned long index); + attribute long unrelatedAttribute; + long unrelatedMethod(); }; - """, valueIterableMembers) + """, valueIterableMembers + unrelatedMembers) + + shouldPass("Iterable (key only) inheriting from parent", + """ + interface Foo1 : Foo2 { + iterable<long>; + readonly attribute unsigned long length; + getter long(unsigned long index); + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, valueIterableMembers, numProductions=2) shouldPass("Iterable (key and value)", """ interface Foo1 { iterable<long, long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); }; - """, iterableMembers, + """, iterableMembers + unrelatedMembers, # numProductions == 2 because of the generated iterator iface, numProductions=2) + shouldPass("Iterable (key and value) inheriting from parent", + """ + interface Foo1 : Foo2 { + iterable<long, long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, iterableMembers, + # numProductions == 3 because of the generated iterator iface, + numProductions=3) + shouldPass("Maplike (readwrite)", """ interface Foo1 { maplike<long, long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, mapRWMembers + unrelatedMembers) + + shouldPass("Maplike (readwrite) inheriting from parent", + """ + interface Foo1 : Foo2 { + maplike<long, long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); }; - """, mapRWMembers) + """, mapRWMembers, numProductions=2) shouldPass("Maplike (readwrite)", """ interface Foo1 { maplike<long, long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, mapRWMembers + unrelatedMembers) + + shouldPass("Maplike (readwrite) inheriting from parent", + """ + interface Foo1 : Foo2 { + maplike<long, long>; }; - """, mapRWMembers) + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, mapRWMembers, numProductions=2) shouldPass("Maplike (readonly)", """ interface Foo1 { readonly maplike<long, long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, mapROMembers + unrelatedMembers) + + shouldPass("Maplike (readonly) inheriting from parent", + """ + interface Foo1 : Foo2 { + readonly maplike<long, long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); }; - """, mapROMembers) + """, mapROMembers, numProductions=2) shouldPass("Setlike (readwrite)", """ interface Foo1 { setlike<long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); }; - """, setRWMembers) + """, setRWMembers + unrelatedMembers) + + shouldPass("Setlike (readwrite) inheriting from parent", + """ + interface Foo1 : Foo2 { + setlike<long>; + }; + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); + }; + """, setRWMembers, numProductions=2) shouldPass("Setlike (readonly)", """ interface Foo1 { readonly setlike<long>; + attribute long unrelatedAttribute; + long unrelatedMethod(); }; - """, setROMembers) + """, setROMembers + unrelatedMembers) - shouldPass("Inheritance of maplike/setlike", + shouldPass("Setlike (readonly) inheriting from parent", """ - interface Foo1 { - maplike<long, long>; + interface Foo1 : Foo2 { + readonly setlike<long>; }; - interface Foo2 : Foo1 { + interface Foo2 { + attribute long unrelatedAttribute; + long unrelatedMethod(); }; - """, mapRWMembers, numProductions=2) + """, setROMembers, numProductions=2) - shouldPass("Implements with maplike/setlike", + shouldPass("Inheritance of maplike/setlike", """ interface Foo1 { maplike<long, long>; }; - interface Foo2 { + interface Foo2 : Foo1 { }; - Foo2 implements Foo1; - """, mapRWMembers, numProductions=3) + """, mapRWMembers, numProductions=2) shouldPass("JS Implemented maplike interface", """ - [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1", - Constructor()] + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"] interface Foo1 { + constructor(); setlike<long>; }; """, setRWChromeMembers) shouldPass("JS Implemented maplike interface", """ - [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1", - Constructor()] + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"] interface Foo1 { + constructor(); maplike<long, long>; }; """, mapRWChromeMembers) @@ -253,31 +340,6 @@ def WebIDLTest(parser, harness): }; """) - shouldFail("Consequential interface with conflicting maplike/setlike", - """ - interface Foo1 { - maplike<long, long>; - }; - interface Foo2 { - setlike<long>; - }; - Foo2 implements Foo1; - """) - - shouldFail("Consequential interfaces with conflicting maplike/setlike", - """ - interface Foo1 { - maplike<long, long>; - }; - interface Foo2 { - setlike<long>; - }; - interface Foo3 { - }; - Foo3 implements Foo1; - Foo3 implements Foo2; - """) - # # Member name collision tests # @@ -380,52 +442,28 @@ def WebIDLTest(parser, harness): }; """, mapRWMembers, numProductions=3) - shouldFail("Interface with consequential maplike/setlike interface member collision", - """ - interface Foo1 { - void entries(); - }; - interface Foo2 { - maplike<long, long>; - }; - Foo1 implements Foo2; - """) - - shouldFail("Maplike interface with consequential interface member collision", + shouldFail("Maplike interface with mixin member collision", """ interface Foo1 { maplike<long, long>; }; - interface Foo2 { + interface mixin Foo2 { void entries(); }; - Foo1 implements Foo2; + Foo1 includes Foo2; """) - shouldPass("Consequential Maplike interface with inherited interface member collision", - """ - interface Foo1 { - maplike<long, long>; - }; - interface Foo2 { - void entries(); - }; - interface Foo3 : Foo2 { - }; - Foo3 implements Foo1; - """, mapRWMembers, numProductions=4) - shouldPass("Inherited Maplike interface with consequential interface member collision", """ interface Foo1 { maplike<long, long>; }; - interface Foo2 { + interface mixin Foo2 { void entries(); }; interface Foo3 : Foo1 { }; - Foo3 implements Foo2; + Foo3 includes Foo2; """, mapRWMembers, numProductions=4) shouldFail("Inheritance of name collision with child maplike/setlike", @@ -548,7 +586,7 @@ def WebIDLTest(parser, harness): }; """) - shouldPass("Implemented interface with readonly allowable overrides", + shouldPass("Interface with readonly allowable overrides", """ interface Foo1 { readonly setlike<long>; @@ -558,9 +596,9 @@ def WebIDLTest(parser, harness): shouldPass("JS Implemented read-only interface with readonly allowable overrides", """ - [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1", - Constructor()] + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"] interface Foo1 { + constructor(); readonly setlike<long>; readonly attribute boolean clear; }; @@ -568,9 +606,9 @@ def WebIDLTest(parser, harness): shouldFail("JS Implemented read-write interface with non-readwrite allowable overrides", """ - [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1", - Constructor()] + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"] interface Foo1 { + constructor(); setlike<long>; readonly attribute boolean clear; }; diff --git a/components/script/dom/bindings/codegen/parser/tests/test_interfacemixin.py b/components/script/dom/bindings/codegen/parser/tests/test_interfacemixin.py new file mode 100644 index 00000000000..477a9f37799 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_interfacemixin.py @@ -0,0 +1,437 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse("interface mixin Foo { };") + results = parser.finish() + harness.ok(True, "Empty interface mixin parsed without error.") + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterfaceMixin), + "Should be an IDLInterfaceMixin") + mixin = results[0] + harness.check(mixin.identifier.QName(), "::Foo", "Interface mixin has the right QName") + harness.check(mixin.identifier.name, "Foo", "Interface mixin has the right name") + + parser = parser.reset() + parser.parse(""" + interface mixin QNameBase { + const long foo = 3; + }; + """) + results = parser.finish() + harness.check(len(results), 1, "Should be one productions") + harness.ok(isinstance(results[0], WebIDL.IDLInterfaceMixin), + "Should be an IDLInterfaceMixin") + harness.check(len(results[0].members), 1, "Expect 1 productions") + mixin = results[0] + harness.check(mixin.members[0].identifier.QName(), "::QNameBase::foo", + "Member has the right QName") + + parser = parser.reset() + parser.parse(""" + interface mixin A { + readonly attribute boolean x; + void foo(); + }; + partial interface mixin A { + readonly attribute boolean y; + void foo(long arg); + }; + """) + results = parser.finish() + harness.check(len(results), 2, + "Should have two results with partial interface mixin") + mixin = results[0] + harness.check(len(mixin.members), 3, + "Should have three members with partial interface mixin") + harness.check(mixin.members[0].identifier.name, "x", + "First member should be x with partial interface mixin") + harness.check(mixin.members[1].identifier.name, "foo", + "Second member should be foo with partial interface mixin") + harness.check(len(mixin.members[1].signatures()), 2, + "Should have two foo signatures with partial interface mixin") + harness.check(mixin.members[2].identifier.name, "y", + "Third member should be y with partial interface mixin") + + parser = parser.reset() + parser.parse(""" + partial interface mixin A { + readonly attribute boolean y; + void foo(long arg); + }; + interface mixin A { + readonly attribute boolean x; + void foo(); + }; + """) + results = parser.finish() + harness.check(len(results), 2, + "Should have two results with reversed partial interface mixin") + mixin = results[1] + harness.check(len(mixin.members), 3, + "Should have three members with reversed partial interface mixin") + harness.check(mixin.members[0].identifier.name, "x", + "First member should be x with reversed partial interface mixin") + harness.check(mixin.members[1].identifier.name, "foo", + "Second member should be foo with reversed partial interface mixin") + harness.check(len(mixin.members[1].signatures()), 2, + "Should have two foo signatures with reversed partial interface mixin") + harness.check(mixin.members[2].identifier.name, "y", + "Third member should be y with reversed partial interface mixin") + + parser = parser.reset() + parser.parse(""" + interface Interface {}; + interface mixin Mixin { + attribute short x; + }; + Interface includes Mixin; + """) + results = parser.finish() + iface = results[0] + harness.check(len(iface.members), 1, "Should merge members from mixins") + harness.check(iface.members[0].identifier.name, "x", + "Should merge members from mixins") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin A { + readonly attribute boolean x; + }; + interface mixin A { + readonly attribute boolean y; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow two non-partial interface mixins with the same name") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + partial interface mixin A { + readonly attribute boolean x; + }; + partial interface mixin A { + readonly attribute boolean y; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Must have a non-partial interface mixin for a given name") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + boolean x; + }; + partial interface mixin A { + readonly attribute boolean y; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow a name collision between partial interface " + "mixin and other object") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + boolean x; + }; + interface mixin A { + readonly attribute boolean y; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow a name collision between interface mixin " + "and other object") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin A { + readonly attribute boolean x; + }; + interface A; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow a name collision between external interface " + "and interface mixin") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [SomeRandomAnnotation] + interface mixin A { + readonly attribute boolean y; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow unknown extended attributes on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin A { + getter double (DOMString propertyName); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow getters on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin A { + setter void (DOMString propertyName, double propertyValue); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow setters on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin A { + deleter void (DOMString propertyName); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow deleters on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin A { + legacycaller double compute(double x); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow legacycallers on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin A { + inherit attribute x; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow inherited attribute on interface mixins") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Interface {}; + interface NotMixin { + attribute short x; + }; + Interface includes NotMixin; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should fail if the right side does not point an interface mixin") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin NotInterface {}; + interface mixin Mixin { + attribute short x; + }; + NotInterface includes Mixin; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should fail if the left side does not point an interface") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin Mixin { + iterable<DOMString>; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should fail if an interface mixin includes iterable") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin Mixin { + setlike<DOMString>; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should fail if an interface mixin includes setlike") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface mixin Mixin { + maplike<DOMString, DOMString>; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should fail if an interface mixin includes maplike") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Interface { + attribute short attr; + }; + interface mixin Mixin { + attribute short attr; + }; + Interface includes Mixin; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should fail if the included mixin interface has duplicated member") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Interface {}; + interface mixin Mixin1 { + attribute short attr; + }; + interface mixin Mixin2 { + attribute short attr; + }; + Interface includes Mixin1; + Interface includes Mixin2; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should fail if the included mixin interfaces have duplicated member") + + parser = parser.reset() + parser.parse(""" + [Global, Exposed=Window] interface Window {}; + [Global, Exposed=Worker] interface Worker {}; + [Exposed=Window] + interface Base {}; + interface mixin Mixin { + Base returnSelf(); + }; + Base includes Mixin; + """) + results = parser.finish() + base = results[2] + attr = base.members[0] + harness.check(attr.exposureSet, set(["Window"]), + "Should expose on globals where the base interfaces are exposed") + + parser = parser.reset() + parser.parse(""" + [Global, Exposed=Window] interface Window {}; + [Global, Exposed=Worker] interface Worker {}; + [Exposed=Window] + interface Base {}; + [Exposed=Window] + interface mixin Mixin { + attribute short a; + }; + Base includes Mixin; + """) + results = parser.finish() + base = results[2] + attr = base.members[0] + harness.check(attr.exposureSet, set(["Window"]), + "Should follow [Exposed] on interface mixin") + + parser = parser.reset() + parser.parse(""" + [Global, Exposed=Window] interface Window {}; + [Global, Exposed=Worker] interface Worker {}; + [Exposed=Window] + interface Base1 {}; + [Exposed=Worker] + interface Base2 {}; + interface mixin Mixin { + attribute short a; + }; + Base1 includes Mixin; + Base2 includes Mixin; + """) + results = parser.finish() + base = results[2] + attr = base.members[0] + harness.check(attr.exposureSet, set(["Window", "Worker"]), + "Should expose on all globals where including interfaces are " + "exposed") + base = results[3] + attr = base.members[0] + harness.check(attr.exposureSet, set(["Window", "Worker"]), + "Should expose on all globals where including interfaces are " + "exposed") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_method.py b/components/script/dom/bindings/codegen/parser/tests/test_method.py index cf7f1b40d76..88ee874386c 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_method.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_method.py @@ -41,7 +41,7 @@ def WebIDLTest(parser, harness): harness.check(argument.variadic, variadic, "Argument has the right variadic value") def checkMethod(method, QName, name, signatures, - static=False, getter=False, setter=False, creator=False, + static=False, getter=False, setter=False, deleter=False, legacycaller=False, stringifier=False): harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod") @@ -53,7 +53,6 @@ def WebIDLTest(parser, harness): harness.check(method.isStatic(), static, "Method has the correct static value") harness.check(method.isGetter(), getter, "Method has the correct getter value") harness.check(method.isSetter(), setter, "Method has the correct setter value") - harness.check(method.isCreator(), creator, "Method has the correct creator value") harness.check(method.isDeleter(), deleter, "Method has the correct deleter value") harness.check(method.isLegacycaller(), legacycaller, "Method has the correct legacycaller value") harness.check(method.isStringifier(), stringifier, "Method has the correct stringifier value") @@ -121,7 +120,7 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(not threw, "Should allow integer to float type corecion") @@ -134,7 +133,7 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should not allow [GetterThrows] on methods") @@ -147,7 +146,7 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should not allow [SetterThrows] on methods") @@ -160,7 +159,7 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should spell [Throws] correctly on methods") @@ -173,6 +172,85 @@ def WebIDLTest(parser, harness): }; """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should not allow __noSuchMethod__ methods") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [Throws, LenientFloat] + void foo(float myFloat); + [Throws] + void foo(); + }; + """) + results = parser.finish() + except Exception as x: + threw = True + harness.ok(not threw, "Should allow LenientFloat to be only in a specific overload") + + parser = parser.reset() + parser.parse(""" + interface A { + [Throws] + void foo(); + [Throws, LenientFloat] + void foo(float myFloat); + }; + """) + results = parser.finish() + iface = results[0] + methods = iface.members + lenientFloat = methods[0].getExtendedAttribute("LenientFloat") + harness.ok(lenientFloat is not None, "LenientFloat in overloads must be added to the method") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [Throws, LenientFloat] + void foo(float myFloat); + [Throws] + void foo(float myFloat, float yourFloat); + }; + """) + results = parser.finish() + except Exception as x: + threw = True + harness.ok(threw, "Should prevent overloads from getting different restricted float behavior") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [Throws] + void foo(float myFloat, float yourFloat); + [Throws, LenientFloat] + void foo(float myFloat); + }; + """) + results = parser.finish() + except Exception as x: + threw = True + harness.ok(threw, "Should prevent overloads from getting different restricted float behavior (2)") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [Throws, LenientFloat] + void foo(float myFloat); + [Throws, LenientFloat] + void foo(short myShort); + }; + """) + results = parser.finish() + except Exception as x: + threw = True + harness.ok(threw, "Should prevent overloads from getting redundant [LenientFloat]") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_namespace.py b/components/script/dom/bindings/codegen/parser/tests/test_namespace.py index 74533a1770e..62edb270c63 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_namespace.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_namespace.py @@ -70,7 +70,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -85,7 +85,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -104,7 +104,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -123,7 +123,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -142,7 +142,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -161,7 +161,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -180,7 +180,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -199,7 +199,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -218,6 +218,6 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_newobject.py b/components/script/dom/bindings/codegen/parser/tests/test_newobject.py new file mode 100644 index 00000000000..26785c6a270 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_newobject.py @@ -0,0 +1,70 @@ +# Import the WebIDL module, so we can do isinstance checks and whatnot +import WebIDL + +def WebIDLTest(parser, harness): + # Basic functionality + parser.parse( + """ + interface Iface { + [NewObject] readonly attribute Iface attr; + [NewObject] Iface method(); + }; + """) + results = parser.finish() + harness.ok(results, "Should not have thrown on basic [NewObject] usage") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Iface { + [Pure, NewObject] readonly attribute Iface attr; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "[NewObject] attributes must depend on something") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Iface { + [Pure, NewObject] Iface method(); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "[NewObject] methods must depend on something") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Iface { + [Cached, NewObject, Affects=Nothing] readonly attribute Iface attr; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "[NewObject] attributes must not be [Cached]") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Iface { + [StoreInSlot, NewObject, Affects=Nothing] readonly attribute Iface attr; + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "[NewObject] attributes must not be [StoreInSlot]") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py b/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py index 2b48b615dd4..8ba6771677a 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py @@ -80,7 +80,7 @@ def checkEquivalent(iface, harness): for attr in dir(type1): if attr.startswith('_') or \ attr in ['nullable', 'builtin', 'filename', 'location', - 'inner', 'QName', 'getDeps', 'name'] or \ + 'inner', 'QName', 'getDeps', 'name', 'prettyName'] or \ (hasattr(type(type1), attr) and not callable(getattr(type1, attr))): continue diff --git a/components/script/dom/bindings/codegen/parser/tests/test_promise.py b/components/script/dom/bindings/codegen/parser/tests/test_promise.py index 55bc0768092..43c74029dc5 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_promise.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_promise.py @@ -2,7 +2,6 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - interface _Promise {}; interface A { legacycaller Promise<any> foo(); }; @@ -18,7 +17,6 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - interface _Promise {}; interface A { Promise<any> foo(); long foo(long arg); @@ -35,7 +33,6 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - interface _Promise {}; interface A { long foo(long arg); Promise<any> foo(); @@ -49,8 +46,35 @@ def WebIDLTest(parser, harness): "non-Promise return types.") parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + Promise<any>? foo(); + }; + """) + results = parser.finish(); + except: + threw = True + harness.ok(threw, + "Should not allow nullable Promise return values.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + void foo(Promise<any>? arg); + }; + """) + results = parser.finish(); + except: + threw = True + harness.ok(threw, + "Should not allow nullable Promise arguments.") + + parser = parser.reset() parser.parse(""" - interface _Promise {}; interface A { Promise<any> foo(); Promise<any> foo(long arg); @@ -61,3 +85,73 @@ def WebIDLTest(parser, harness): harness.ok(True, "Should allow overloads which only have Promise and return " "types.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + attribute Promise<any> attr; + }; + """) + results = parser.finish(); + except: + threw = True + harness.ok(threw, + "Should not allow writable Promise-typed attributes.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [LenientSetter] readonly attribute Promise<any> attr; + }; + """) + results = parser.finish(); + except: + threw = True + harness.ok(threw, + "Should not allow [LenientSetter] Promise-typed attributes.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [PutForwards=bar] readonly attribute Promise<any> attr; + }; + """) + results = parser.finish(); + except: + threw = True + harness.ok(threw, + "Should not allow [PutForwards] Promise-typed attributes.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [Replaceable] readonly attribute Promise<any> attr; + }; + """) + results = parser.finish(); + except: + threw = True + harness.ok(threw, + "Should not allow [Replaceable] Promise-typed attributes.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [SameObject] readonly attribute Promise<any> attr; + }; + """) + results = parser.finish(); + except: + threw = True + harness.ok(threw, + "Should not allow [SameObject] Promise-typed attributes.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_mozmap.py b/components/script/dom/bindings/codegen/parser/tests/test_record.py index 1a36fdd62c4..d50572caf07 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_mozmap.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_record.py @@ -3,8 +3,8 @@ import WebIDL def WebIDLTest(parser, harness): parser.parse(""" dictionary Dict {}; - interface MozMapArg { - void foo(MozMap<Dict> arg); + interface RecordArg { + void foo(record<DOMString, Dict> arg); }; """) @@ -19,7 +19,7 @@ def WebIDLTest(parser, harness): signature = members[0].signatures()[0] args = signature[1] harness.check(len(args), 1, "Should have one arg") - harness.ok(args[0].type.isMozMap(), "Should have a MozMap type here") + harness.ok(args[0].type.isRecord(), "Should have a record type here") harness.ok(args[0].type.inner.isDictionary(), "Should have a dictionary inner type") @@ -27,13 +27,27 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - interface MozMapVoidArg { - void foo(MozMap<void> arg); + interface RecordVoidArg { + void foo(record<DOMString, void> arg); }; """) results = parser.finish() - except Exception,x: + except Exception as x: threw = True + harness.ok(threw, "Should have thrown because record can't have void as value type.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary Dict { + record<DOMString, Dict> val; + }; + """) - harness.ok(threw, "Should have thrown.") + results = parser.finish() + except Exception as x: + threw = True + harness.ok(threw, + "Should have thrown on dictionary containing itself via record.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_securecontext_extended_attribute.py b/components/script/dom/bindings/codegen/parser/tests/test_securecontext_extended_attribute.py index 084f19fa7f5..442dba45d76 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_securecontext_extended_attribute.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_securecontext_extended_attribute.py @@ -288,33 +288,32 @@ def WebIDLTest(parser, harness): threw = True harness.ok(threw, "[SecureContext] must appear on interfaces that inherit from another [SecureContext] interface") - # Test 'implements'. The behavior tested here may have to change depending - # on the resolution of https://github.com/heycam/webidl/issues/118 + # Test 'includes'. parser = parser.reset() parser.parse(""" [SecureContext] - interface TestSecureContextInterfaceThatImplementsNonSecureContextInterface { + interface TestSecureContextInterfaceThatIncludesNonSecureContextMixin { const octet TEST_CONSTANT = 0; }; - interface TestNonSecureContextInterface { + interface mixin TestNonSecureContextMixin { const octet TEST_CONSTANT_2 = 0; readonly attribute byte testAttribute2; void testMethod2(byte foo); }; - TestSecureContextInterfaceThatImplementsNonSecureContextInterface implements TestNonSecureContextInterface; + TestSecureContextInterfaceThatIncludesNonSecureContextMixin includes TestNonSecureContextMixin; """) results = parser.finish() - harness.check(len(results[0].members), 4, "TestSecureContextInterfaceThatImplementsNonSecureContextInterface should have two members") + harness.check(len(results[0].members), 4, "TestSecureContextInterfaceThatImplementsNonSecureContextInterface should have four members") harness.ok(results[0].getExtendedAttribute("SecureContext"), "Interface should have [SecureContext] extended attribute") harness.ok(results[0].members[0].getExtendedAttribute("SecureContext"), "[SecureContext] should propagate from interface to constant members even when other members are copied from a non-[SecureContext] interface") harness.ok(results[0].members[1].getExtendedAttribute("SecureContext") is None, - "Constants copied from non-[SecureContext] interface should not be [SecureContext]") + "Constants copied from non-[SecureContext] mixin should not be [SecureContext]") harness.ok(results[0].members[2].getExtendedAttribute("SecureContext") is None, - "Attributes copied from non-[SecureContext] interface should not be [SecureContext]") + "Attributes copied from non-[SecureContext] mixin should not be [SecureContext]") harness.ok(results[0].members[3].getExtendedAttribute("SecureContext") is None, - "Methods copied from non-[SecureContext] interface should not be [SecureContext]") + "Methods copied from non-[SecureContext] mixin should not be [SecureContext]") # Test SecureContext and NoInterfaceObject parser = parser.reset() diff --git a/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py b/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py index 5ea1743d36a..52cfcb96817 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py @@ -222,73 +222,3 @@ def WebIDLTest(parser, harness): threw = True harness.ok(threw, "Should have thrown.") - - threw = False - try: - parser.parse(""" - interface SpecialMethodSignatureMismatch20 { - creator long long foo(long index, long long value); - }; - """) - - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown.") - - threw = False - try: - parser.parse(""" - interface SpecialMethodSignatureMismatch22 { - creator boolean foo(unsigned long index, boolean value, long long extraArg); - }; - """) - - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown.") - - threw = False - try: - parser.parse(""" - interface SpecialMethodSignatureMismatch23 { - creator boolean foo(unsigned long index, boolean... value); - }; - """) - - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown.") - - threw = False - try: - parser.parse(""" - interface SpecialMethodSignatureMismatch24 { - creator boolean foo(unsigned long index, optional boolean value); - }; - """) - - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown.") - - threw = False - try: - parser.parse(""" - interface SpecialMethodSignatureMismatch25 { - creator boolean foo(); - }; - """) - - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py b/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py index 695cfe4f250..7f911733b62 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py @@ -5,26 +5,21 @@ def WebIDLTest(parser, harness): interface SpecialMethods { getter long long (unsigned long index); setter long long (unsigned long index, long long value); - creator long long (unsigned long index, long long value); - deleter long long (unsigned long index); getter boolean (DOMString name); setter boolean (DOMString name, boolean value); - creator boolean (DOMString name, boolean value); deleter boolean (DOMString name); + readonly attribute unsigned long length; }; interface SpecialMethodsCombination { - getter deleter long long (unsigned long index); - setter creator long long (unsigned long index, long long value); getter deleter boolean (DOMString name); - setter creator boolean (DOMString name, boolean value); }; """) results = parser.finish() def checkMethod(method, QName, name, - static=False, getter=False, setter=False, creator=False, + static=False, getter=False, setter=False, deleter=False, legacycaller=False, stringifier=False): harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod") @@ -33,7 +28,6 @@ def WebIDLTest(parser, harness): harness.check(method.isStatic(), static, "Method has the correct static value") harness.check(method.isGetter(), getter, "Method has the correct getter value") harness.check(method.isSetter(), setter, "Method has the correct setter value") - harness.check(method.isCreator(), creator, "Method has the correct creator value") harness.check(method.isDeleter(), deleter, "Method has the correct deleter value") harness.check(method.isLegacycaller(), legacycaller, "Method has the correct legacycaller value") harness.check(method.isStringifier(), stringifier, "Method has the correct stringifier value") @@ -41,33 +35,39 @@ def WebIDLTest(parser, harness): harness.check(len(results), 2, "Expect 2 interfaces") iface = results[0] - harness.check(len(iface.members), 8, "Expect 8 members") + harness.check(len(iface.members), 6, "Expect 6 members") checkMethod(iface.members[0], "::SpecialMethods::__indexedgetter", "__indexedgetter", getter=True) checkMethod(iface.members[1], "::SpecialMethods::__indexedsetter", "__indexedsetter", setter=True) - checkMethod(iface.members[2], "::SpecialMethods::__indexedcreator", "__indexedcreator", - creator=True) - checkMethod(iface.members[3], "::SpecialMethods::__indexeddeleter", "__indexeddeleter", - deleter=True) - checkMethod(iface.members[4], "::SpecialMethods::__namedgetter", "__namedgetter", + checkMethod(iface.members[2], "::SpecialMethods::__namedgetter", "__namedgetter", getter=True) - checkMethod(iface.members[5], "::SpecialMethods::__namedsetter", "__namedsetter", + checkMethod(iface.members[3], "::SpecialMethods::__namedsetter", "__namedsetter", setter=True) - checkMethod(iface.members[6], "::SpecialMethods::__namedcreator", "__namedcreator", - creator=True) - checkMethod(iface.members[7], "::SpecialMethods::__nameddeleter", "__nameddeleter", + checkMethod(iface.members[4], "::SpecialMethods::__nameddeleter", "__nameddeleter", deleter=True) iface = results[1] - harness.check(len(iface.members), 4, "Expect 4 members") + harness.check(len(iface.members), 1, "Expect 1 member") - checkMethod(iface.members[0], "::SpecialMethodsCombination::__indexedgetterdeleter", - "__indexedgetterdeleter", getter=True, deleter=True) - checkMethod(iface.members[1], "::SpecialMethodsCombination::__indexedsettercreator", - "__indexedsettercreator", setter=True, creator=True) - checkMethod(iface.members[2], "::SpecialMethodsCombination::__namedgetterdeleter", + checkMethod(iface.members[0], "::SpecialMethodsCombination::__namedgetterdeleter", "__namedgetterdeleter", getter=True, deleter=True) - checkMethod(iface.members[3], "::SpecialMethodsCombination::__namedsettercreator", - "__namedsettercreator", setter=True, creator=True) + + parser = parser.reset(); + + threw = False + try: + parser.parse( + """ + interface IndexedDeleter { + deleter void(unsigned long index); + }; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "There are no indexed deleters") + + diff --git a/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py b/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py index 42e2c5bb71b..9bf3d903463 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py @@ -35,23 +35,8 @@ def WebIDLTest(parser, harness): try: parser.parse(""" interface SpecialMethodUniqueness1 { - setter creator boolean (DOMString name); - creator boolean (DOMString name); - }; - """) - - results = parser.finish() - except: - threw = True - - harness.ok(threw, "Should have thrown.") - - threw = False - try: - parser.parse(""" - interface SpecialMethodUniqueness1 { setter boolean (DOMString name); - creator setter boolean (DOMString name); + setter boolean (DOMString name); }; """) diff --git a/components/script/dom/bindings/codegen/parser/tests/test_stringifier.py b/components/script/dom/bindings/codegen/parser/tests/test_stringifier.py index 14c2c5226fc..deabdc5ec81 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_stringifier.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_stringifier.py @@ -44,3 +44,103 @@ def WebIDLTest(parser, harness): harness.ok(threw, "Should not allow a 'stringifier;' and a 'stringifier()'") + parser = parser.reset() + parser.parse(""" + interface TestStringifier { + stringifier attribute DOMString foo; + }; + """) + results = parser.finish() + harness.ok(isinstance(results[0].members[0], WebIDL.IDLAttribute), + "Stringifier attribute should be an attribute") + stringifier = results[0].members[1] + harness.ok(isinstance(stringifier, WebIDL.IDLMethod), + "Stringifier attribute should insert a method") + harness.ok(stringifier.isStringifier(), + "Inserted method should be a stringifier") + + parser = parser.reset() + parser.parse(""" + interface TestStringifier {}; + interface mixin TestStringifierMixin { + stringifier attribute DOMString foo; + }; + TestStringifier includes TestStringifierMixin; + """) + results = parser.finish() + harness.ok(isinstance(results[0].members[0], WebIDL.IDLAttribute), + "Stringifier attribute should be an attribute") + stringifier = results[0].members[1] + harness.ok(isinstance(stringifier, WebIDL.IDLMethod), + "Stringifier attribute should insert a method") + harness.ok(stringifier.isStringifier(), + "Inserted method should be a stringifier") + + parser = parser.reset() + parser.parse(""" + interface TestStringifier { + stringifier attribute USVString foo; + }; + """) + results = parser.finish() + stringifier = results[0].members[1] + harness.ok(stringifier.signatures()[0][0].isUSVString(), + "Stringifier attributes should allow USVString") + + parser = parser.reset() + parser.parse(""" + interface TestStringifier { + [Throws, NeedsSubjectPrincipal] + stringifier attribute USVString foo; + }; + """) + results = parser.finish() + stringifier = results[0].members[1] + harness.ok(stringifier.getExtendedAttribute("Throws"), + "Stringifier attributes should support [Throws]") + harness.ok(stringifier.getExtendedAttribute("NeedsSubjectPrincipal"), + "Stringifier attributes should support [NeedsSubjectPrincipal]") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestStringifier { + stringifier attribute ByteString foo; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow ByteString") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestStringifier { + stringifier; + stringifier attribute DOMString foo; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow a 'stringifier;' and a stringifier attribute") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestStringifier { + stringifier attribute DOMString foo; + stringifier attribute DOMString bar; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow multiple stringifier attributes") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_toJSON.py b/components/script/dom/bindings/codegen/parser/tests/test_toJSON.py new file mode 100644 index 00000000000..ad01330e65a --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_toJSON.py @@ -0,0 +1,195 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse( + """ + interface Test { + object toJSON(); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(not threw, "Should allow a toJSON method.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + object toJSON(object arg); + object toJSON(long arg); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "Should not allow overloads of a toJSON method.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + object toJSON(object arg); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "Should not allow a toJSON method with arguments.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + long toJSON(); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(not threw, "Should allow a toJSON method with 'long' as return type.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + [Default] object toJSON(); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(not threw, "Should allow a default toJSON method with 'object' as return type.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface Test { + [Default] long toJSON(); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "Should not allow a default toJSON method with non-'object' as return type.") + + JsonTypes = [ "byte", "octet", "short", "unsigned short", "long", "unsigned long", "long long", + "unsigned long long", "float", "unrestricted float", "double", "unrestricted double", "boolean", + "DOMString", "ByteString", "UTF8String", "USVString", "Enum", "InterfaceWithToJSON", "object" ] + + nonJsonTypes = [ "InterfaceWithoutToJSON", "any", "Int8Array", "Int16Array", "Int32Array","Uint8Array", + "Uint16Array", "Uint32Array", "Uint8ClampedArray", "Float32Array", "Float64Array", "ArrayBuffer" ] + + def doTest(testIDL, shouldThrow, description): + p = parser.reset() + threw = False + try: + p.parse(testIDL + + """ + enum Enum { "a", "b", "c" }; + interface InterfaceWithToJSON { long toJSON(); }; + interface InterfaceWithoutToJSON {}; + """); + p.finish(); + except Exception as x: + threw = True + harness.ok(x.message == "toJSON method has non-JSON return type", x) + harness.check(threw, shouldThrow, description) + + + for type in JsonTypes: + doTest("interface Test { %s toJSON(); };" % type, False, + "%s should be a JSON type" % type) + + doTest("interface Test { sequence<%s> toJSON(); };" % type, False, + "sequence<%s> should be a JSON type" % type) + + doTest("dictionary Foo { %s foo; }; " + "interface Test { Foo toJSON(); }; " % type, False, + "dictionary containing only JSON type (%s) should be a JSON type" % type) + + doTest("dictionary Foo { %s foo; }; dictionary Bar : Foo { }; " + "interface Test { Bar toJSON(); }; " % type, False, + "dictionary whose ancestors only contain JSON types should be a JSON type") + + doTest("dictionary Foo { any foo; }; dictionary Bar : Foo { %s bar; };" + "interface Test { Bar toJSON(); };" % type, True, + "dictionary whose ancestors contain non-JSON types should not be a JSON type") + + doTest("interface Test { record<DOMString, %s> toJSON(); };" % type, False, + "record<DOMString, %s> should be a JSON type" % type) + + doTest("interface Test { record<ByteString, %s> toJSON(); };" % type, False, + "record<ByteString, %s> should be a JSON type" % type) + + doTest("interface Test { record<UTF8String, %s> toJSON(); };" % type, False, + "record<UTF8String, %s> should be a JSON type" % type) + + doTest("interface Test { record<USVString, %s> toJSON(); };" % type, False, + "record<USVString, %s> should be a JSON type" % type) + + otherUnionType = "Foo" if type != "object" else "long" + doTest("interface Foo { object toJSON(); };" + "interface Test { (%s or %s) toJSON(); };" % (otherUnionType, type), False, + "union containing only JSON types (%s or %s) should be a JSON type" %(otherUnionType, type)) + + doTest("interface test { %s? toJSON(); };" % type, False, + "Nullable type (%s) should be a JSON type" % type) + + doTest("interface Foo : InterfaceWithoutToJSON { %s toJSON(); };" + "interface Test { Foo toJSON(); };" % type, False, + "interface with toJSON should be a JSON type") + + doTest("interface Foo : InterfaceWithToJSON { };" + "interface Test { Foo toJSON(); };", False, + "inherited interface with toJSON should be a JSON type") + + for type in nonJsonTypes: + doTest("interface Test { %s toJSON(); };" % type, True, + "%s should not be a JSON type" % type) + + doTest("interface Test { sequence<%s> toJSON(); };" % type, True, + "sequence<%s> should not be a JSON type" % type) + + doTest("dictionary Foo { %s foo; }; " + "interface Test { Foo toJSON(); }; " % type, True, + "Dictionary containing a non-JSON type (%s) should not be a JSON type" % type) + + doTest("dictionary Foo { %s foo; }; dictionary Bar : Foo { }; " + "interface Test { Bar toJSON(); }; " % type, True, + "dictionary whose ancestors only contain non-JSON types should not be a JSON type") + + doTest("interface Test { record<DOMString, %s> toJSON(); };" % type, True, + "record<DOMString, %s> should not be a JSON type" % type) + + doTest("interface Test { record<ByteString, %s> toJSON(); };" % type, True, + "record<ByteString, %s> should not be a JSON type" % type) + + doTest("interface Test { record<USVString, %s> toJSON(); };" % type, True, + "record<USVString, %s> should not be a JSON type" % type) + + if type != "any": + doTest("interface Foo { object toJSON(); }; " + "interface Test { (Foo or %s) toJSON(); };" % type, True, + "union containing a non-JSON type (%s) should not be a JSON type" % type) + + doTest("interface test { %s? toJSON(); };" % type, True, + "Nullable type (%s) should not be a JSON type" % type) + + doTest("dictionary Foo { long foo; any bar; };" + "interface Test { Foo toJSON(); };", True, + "dictionary containing a non-JSON type should not be a JSON type") + + doTest("interface Foo : InterfaceWithoutToJSON { }; " + "interface Test { Foo toJSON(); };", True, + "interface without toJSON should not be a JSON type") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_typedef.py b/components/script/dom/bindings/codegen/parser/tests/test_typedef.py index 8921985c5ca..b5fc1c68890 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_typedef.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_typedef.py @@ -4,15 +4,15 @@ def WebIDLTest(parser, harness): typedef long? mynullablelong; interface Foo { const mylong X = 5; - const mynullablelong Y = 7; - const mynullablelong Z = null; - void foo(mylong arg); + void foo(optional mynullablelong arg = 7); + void bar(optional mynullablelong arg = null); + void baz(mylong arg); }; """) results = parser.finish() - harness.check(results[2].members[1].type.name, "LongOrNull", + harness.check(results[2].members[1].signatures()[0][1][0].type.name, "LongOrNull", "Should expand typedefs") parser = parser.reset() diff --git a/components/script/dom/bindings/codegen/parser/tests/test_typedef_identifier_conflict.py b/components/script/dom/bindings/codegen/parser/tests/test_typedef_identifier_conflict.py new file mode 100644 index 00000000000..0ea38ce437b --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_typedef_identifier_conflict.py @@ -0,0 +1,16 @@ +def WebIDLTest(parser, harness): + exception = None + try: + parser.parse( + """ + typedef long foo; + typedef long foo; + """) + + results = parser.finish() + except Exception as e: + exception = e + + harness.ok(exception, "Should have thrown.") + harness.ok("Multiple unresolvable definitions of identifier 'foo'" in str(exception), + "Should have a sane exception message") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_unenumerable_own_properties.py b/components/script/dom/bindings/codegen/parser/tests/test_unenumerable_own_properties.py index d017d5ce092..d28cc1ec052 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_unenumerable_own_properties.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_unenumerable_own_properties.py @@ -24,7 +24,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -39,7 +39,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") @@ -59,6 +59,6 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception, x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_unforgeable.py b/components/script/dom/bindings/codegen/parser/tests/test_unforgeable.py index 3787e8c6af1..770a9d3736f 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_unforgeable.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_unforgeable.py @@ -111,7 +111,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception,x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown when shadowing unforgeable attribute on " @@ -130,7 +130,7 @@ def WebIDLTest(parser, harness): """) results = parser.finish() - except Exception,x: + except Exception as x: threw = True harness.ok(threw, "Should have thrown when shadowing unforgeable operation on " @@ -141,16 +141,16 @@ def WebIDLTest(parser, harness): interface Child : Parent { }; interface Parent {}; - interface Consequential { + interface mixin Mixin { [Unforgeable] readonly attribute long foo; }; - Parent implements Consequential; + Parent includes Mixin; """) results = parser.finish() harness.check(len(results), 4, "Should be able to inherit from an interface with a " - "consequential interface with [Unforgeable] properties.") + "mixin with [Unforgeable] properties.") parser = parser.reset(); threw = False @@ -160,10 +160,10 @@ def WebIDLTest(parser, harness): void foo(); }; interface Parent {}; - interface Consequential { + interface mixin Mixin { [Unforgeable] readonly attribute long foo; }; - Parent implements Consequential; + Parent includes Mixin; """) results = parser.finish() @@ -182,14 +182,14 @@ def WebIDLTest(parser, harness): }; interface Parent : GrandParent {}; interface GrandParent {}; - interface Consequential { + interface mixin Mixin { [Unforgeable] readonly attribute long foo; }; - GrandParent implements Consequential; - interface ChildConsequential { + GrandParent includes Mixin; + interface mixin ChildMixin { void foo(); }; - Child implements ChildConsequential; + Child includes ChildMixin; """) results = parser.finish() @@ -208,14 +208,14 @@ def WebIDLTest(parser, harness): }; interface Parent : GrandParent {}; interface GrandParent {}; - interface Consequential { + interface mixin Mixin { [Unforgeable] void foo(); }; - GrandParent implements Consequential; - interface ChildConsequential { + GrandParent includes Mixin; + interface mixin ChildMixin { void foo(); }; - Child implements ChildConsequential; + Child includes ChildMixin; """) results = parser.finish() diff --git a/components/script/dom/bindings/codegen/parser/tests/test_union.py b/components/script/dom/bindings/codegen/parser/tests/test_union.py index 9c4f2a56ab6..801314fd0bd 100644 --- a/components/script/dom/bindings/codegen/parser/tests/test_union.py +++ b/components/script/dom/bindings/codegen/parser/tests/test_union.py @@ -17,7 +17,7 @@ def combinations(iterable, r): n = len(pool) if r > n: return - indices = range(r) + indices = list(range(r)) yield tuple(pool[i] for i in indices) while True: for i in reversed(range(r)): diff --git a/components/script/dom/bindings/codegen/parser/union-typedef.patch b/components/script/dom/bindings/codegen/parser/union-typedef.patch index 3021e14193f..20efea8e129 100644 --- a/components/script/dom/bindings/codegen/parser/union-typedef.patch +++ b/components/script/dom/bindings/codegen/parser/union-typedef.patch @@ -1,22 +1,22 @@ --- WebIDL.py +++ WebIDL.py -@@ -2481,10 +2481,18 @@ class IDLUnionType(IDLType): +@@ -2624,10 +2624,18 @@ class IDLUnionType(IDLType): return type.name - + for (i, type) in enumerate(self.memberTypes): +- if not type.isComplete(): + # Exclude typedefs because if given "typedef (B or C) test", + # we want AOrTest, not AOrBOrC + if not type.isComplete() and not isinstance(type, IDLTypedefType): -+ self.memberTypes[i] = type.complete(scope) -+ -+ self.name = "Or".join(typeName(type) for type in self.memberTypes) + self.memberTypes[i] = type.complete(scope) + + self.name = "Or".join(typeName(type) for type in self.memberTypes) + + # We do this again to complete the typedef types + for (i, type) in enumerate(self.memberTypes): - if not type.isComplete(): - self.memberTypes[i] = type.complete(scope) - -- self.name = "Or".join(typeName(type) for type in self.memberTypes) ++ if not type.isComplete(): ++ self.memberTypes[i] = type.complete(scope) ++ self.flatMemberTypes = list(self.memberTypes) i = 0 while i < len(self.flatMemberTypes): diff --git a/components/script/dom/bindings/codegen/parser/update.sh b/components/script/dom/bindings/codegen/parser/update.sh index 213aba6df89..dd7803c940c 100755 --- a/components/script/dom/bindings/codegen/parser/update.sh +++ b/components/script/dom/bindings/codegen/parser/update.sh @@ -1,13 +1,12 @@ wget https://hg.mozilla.org/mozilla-central/raw-file/tip/dom/bindings/parser/WebIDL.py -O WebIDL.py patch < abstract.patch patch < debug.patch -patch < pref-main-thread.patch patch < callback-location.patch patch < union-typedef.patch patch < inline.patch -wget https://hg.mozilla.org/mozilla-central/archive/tip.tar.gz/dom/bindings/parser/tests/ -O tests.tar.gz +wget https://hg.mozilla.org/mozilla-central/archive/tip.zip/dom/bindings/parser/tests/ -O tests.zip rm -r tests mkdir tests -tar xvpf tests.tar.gz -C tests --strip-components=5 -rm tests.tar.gz WebIDL.py.orig +unzip -d tests -j tests.zip +rm tests.zip WebIDL.py.orig diff --git a/components/script/dom/bindings/codegen/ply/README b/components/script/dom/bindings/codegen/ply/README index 2459c490197..d3de9993360 100644 --- a/components/script/dom/bindings/codegen/ply/README +++ b/components/script/dom/bindings/codegen/ply/README @@ -3,7 +3,7 @@ http://www.dabeaz.com/ply/ Licensed under BSD. -This directory contains just the code and license from PLY version 3.3; +This directory contains just the code and license from PLY version 4.0; the full distribution (see the URL) also contains examples, tests, documentation, and a longer README. diff --git a/components/script/dom/bindings/codegen/ply/ply/__init__.py b/components/script/dom/bindings/codegen/ply/ply/__init__.py index 853a985542b..87838622863 100644 --- a/components/script/dom/bindings/codegen/ply/ply/__init__.py +++ b/components/script/dom/bindings/codegen/ply/ply/__init__.py @@ -1,4 +1,6 @@ # PLY package # Author: David Beazley (dave@dabeaz.com) +# https://dabeaz.com/ply/index.html +__version__ = '4.0' __all__ = ['lex','yacc'] diff --git a/components/script/dom/bindings/codegen/ply/ply/lex.py b/components/script/dom/bindings/codegen/ply/ply/lex.py index 267ec100fc2..57b61f1779e 100644 --- a/components/script/dom/bindings/codegen/ply/ply/lex.py +++ b/components/script/dom/bindings/codegen/ply/ply/lex.py @@ -1,22 +1,24 @@ # ----------------------------------------------------------------------------- # ply: lex.py # -# Copyright (C) 2001-2009, +# Copyright (C) 2001-2020 # David M. Beazley (Dabeaz LLC) # All rights reserved. # +# Latest version: https://github.com/dabeaz/ply +# # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: -# +# # * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * Neither the name of the David Beazley or Dabeaz LLC may be used to +# and/or other materials provided with the distribution. +# * Neither the name of David Beazley or Dabeaz LLC may be used to # endorse or promote products derived from this software without -# specific prior written permission. +# specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT @@ -31,72 +33,50 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -__version__ = "3.3" -__tabversion__ = "3.2" # Version of table file used - -import re, sys, types, copy, os - -# This tuple contains known string types -try: - # Python 2.6 - StringTypes = (types.StringType, types.UnicodeType) -except AttributeError: - # Python 3.0 - StringTypes = (str, bytes) - -# Extract the code attribute of a function. Different implementations -# are for Python 2/3 compatibility. +import re +import sys +import types +import copy +import os +import inspect -if sys.version_info[0] < 3: - def func_code(f): - return f.func_code -else: - def func_code(f): - return f.__code__ +# This tuple contains acceptable string types +StringTypes = (str, bytes) # This regular expression is used to match valid token names _is_identifier = re.compile(r'^[a-zA-Z0-9_]+$') # Exception thrown when invalid token encountered and no default error # handler is defined. - class LexError(Exception): - def __init__(self,message,s): - self.args = (message,) - self.text = s + def __init__(self, message, s): + self.args = (message,) + self.text = s # Token class. This class is used to represent the tokens produced. class LexToken(object): - def __str__(self): - return "LexToken(%s,%r,%d,%d)" % (self.type,self.value,self.lineno,self.lexpos) def __repr__(self): - return str(self) + return f'LexToken({self.type},{self.value!r},{self.lineno},{self.lexpos})' -# This object is a stand-in for a logging object created by the -# logging module. +# This object is a stand-in for a logging object created by the +# logging module. class PlyLogger(object): - def __init__(self,f): + def __init__(self, f): self.f = f - def critical(self,msg,*args,**kwargs): - self.f.write((msg % args) + "\n") - def warning(self,msg,*args,**kwargs): - self.f.write("WARNING: "+ (msg % args) + "\n") + def critical(self, msg, *args, **kwargs): + self.f.write((msg % args) + '\n') + + def warning(self, msg, *args, **kwargs): + self.f.write('WARNING: ' + (msg % args) + '\n') - def error(self,msg,*args,**kwargs): - self.f.write("ERROR: " + (msg % args) + "\n") + def error(self, msg, *args, **kwargs): + self.f.write('ERROR: ' + (msg % args) + '\n') info = critical debug = critical -# Null logger is used when no output is generated. Does nothing. -class NullLogger(object): - def __getattribute__(self,name): - return self - def __call__(self,*args,**kwargs): - return self - # ----------------------------------------------------------------------------- # === Lexing Engine === # @@ -114,31 +94,32 @@ class NullLogger(object): class Lexer: def __init__(self): self.lexre = None # Master regular expression. This is a list of - # tuples (re,findex) where re is a compiled - # regular expression and findex is a list - # mapping regex group numbers to rules + # tuples (re, findex) where re is a compiled + # regular expression and findex is a list + # mapping regex group numbers to rules self.lexretext = None # Current regular expression strings self.lexstatere = {} # Dictionary mapping lexer states to master regexs self.lexstateretext = {} # Dictionary mapping lexer states to regex strings self.lexstaterenames = {} # Dictionary mapping lexer states to symbol names - self.lexstate = "INITIAL" # Current lexer state + self.lexstate = 'INITIAL' # Current lexer state self.lexstatestack = [] # Stack of lexer states self.lexstateinfo = None # State information self.lexstateignore = {} # Dictionary of ignored characters for each state self.lexstateerrorf = {} # Dictionary of error functions for each state + self.lexstateeoff = {} # Dictionary of eof functions for each state self.lexreflags = 0 # Optional re compile flags self.lexdata = None # Actual input data (as a string) self.lexpos = 0 # Current position in input text self.lexlen = 0 # Length of the input text self.lexerrorf = None # Error rule (if any) + self.lexeoff = None # EOF rule (if any) self.lextokens = None # List of valid tokens - self.lexignore = "" # Ignored characters - self.lexliterals = "" # Literal characters that can be passed through + self.lexignore = '' # Ignored characters + self.lexliterals = '' # Literal characters that can be passed through self.lexmodule = None # Module self.lineno = 1 # Current line number - self.lexoptimize = 0 # Optimized mode - def clone(self,object=None): + def clone(self, object=None): c = copy.copy(self) # If the object parameter has been supplied, it means we are attaching the @@ -146,113 +127,29 @@ class Lexer: # the lexstatere and lexstateerrorf tables. if object: - newtab = { } + newtab = {} for key, ritem in self.lexstatere.items(): newre = [] for cre, findex in ritem: - newfindex = [] - for f in findex: - if not f or not f[0]: - newfindex.append(f) - continue - newfindex.append((getattr(object,f[0].__name__),f[1])) - newre.append((cre,newfindex)) + newfindex = [] + for f in findex: + if not f or not f[0]: + newfindex.append(f) + continue + newfindex.append((getattr(object, f[0].__name__), f[1])) + newre.append((cre, newfindex)) newtab[key] = newre c.lexstatere = newtab - c.lexstateerrorf = { } + c.lexstateerrorf = {} for key, ef in self.lexstateerrorf.items(): - c.lexstateerrorf[key] = getattr(object,ef.__name__) + c.lexstateerrorf[key] = getattr(object, ef.__name__) c.lexmodule = object return c # ------------------------------------------------------------ - # writetab() - Write lexer information to a table file - # ------------------------------------------------------------ - def writetab(self,tabfile,outputdir=""): - if isinstance(tabfile,types.ModuleType): - return - basetabfilename = tabfile.split(".")[-1] - filename = os.path.join(outputdir,basetabfilename)+".py" - tf = open(filename,"w") - tf.write("# %s.py. This file automatically created by PLY (version %s). Don't edit!\n" % (tabfile,__version__)) - tf.write("_tabversion = %s\n" % repr(__version__)) - tf.write("_lextokens = %s\n" % repr(self.lextokens)) - tf.write("_lexreflags = %s\n" % repr(self.lexreflags)) - tf.write("_lexliterals = %s\n" % repr(self.lexliterals)) - tf.write("_lexstateinfo = %s\n" % repr(self.lexstateinfo)) - - tabre = { } - # Collect all functions in the initial state - initial = self.lexstatere["INITIAL"] - initialfuncs = [] - for part in initial: - for f in part[1]: - if f and f[0]: - initialfuncs.append(f) - - for key, lre in self.lexstatere.items(): - titem = [] - for i in range(len(lre)): - titem.append((self.lexstateretext[key][i],_funcs_to_names(lre[i][1],self.lexstaterenames[key][i]))) - tabre[key] = titem - - tf.write("_lexstatere = %s\n" % repr(tabre)) - tf.write("_lexstateignore = %s\n" % repr(self.lexstateignore)) - - taberr = { } - for key, ef in self.lexstateerrorf.items(): - if ef: - taberr[key] = ef.__name__ - else: - taberr[key] = None - tf.write("_lexstateerrorf = %s\n" % repr(taberr)) - tf.close() - - # ------------------------------------------------------------ - # readtab() - Read lexer information from a tab file - # ------------------------------------------------------------ - def readtab(self,tabfile,fdict): - if isinstance(tabfile,types.ModuleType): - lextab = tabfile - else: - if sys.version_info[0] < 3: - exec("import %s as lextab" % tabfile) - else: - env = { } - exec("import %s as lextab" % tabfile, env,env) - lextab = env['lextab'] - - if getattr(lextab,"_tabversion","0.0") != __version__: - raise ImportError("Inconsistent PLY version") - - self.lextokens = lextab._lextokens - self.lexreflags = lextab._lexreflags - self.lexliterals = lextab._lexliterals - self.lexstateinfo = lextab._lexstateinfo - self.lexstateignore = lextab._lexstateignore - self.lexstatere = { } - self.lexstateretext = { } - for key,lre in lextab._lexstatere.items(): - titem = [] - txtitem = [] - for i in range(len(lre)): - titem.append((re.compile(lre[i][0],lextab._lexreflags | re.VERBOSE),_names_to_funcs(lre[i][1],fdict))) - txtitem.append(lre[i][0]) - self.lexstatere[key] = titem - self.lexstateretext[key] = txtitem - self.lexstateerrorf = { } - for key,ef in lextab._lexstateerrorf.items(): - self.lexstateerrorf[key] = fdict[ef] - self.begin('INITIAL') - - # ------------------------------------------------------------ # input() - Push a new string into the lexer # ------------------------------------------------------------ - def input(self,s): - # Pull off the first character to see if s looks like a string - c = s[:1] - if not isinstance(c,StringTypes): - raise ValueError("Expected a string") + def input(self, s): self.lexdata = s self.lexpos = 0 self.lexlen = len(s) @@ -260,19 +157,20 @@ class Lexer: # ------------------------------------------------------------ # begin() - Changes the lexing state # ------------------------------------------------------------ - def begin(self,state): - if not state in self.lexstatere: - raise ValueError("Undefined state") + def begin(self, state): + if state not in self.lexstatere: + raise ValueError(f'Undefined state {state!r}') self.lexre = self.lexstatere[state] self.lexretext = self.lexstateretext[state] - self.lexignore = self.lexstateignore.get(state,"") - self.lexerrorf = self.lexstateerrorf.get(state,None) + self.lexignore = self.lexstateignore.get(state, '') + self.lexerrorf = self.lexstateerrorf.get(state, None) + self.lexeoff = self.lexstateeoff.get(state, None) self.lexstate = state # ------------------------------------------------------------ # push_state() - Changes the lexing state and saves old on stack # ------------------------------------------------------------ - def push_state(self,state): + def push_state(self, state): self.lexstatestack.append(self.lexstate) self.begin(state) @@ -291,11 +189,11 @@ class Lexer: # ------------------------------------------------------------ # skip() - Skip ahead n characters # ------------------------------------------------------------ - def skip(self,n): + def skip(self, n): self.lexpos += n # ------------------------------------------------------------ - # opttoken() - Return the next token from the Lexer + # token() - Return the next token from the Lexer # # Note: This function has been carefully implemented to be as fast # as possible. Don't make changes unless you really know what @@ -315,9 +213,10 @@ class Lexer: continue # Look for a regular expression match - for lexre,lexindexfunc in self.lexre: - m = lexre.match(lexdata,lexpos) - if not m: continue + for lexre, lexindexfunc in self.lexre: + m = lexre.match(lexdata, lexpos) + if not m: + continue # Create a token for return tok = LexToken() @@ -326,16 +225,16 @@ class Lexer: tok.lexpos = lexpos i = m.lastindex - func,tok.type = lexindexfunc[i] + func, tok.type = lexindexfunc[i] if not func: - # If no token type was set, it's an ignored token - if tok.type: - self.lexpos = m.end() - return tok - else: - lexpos = m.end() - break + # If no token type was set, it's an ignored token + if tok.type: + self.lexpos = m.end() + return tok + else: + lexpos = m.end() + break lexpos = m.end() @@ -344,22 +243,15 @@ class Lexer: tok.lexer = self # Set additional attributes useful in token rules self.lexmatch = m self.lexpos = lexpos - newtok = func(tok) + del tok.lexer + del self.lexmatch # Every function must return a token, if nothing, we just move to next token if not newtok: lexpos = self.lexpos # This is here in case user has updated lexpos. lexignore = self.lexignore # This is here in case there was a state change break - - # Verify type of the token. If not in the token map, raise an error - if not self.lexoptimize: - if not newtok.type in self.lextokens: - raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % ( - func_code(func).co_filename, func_code(func).co_firstlineno, - func.__name__, newtok.type),lexdata[lexpos:]) - return newtok else: # No match, see if in literals @@ -377,38 +269,50 @@ class Lexer: tok = LexToken() tok.value = self.lexdata[lexpos:] tok.lineno = self.lineno - tok.type = "error" + tok.type = 'error' tok.lexer = self tok.lexpos = lexpos self.lexpos = lexpos newtok = self.lexerrorf(tok) if lexpos == self.lexpos: # Error method didn't change text position at all. This is an error. - raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:]) + raise LexError(f"Scanning error. Illegal character {lexdata[lexpos]!r}", + lexdata[lexpos:]) lexpos = self.lexpos - if not newtok: continue + if not newtok: + continue return newtok self.lexpos = lexpos - raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:]) + raise LexError(f"Illegal character {lexdata[lexpos]!r} at index {lexpos}", + lexdata[lexpos:]) + + if self.lexeoff: + tok = LexToken() + tok.type = 'eof' + tok.value = '' + tok.lineno = self.lineno + tok.lexpos = lexpos + tok.lexer = self + self.lexpos = lexpos + newtok = self.lexeoff(tok) + return newtok self.lexpos = lexpos + 1 if self.lexdata is None: - raise RuntimeError("No input string given with input()") + raise RuntimeError('No input string given with input()') return None # Iterator interface def __iter__(self): return self - def next(self): + def __next__(self): t = self.token() if t is None: raise StopIteration return t - __next__ = next - # ----------------------------------------------------------------------------- # ==== Lex Builder === # @@ -417,59 +321,24 @@ class Lexer: # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- +# _get_regex(func) +# +# Returns the regular expression assigned to a function either as a doc string +# or as a .regex attribute attached by the @TOKEN decorator. +# ----------------------------------------------------------------------------- +def _get_regex(func): + return getattr(func, 'regex', func.__doc__) + +# ----------------------------------------------------------------------------- # get_caller_module_dict() # # This function returns a dictionary containing all of the symbols defined within # a caller further down the call stack. This is used to get the environment # associated with the yacc() call if none was provided. # ----------------------------------------------------------------------------- - def get_caller_module_dict(levels): - try: - raise RuntimeError - except RuntimeError: - e,b,t = sys.exc_info() - f = t.tb_frame - while levels > 0: - f = f.f_back - levels -= 1 - ldict = f.f_globals.copy() - if f.f_globals != f.f_locals: - ldict.update(f.f_locals) - - return ldict - -# ----------------------------------------------------------------------------- -# _funcs_to_names() -# -# Given a list of regular expression functions, this converts it to a list -# suitable for output to a table file -# ----------------------------------------------------------------------------- - -def _funcs_to_names(funclist,namelist): - result = [] - for f,name in zip(funclist,namelist): - if f and f[0]: - result.append((name, f[1])) - else: - result.append(f) - return result - -# ----------------------------------------------------------------------------- -# _names_to_funcs() -# -# Given a list of regular expression function names, this converts it back to -# functions. -# ----------------------------------------------------------------------------- - -def _names_to_funcs(namelist,fdict): - result = [] - for n in namelist: - if n and n[0]: - result.append((fdict[n[0]],n[1])) - else: - result.append(n) - return result + f = sys._getframe(levels) + return { **f.f_globals, **f.f_locals } # ----------------------------------------------------------------------------- # _form_master_re() @@ -478,36 +347,35 @@ def _names_to_funcs(namelist,fdict): # form the master regular expression. Given limitations in the Python re # module, it may be necessary to break the master regex into separate expressions. # ----------------------------------------------------------------------------- - -def _form_master_re(relist,reflags,ldict,toknames): - if not relist: return [] - regex = "|".join(relist) +def _form_master_re(relist, reflags, ldict, toknames): + if not relist: + return [], [], [] + regex = '|'.join(relist) try: - lexre = re.compile(regex,re.VERBOSE | reflags) + lexre = re.compile(regex, reflags) # Build the index to function map for the matching engine - lexindexfunc = [ None ] * (max(lexre.groupindex.values())+1) + lexindexfunc = [None] * (max(lexre.groupindex.values()) + 1) lexindexnames = lexindexfunc[:] - for f,i in lexre.groupindex.items(): - handle = ldict.get(f,None) + for f, i in lexre.groupindex.items(): + handle = ldict.get(f, None) if type(handle) in (types.FunctionType, types.MethodType): - lexindexfunc[i] = (handle,toknames[f]) + lexindexfunc[i] = (handle, toknames[f]) lexindexnames[i] = f elif handle is not None: lexindexnames[i] = f - if f.find("ignore_") > 0: - lexindexfunc[i] = (None,None) + if f.find('ignore_') > 0: + lexindexfunc[i] = (None, None) else: lexindexfunc[i] = (None, toknames[f]) - - return [(lexre,lexindexfunc)],[regex],[lexindexnames] + + return [(lexre, lexindexfunc)], [regex], [lexindexnames] except Exception: - m = int(len(relist)/2) - if m == 0: m = 1 - llist, lre, lnames = _form_master_re(relist[:m],reflags,ldict,toknames) - rlist, rre, rnames = _form_master_re(relist[m:],reflags,ldict,toknames) - return llist+rlist, lre+rre, lnames+rnames + m = (len(relist) // 2) + 1 + llist, lre, lnames = _form_master_re(relist[:m], reflags, ldict, toknames) + rlist, rre, rnames = _form_master_re(relist[m:], reflags, ldict, toknames) + return (llist+rlist), (lre+rre), (lnames+rnames) # ----------------------------------------------------------------------------- # def _statetoken(s,names) @@ -517,22 +385,22 @@ def _form_master_re(relist,reflags,ldict,toknames): # is a tuple of state names and tokenname is the name of the token. For example, # calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM') # ----------------------------------------------------------------------------- +def _statetoken(s, names): + parts = s.split('_') + for i, part in enumerate(parts[1:], 1): + if part not in names and part != 'ANY': + break -def _statetoken(s,names): - nonstate = 1 - parts = s.split("_") - for i in range(1,len(parts)): - if not parts[i] in names and parts[i] != 'ANY': break if i > 1: - states = tuple(parts[1:i]) + states = tuple(parts[1:i]) else: - states = ('INITIAL',) + states = ('INITIAL',) if 'ANY' in states: - states = tuple(names) + states = tuple(names) - tokenname = "_".join(parts[i:]) - return (states,tokenname) + tokenname = '_'.join(parts[i:]) + return (states, tokenname) # ----------------------------------------------------------------------------- @@ -542,19 +410,15 @@ def _statetoken(s,names): # user's input file. # ----------------------------------------------------------------------------- class LexerReflect(object): - def __init__(self,ldict,log=None,reflags=0): + def __init__(self, ldict, log=None, reflags=0): self.ldict = ldict self.error_func = None self.tokens = [] self.reflags = reflags - self.stateinfo = { 'INITIAL' : 'inclusive'} - self.files = {} - self.error = 0 - - if log is None: - self.log = PlyLogger(sys.stderr) - else: - self.log = log + self.stateinfo = {'INITIAL': 'inclusive'} + self.modules = set() + self.error = False + self.log = PlyLogger(sys.stderr) if log is None else log # Get all of the basic information def get_all(self): @@ -562,7 +426,7 @@ class LexerReflect(object): self.get_literals() self.get_states() self.get_rules() - + # Validate all of the information def validate_all(self): self.validate_tokens() @@ -572,20 +436,20 @@ class LexerReflect(object): # Get the tokens map def get_tokens(self): - tokens = self.ldict.get("tokens",None) + tokens = self.ldict.get('tokens', None) if not tokens: - self.log.error("No token list is defined") - self.error = 1 + self.log.error('No token list is defined') + self.error = True return - if not isinstance(tokens,(list, tuple)): - self.log.error("tokens must be a list or tuple") - self.error = 1 + if not isinstance(tokens, (list, tuple)): + self.log.error('tokens must be a list or tuple') + self.error = True return - + if not tokens: - self.log.error("tokens is empty") - self.error = 1 + self.log.error('tokens is empty') + self.error = True return self.tokens = tokens @@ -595,280 +459,270 @@ class LexerReflect(object): terminals = {} for n in self.tokens: if not _is_identifier.match(n): - self.log.error("Bad token name '%s'",n) - self.error = 1 + self.log.error(f"Bad token name {n!r}") + self.error = True if n in terminals: - self.log.warning("Token '%s' multiply defined", n) + self.log.warning(f"Token {n!r} multiply defined") terminals[n] = 1 # Get the literals specifier def get_literals(self): - self.literals = self.ldict.get("literals","") + self.literals = self.ldict.get('literals', '') + if not self.literals: + self.literals = '' # Validate literals def validate_literals(self): try: for c in self.literals: - if not isinstance(c,StringTypes) or len(c) > 1: - self.log.error("Invalid literal %s. Must be a single character", repr(c)) - self.error = 1 - continue + if not isinstance(c, StringTypes) or len(c) > 1: + self.log.error(f'Invalid literal {c!r}. Must be a single character') + self.error = True except TypeError: - self.log.error("Invalid literals specification. literals must be a sequence of characters") - self.error = 1 + self.log.error('Invalid literals specification. literals must be a sequence of characters') + self.error = True def get_states(self): - self.states = self.ldict.get("states",None) + self.states = self.ldict.get('states', None) # Build statemap if self.states: - if not isinstance(self.states,(tuple,list)): - self.log.error("states must be defined as a tuple or list") - self.error = 1 - else: - for s in self.states: - if not isinstance(s,tuple) or len(s) != 2: - self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')",repr(s)) - self.error = 1 - continue - name, statetype = s - if not isinstance(name,StringTypes): - self.log.error("State name %s must be a string", repr(name)) - self.error = 1 - continue - if not (statetype == 'inclusive' or statetype == 'exclusive'): - self.log.error("State type for state %s must be 'inclusive' or 'exclusive'",name) - self.error = 1 - continue - if name in self.stateinfo: - self.log.error("State '%s' already defined",name) - self.error = 1 - continue - self.stateinfo[name] = statetype + if not isinstance(self.states, (tuple, list)): + self.log.error('states must be defined as a tuple or list') + self.error = True + else: + for s in self.states: + if not isinstance(s, tuple) or len(s) != 2: + self.log.error("Invalid state specifier %r. Must be a tuple (statename,'exclusive|inclusive')", s) + self.error = True + continue + name, statetype = s + if not isinstance(name, StringTypes): + self.log.error('State name %r must be a string', name) + self.error = True + continue + if not (statetype == 'inclusive' or statetype == 'exclusive'): + self.log.error("State type for state %r must be 'inclusive' or 'exclusive'", name) + self.error = True + continue + if name in self.stateinfo: + self.log.error("State %r already defined", name) + self.error = True + continue + self.stateinfo[name] = statetype # Get all of the symbols with a t_ prefix and sort them into various # categories (functions, strings, error functions, and ignore characters) def get_rules(self): - tsymbols = [f for f in self.ldict if f[:2] == 't_' ] + tsymbols = [f for f in self.ldict if f[:2] == 't_'] # Now build up a list of functions and a list of strings - - self.toknames = { } # Mapping of symbols to token names - self.funcsym = { } # Symbols defined as functions - self.strsym = { } # Symbols defined as strings - self.ignore = { } # Ignore strings by state - self.errorf = { } # Error functions by state + self.toknames = {} # Mapping of symbols to token names + self.funcsym = {} # Symbols defined as functions + self.strsym = {} # Symbols defined as strings + self.ignore = {} # Ignore strings by state + self.errorf = {} # Error functions by state + self.eoff = {} # EOF functions by state for s in self.stateinfo: - self.funcsym[s] = [] - self.strsym[s] = [] + self.funcsym[s] = [] + self.strsym[s] = [] if len(tsymbols) == 0: - self.log.error("No rules of the form t_rulename are defined") - self.error = 1 + self.log.error('No rules of the form t_rulename are defined') + self.error = True return for f in tsymbols: t = self.ldict[f] - states, tokname = _statetoken(f,self.stateinfo) + states, tokname = _statetoken(f, self.stateinfo) self.toknames[f] = tokname - if hasattr(t,"__call__"): + if hasattr(t, '__call__'): if tokname == 'error': for s in states: self.errorf[s] = t + elif tokname == 'eof': + for s in states: + self.eoff[s] = t elif tokname == 'ignore': - line = func_code(t).co_firstlineno - file = func_code(t).co_filename - self.log.error("%s:%d: Rule '%s' must be defined as a string",file,line,t.__name__) - self.error = 1 + line = t.__code__.co_firstlineno + file = t.__code__.co_filename + self.log.error("%s:%d: Rule %r must be defined as a string", file, line, t.__name__) + self.error = True else: - for s in states: - self.funcsym[s].append((f,t)) + for s in states: + self.funcsym[s].append((f, t)) elif isinstance(t, StringTypes): if tokname == 'ignore': for s in states: self.ignore[s] = t - if "\\" in t: - self.log.warning("%s contains a literal backslash '\\'",f) + if '\\' in t: + self.log.warning("%s contains a literal backslash '\\'", f) elif tokname == 'error': - self.log.error("Rule '%s' must be defined as a function", f) - self.error = 1 + self.log.error("Rule %r must be defined as a function", f) + self.error = True else: - for s in states: - self.strsym[s].append((f,t)) + for s in states: + self.strsym[s].append((f, t)) else: - self.log.error("%s not defined as a function or string", f) - self.error = 1 + self.log.error('%s not defined as a function or string', f) + self.error = True # Sort the functions by line number for f in self.funcsym.values(): - if sys.version_info[0] < 3: - f.sort(lambda x,y: cmp(func_code(x[1]).co_firstlineno,func_code(y[1]).co_firstlineno)) - else: - # Python 3.0 - f.sort(key=lambda x: func_code(x[1]).co_firstlineno) + f.sort(key=lambda x: x[1].__code__.co_firstlineno) # Sort the strings by regular expression length for s in self.strsym.values(): - if sys.version_info[0] < 3: - s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1]))) - else: - # Python 3.0 - s.sort(key=lambda x: len(x[1]),reverse=True) + s.sort(key=lambda x: len(x[1]), reverse=True) - # Validate all of the t_rules collected + # Validate all of the t_rules collected def validate_rules(self): for state in self.stateinfo: # Validate all rules defined by functions - - for fname, f in self.funcsym[state]: - line = func_code(f).co_firstlineno - file = func_code(f).co_filename - self.files[file] = 1 + line = f.__code__.co_firstlineno + file = f.__code__.co_filename + module = inspect.getmodule(f) + self.modules.add(module) tokname = self.toknames[fname] if isinstance(f, types.MethodType): reqargs = 2 else: reqargs = 1 - nargs = func_code(f).co_argcount + nargs = f.__code__.co_argcount if nargs > reqargs: - self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__) - self.error = 1 + self.log.error("%s:%d: Rule %r has too many arguments", file, line, f.__name__) + self.error = True continue if nargs < reqargs: - self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__) - self.error = 1 + self.log.error("%s:%d: Rule %r requires an argument", file, line, f.__name__) + self.error = True continue - if not f.__doc__: - self.log.error("%s:%d: No regular expression defined for rule '%s'",file,line,f.__name__) - self.error = 1 + if not _get_regex(f): + self.log.error("%s:%d: No regular expression defined for rule %r", file, line, f.__name__) + self.error = True continue try: - c = re.compile("(?P<%s>%s)" % (fname,f.__doc__), re.VERBOSE | self.reflags) - if c.match(""): - self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file,line,f.__name__) - self.error = 1 - except re.error: - _etype, e, _etrace = sys.exc_info() - self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file,line,f.__name__,e) - if '#' in f.__doc__: - self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'",file,line, f.__name__) - self.error = 1 + c = re.compile('(?P<%s>%s)' % (fname, _get_regex(f)), self.reflags) + if c.match(''): + self.log.error("%s:%d: Regular expression for rule %r matches empty string", file, line, f.__name__) + self.error = True + except re.error as e: + self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file, line, f.__name__, e) + if '#' in _get_regex(f): + self.log.error("%s:%d. Make sure '#' in rule %r is escaped with '\\#'", file, line, f.__name__) + self.error = True # Validate all rules defined by strings - for name,r in self.strsym[state]: + for name, r in self.strsym[state]: tokname = self.toknames[name] if tokname == 'error': - self.log.error("Rule '%s' must be defined as a function", name) - self.error = 1 + self.log.error("Rule %r must be defined as a function", name) + self.error = True continue - if not tokname in self.tokens and tokname.find("ignore_") < 0: - self.log.error("Rule '%s' defined for an unspecified token %s",name,tokname) - self.error = 1 + if tokname not in self.tokens and tokname.find('ignore_') < 0: + self.log.error("Rule %r defined for an unspecified token %s", name, tokname) + self.error = True continue try: - c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | self.reflags) - if (c.match("")): - self.log.error("Regular expression for rule '%s' matches empty string",name) - self.error = 1 - except re.error: - _etype, e, _etrace = sys.exc_info() - self.log.error("Invalid regular expression for rule '%s'. %s",name,e) + c = re.compile('(?P<%s>%s)' % (name, r), self.reflags) + if (c.match('')): + self.log.error("Regular expression for rule %r matches empty string", name) + self.error = True + except re.error as e: + self.log.error("Invalid regular expression for rule %r. %s", name, e) if '#' in r: - self.log.error("Make sure '#' in rule '%s' is escaped with '\\#'",name) - self.error = 1 + self.log.error("Make sure '#' in rule %r is escaped with '\\#'", name) + self.error = True if not self.funcsym[state] and not self.strsym[state]: - self.log.error("No rules defined for state '%s'",state) - self.error = 1 + self.log.error("No rules defined for state %r", state) + self.error = True # Validate the error function - efunc = self.errorf.get(state,None) + efunc = self.errorf.get(state, None) if efunc: f = efunc - line = func_code(f).co_firstlineno - file = func_code(f).co_filename - self.files[file] = 1 + line = f.__code__.co_firstlineno + file = f.__code__.co_filename + module = inspect.getmodule(f) + self.modules.add(module) if isinstance(f, types.MethodType): reqargs = 2 else: reqargs = 1 - nargs = func_code(f).co_argcount + nargs = f.__code__.co_argcount if nargs > reqargs: - self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__) - self.error = 1 + self.log.error("%s:%d: Rule %r has too many arguments", file, line, f.__name__) + self.error = True if nargs < reqargs: - self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__) - self.error = 1 - - for f in self.files: - self.validate_file(f) + self.log.error("%s:%d: Rule %r requires an argument", file, line, f.__name__) + self.error = True + for module in self.modules: + self.validate_module(module) # ----------------------------------------------------------------------------- - # validate_file() + # validate_module() # # This checks to see if there are duplicated t_rulename() functions or strings # in the parser input file. This is done using a simple regular expression - # match on each line in the given file. + # match on each line in the source code of the given module. # ----------------------------------------------------------------------------- - def validate_file(self,filename): - import os.path - base,ext = os.path.splitext(filename) - if ext != '.py': return # No idea what the file is. Return OK - + def validate_module(self, module): try: - f = open(filename) - lines = f.readlines() - f.close() + lines, linen = inspect.getsourcelines(module) except IOError: - return # Couldn't find the file. Don't worry about it + return fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') - counthash = { } - linen = 1 - for l in lines: - m = fre.match(l) + counthash = {} + linen += 1 + for line in lines: + m = fre.match(line) if not m: - m = sre.match(l) + m = sre.match(line) if m: name = m.group(1) prev = counthash.get(name) if not prev: counthash[name] = linen else: - self.log.error("%s:%d: Rule %s redefined. Previously defined on line %d",filename,linen,name,prev) - self.error = 1 + filename = inspect.getsourcefile(module) + self.log.error('%s:%d: Rule %s redefined. Previously defined on line %d', filename, linen, name, prev) + self.error = True linen += 1 - + # ----------------------------------------------------------------------------- # lex(module) # # Build all of the regular expression rules from definitions in the supplied module # ----------------------------------------------------------------------------- -def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0,outputdir="", debuglog=None, errorlog=None): +def lex(*, module=None, object=None, debug=False, + reflags=int(re.VERBOSE), debuglog=None, errorlog=None): + global lexer + ldict = None - stateinfo = { 'INITIAL' : 'inclusive'} + stateinfo = {'INITIAL': 'inclusive'} lexobj = Lexer() - lexobj.lexoptimize = optimize - global token,input + global token, input if errorlog is None: errorlog = PlyLogger(sys.stderr) @@ -878,131 +732,124 @@ def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,now debuglog = PlyLogger(sys.stderr) # Get the module dictionary used for the lexer - if object: module = object + if object: + module = object + # Get the module dictionary used for the parser if module: - _items = [(k,getattr(module,k)) for k in dir(module)] + _items = [(k, getattr(module, k)) for k in dir(module)] ldict = dict(_items) + # If no __file__ attribute is available, try to obtain it from the __module__ instead + if '__file__' not in ldict: + ldict['__file__'] = sys.modules[ldict['__module__']].__file__ else: ldict = get_caller_module_dict(2) # Collect parser information from the dictionary - linfo = LexerReflect(ldict,log=errorlog,reflags=reflags) + linfo = LexerReflect(ldict, log=errorlog, reflags=reflags) linfo.get_all() - if not optimize: - if linfo.validate_all(): - raise SyntaxError("Can't build lexer") - - if optimize and lextab: - try: - lexobj.readtab(lextab,ldict) - token = lexobj.token - input = lexobj.input - lexer = lexobj - return lexobj - - except ImportError: - pass + if linfo.validate_all(): + raise SyntaxError("Can't build lexer") # Dump some basic debugging information if debug: - debuglog.info("lex: tokens = %r", linfo.tokens) - debuglog.info("lex: literals = %r", linfo.literals) - debuglog.info("lex: states = %r", linfo.stateinfo) + debuglog.info('lex: tokens = %r', linfo.tokens) + debuglog.info('lex: literals = %r', linfo.literals) + debuglog.info('lex: states = %r', linfo.stateinfo) # Build a dictionary of valid token names - lexobj.lextokens = { } + lexobj.lextokens = set() for n in linfo.tokens: - lexobj.lextokens[n] = 1 + lexobj.lextokens.add(n) # Get literals specification - if isinstance(linfo.literals,(list,tuple)): + if isinstance(linfo.literals, (list, tuple)): lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals) else: lexobj.lexliterals = linfo.literals + lexobj.lextokens_all = lexobj.lextokens | set(lexobj.lexliterals) + # Get the stateinfo dictionary stateinfo = linfo.stateinfo - regexs = { } + regexs = {} # Build the master regular expressions for state in stateinfo: regex_list = [] # Add rules defined by functions first for fname, f in linfo.funcsym[state]: - line = func_code(f).co_firstlineno - file = func_code(f).co_filename - regex_list.append("(?P<%s>%s)" % (fname,f.__doc__)) + regex_list.append('(?P<%s>%s)' % (fname, _get_regex(f))) if debug: - debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",fname,f.__doc__, state) + debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", fname, _get_regex(f), state) # Now add all of the simple rules - for name,r in linfo.strsym[state]: - regex_list.append("(?P<%s>%s)" % (name,r)) + for name, r in linfo.strsym[state]: + regex_list.append('(?P<%s>%s)' % (name, r)) if debug: - debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",name,r, state) + debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", name, r, state) regexs[state] = regex_list # Build the master regular expressions if debug: - debuglog.info("lex: ==== MASTER REGEXS FOLLOW ====") + debuglog.info('lex: ==== MASTER REGEXS FOLLOW ====') for state in regexs: - lexre, re_text, re_names = _form_master_re(regexs[state],reflags,ldict,linfo.toknames) + lexre, re_text, re_names = _form_master_re(regexs[state], reflags, ldict, linfo.toknames) lexobj.lexstatere[state] = lexre lexobj.lexstateretext[state] = re_text lexobj.lexstaterenames[state] = re_names if debug: - for i in range(len(re_text)): - debuglog.info("lex: state '%s' : regex[%d] = '%s'",state, i, re_text[i]) + for i, text in enumerate(re_text): + debuglog.info("lex: state '%s' : regex[%d] = '%s'", state, i, text) # For inclusive states, we need to add the regular expressions from the INITIAL state - for state,stype in stateinfo.items(): - if state != "INITIAL" and stype == 'inclusive': - lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) - lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) - lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL']) + for state, stype in stateinfo.items(): + if state != 'INITIAL' and stype == 'inclusive': + lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) + lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) + lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL']) lexobj.lexstateinfo = stateinfo - lexobj.lexre = lexobj.lexstatere["INITIAL"] - lexobj.lexretext = lexobj.lexstateretext["INITIAL"] + lexobj.lexre = lexobj.lexstatere['INITIAL'] + lexobj.lexretext = lexobj.lexstateretext['INITIAL'] lexobj.lexreflags = reflags # Set up ignore variables lexobj.lexstateignore = linfo.ignore - lexobj.lexignore = lexobj.lexstateignore.get("INITIAL","") + lexobj.lexignore = lexobj.lexstateignore.get('INITIAL', '') # Set up error functions lexobj.lexstateerrorf = linfo.errorf - lexobj.lexerrorf = linfo.errorf.get("INITIAL",None) + lexobj.lexerrorf = linfo.errorf.get('INITIAL', None) if not lexobj.lexerrorf: - errorlog.warning("No t_error rule is defined") + errorlog.warning('No t_error rule is defined') + + # Set up eof functions + lexobj.lexstateeoff = linfo.eoff + lexobj.lexeoff = linfo.eoff.get('INITIAL', None) # Check state information for ignore and error rules - for s,stype in stateinfo.items(): + for s, stype in stateinfo.items(): if stype == 'exclusive': - if not s in linfo.errorf: - errorlog.warning("No error rule is defined for exclusive state '%s'", s) - if not s in linfo.ignore and lexobj.lexignore: - errorlog.warning("No ignore rule is defined for exclusive state '%s'", s) + if s not in linfo.errorf: + errorlog.warning("No error rule is defined for exclusive state %r", s) + if s not in linfo.ignore and lexobj.lexignore: + errorlog.warning("No ignore rule is defined for exclusive state %r", s) elif stype == 'inclusive': - if not s in linfo.errorf: - linfo.errorf[s] = linfo.errorf.get("INITIAL",None) - if not s in linfo.ignore: - linfo.ignore[s] = linfo.ignore.get("INITIAL","") + if s not in linfo.errorf: + linfo.errorf[s] = linfo.errorf.get('INITIAL', None) + if s not in linfo.ignore: + linfo.ignore[s] = linfo.ignore.get('INITIAL', '') # Create global versions of the token() and input() functions token = lexobj.token input = lexobj.input lexer = lexobj - # If in optimize mode, we write the lextab - if lextab and optimize: - lexobj.writetab(lextab,outputdir) - return lexobj # ----------------------------------------------------------------------------- @@ -1011,15 +858,14 @@ def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,now # This runs the lexer as a main program # ----------------------------------------------------------------------------- -def runmain(lexer=None,data=None): +def runmain(lexer=None, data=None): if not data: try: filename = sys.argv[1] - f = open(filename) - data = f.read() - f.close() + with open(filename) as f: + data = f.read() except IndexError: - sys.stdout.write("Reading from standard input (type EOF to end):\n") + sys.stdout.write('Reading from standard input (type EOF to end):\n') data = sys.stdin.read() if lexer: @@ -1032,10 +878,11 @@ def runmain(lexer=None,data=None): else: _token = token - while 1: + while True: tok = _token() - if not tok: break - sys.stdout.write("(%s,%r,%d,%d)\n" % (tok.type, tok.value, tok.lineno,tok.lexpos)) + if not tok: + break + sys.stdout.write(f'({tok.type},{tok.value!r},{tok.lineno},{tok.lexpos})\n') # ----------------------------------------------------------------------------- # @TOKEN(regex) @@ -1045,14 +892,10 @@ def runmain(lexer=None,data=None): # ----------------------------------------------------------------------------- def TOKEN(r): - def set_doc(f): - if hasattr(r,"__call__"): - f.__doc__ = r.__doc__ + def set_regex(f): + if hasattr(r, '__call__'): + f.regex = _get_regex(r) else: - f.__doc__ = r + f.regex = r return f - return set_doc - -# Alternative spelling of the TOKEN decorator -Token = TOKEN - + return set_regex diff --git a/components/script/dom/bindings/codegen/ply/ply/yacc.py b/components/script/dom/bindings/codegen/ply/ply/yacc.py index e9f5c657551..bce63c18241 100644 --- a/components/script/dom/bindings/codegen/ply/ply/yacc.py +++ b/components/script/dom/bindings/codegen/ply/ply/yacc.py @@ -1,22 +1,24 @@ # ----------------------------------------------------------------------------- # ply: yacc.py # -# Copyright (C) 2001-2009, +# Copyright (C) 2001-2020 # David M. Beazley (Dabeaz LLC) # All rights reserved. # +# Latest version: https://github.com/dabeaz/ply +# # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: -# +# # * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * Neither the name of the David Beazley or Dabeaz LLC may be used to +# and/or other materials provided with the distribution. +# * Neither the name of David Beazley or Dabeaz LLC may be used to # endorse or promote products derived from this software without -# specific prior written permission. +# specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT @@ -32,7 +34,7 @@ # ----------------------------------------------------------------------------- # # This implements an LR parser that is constructed from grammar rules defined -# as Python functions. The grammer is specified by supplying the BNF inside +# as Python functions. The grammar is specified by supplying the BNF inside # Python documentation strings. The inspiration for this technique was borrowed # from John Aycock's Spark parsing system. PLY might be viewed as cross between # Spark and the GNU bison utility. @@ -59,8 +61,10 @@ # own risk! # ---------------------------------------------------------------------------- -__version__ = "3.3" -__tabversion__ = "3.2" # Table version +import re +import types +import sys +import inspect #----------------------------------------------------------------------------- # === User configurable parameters === @@ -68,95 +72,69 @@ __tabversion__ = "3.2" # Table version # Change these to modify the default behavior of yacc (if you wish) #----------------------------------------------------------------------------- -yaccdebug = 1 # Debugging mode. If set, yacc generates a - # a 'parser.out' file in the current directory +yaccdebug = False # Debugging mode. If set, yacc generates a +# a 'parser.out' file in the current directory debug_file = 'parser.out' # Default name of the debugging file -tab_module = 'parsetab' # Default name of the table module -default_lr = 'LALR' # Default LR table generation method - error_count = 3 # Number of symbols that must be shifted to leave recovery mode - -yaccdevel = 0 # Set to True if developing yacc. This turns off optimized - # implementations of certain functions. - resultlimit = 40 # Size limit of results when running in debug mode. -pickle_protocol = 0 # Protocol to use when writing pickle files - -import re, types, sys, os.path - -# Compatibility function for python 2.6/3.0 -if sys.version_info[0] < 3: - def func_code(f): - return f.func_code -else: - def func_code(f): - return f.__code__ - -# Compatibility -try: - MAXINT = sys.maxint -except AttributeError: - MAXINT = sys.maxsize +MAXINT = sys.maxsize -# Python 2.x/3.0 compatibility. -def load_ply_lex(): - if sys.version_info[0] < 3: - import lex - else: - import ply.lex as lex - return lex - -# This object is a stand-in for a logging object created by the +# This object is a stand-in for a logging object created by the # logging module. PLY will use this by default to create things # such as the parser.out file. If a user wants more detailed # information, they can create their own logging object and pass # it into PLY. class PlyLogger(object): - def __init__(self,f): + def __init__(self, f): self.f = f - def debug(self,msg,*args,**kwargs): - self.f.write((msg % args) + "\n") - info = debug - def warning(self,msg,*args,**kwargs): - self.f.write("WARNING: "+ (msg % args) + "\n") + def debug(self, msg, *args, **kwargs): + self.f.write((msg % args) + '\n') + + info = debug - def error(self,msg,*args,**kwargs): - self.f.write("ERROR: " + (msg % args) + "\n") + def warning(self, msg, *args, **kwargs): + self.f.write('WARNING: ' + (msg % args) + '\n') + + def error(self, msg, *args, **kwargs): + self.f.write('ERROR: ' + (msg % args) + '\n') critical = debug # Null logger is used when no output is generated. Does nothing. class NullLogger(object): - def __getattribute__(self,name): + def __getattribute__(self, name): return self - def __call__(self,*args,**kwargs): + + def __call__(self, *args, **kwargs): return self - + # Exception raised for yacc-related errors -class YaccError(Exception): pass +class YaccError(Exception): + pass # Format the result message that the parser produces when running in debug mode. def format_result(r): repr_str = repr(r) - if '\n' in repr_str: repr_str = repr(repr_str) + if '\n' in repr_str: + repr_str = repr(repr_str) if len(repr_str) > resultlimit: - repr_str = repr_str[:resultlimit]+" ..." - result = "<%s @ 0x%x> (%s)" % (type(r).__name__,id(r),repr_str) + repr_str = repr_str[:resultlimit] + ' ...' + result = '<%s @ 0x%x> (%s)' % (type(r).__name__, id(r), repr_str) return result - # Format stack entries when the parser is running in debug mode def format_stack_entry(r): repr_str = repr(r) - if '\n' in repr_str: repr_str = repr(repr_str) + if '\n' in repr_str: + repr_str = repr(repr_str) if len(repr_str) < 16: return repr_str else: - return "<%s @ 0x%x>" % (type(r).__name__,id(r)) + return '<%s @ 0x%x>' % (type(r).__name__, id(r)) #----------------------------------------------------------------------------- # === LR Parsing Engine === @@ -176,8 +154,11 @@ def format_stack_entry(r): # .endlexpos = Ending lex position (optional, set automatically) class YaccSymbol: - def __str__(self): return self.type - def __repr__(self): return str(self) + def __str__(self): + return self.type + + def __repr__(self): + return str(self) # This class is a wrapper around the objects actually passed to each # grammar rule. Index lookup and assignment actually assign the @@ -189,46 +170,53 @@ class YaccSymbol: # representing the range of positional information for a symbol. class YaccProduction: - def __init__(self,s,stack=None): + def __init__(self, s, stack=None): self.slice = s self.stack = stack self.lexer = None - self.parser= None - def __getitem__(self,n): - if n >= 0: return self.slice[n].value - else: return self.stack[n].value + self.parser = None + + def __getitem__(self, n): + if isinstance(n, slice): + return [s.value for s in self.slice[n]] + elif n >= 0: + return self.slice[n].value + else: + return self.stack[n].value - def __setitem__(self,n,v): + def __setitem__(self, n, v): self.slice[n].value = v - def __getslice__(self,i,j): + def __getslice__(self, i, j): return [s.value for s in self.slice[i:j]] def __len__(self): return len(self.slice) - def lineno(self,n): - return getattr(self.slice[n],"lineno",0) + def lineno(self, n): + return getattr(self.slice[n], 'lineno', 0) - def set_lineno(self,n,lineno): + def set_lineno(self, n, lineno): self.slice[n].lineno = lineno - def linespan(self,n): - startline = getattr(self.slice[n],"lineno",0) - endline = getattr(self.slice[n],"endlineno",startline) - return startline,endline + def linespan(self, n): + startline = getattr(self.slice[n], 'lineno', 0) + endline = getattr(self.slice[n], 'endlineno', startline) + return startline, endline - def lexpos(self,n): - return getattr(self.slice[n],"lexpos",0) + def lexpos(self, n): + return getattr(self.slice[n], 'lexpos', 0) - def lexspan(self,n): - startpos = getattr(self.slice[n],"lexpos",0) - endpos = getattr(self.slice[n],"endlexpos",startpos) - return startpos,endpos + def set_lexpos(self, n, lexpos): + self.slice[n].lexpos = lexpos - def error(self): - raise SyntaxError + def lexspan(self, n): + startpos = getattr(self.slice[n], 'lexpos', 0) + endpos = getattr(self.slice[n], 'endlexpos', startpos) + return startpos, endpos + def error(self): + raise SyntaxError # ----------------------------------------------------------------------------- # == LRParser == @@ -237,14 +225,16 @@ class YaccProduction: # ----------------------------------------------------------------------------- class LRParser: - def __init__(self,lrtab,errorf): + def __init__(self, lrtab, errorf): self.productions = lrtab.lr_productions - self.action = lrtab.lr_action - self.goto = lrtab.lr_goto - self.errorfunc = errorf + self.action = lrtab.lr_action + self.goto = lrtab.lr_goto + self.errorfunc = errorf + self.set_defaulted_states() + self.errorok = True def errok(self): - self.errorok = 1 + self.errorok = True def restart(self): del self.statestack[:] @@ -254,47 +244,52 @@ class LRParser: self.symstack.append(sym) self.statestack.append(0) - def parse(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None): - if debug or yaccdevel: - if isinstance(debug,int): - debug = PlyLogger(sys.stderr) - return self.parsedebug(input,lexer,debug,tracking,tokenfunc) - elif tracking: - return self.parseopt(input,lexer,debug,tracking,tokenfunc) - else: - return self.parseopt_notrack(input,lexer,debug,tracking,tokenfunc) - - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # parsedebug(). + # Defaulted state support. + # This method identifies parser states where there is only one possible reduction action. + # For such states, the parser can make a choose to make a rule reduction without consuming + # the next look-ahead token. This delayed invocation of the tokenizer can be useful in + # certain kinds of advanced parsing situations where the lexer and parser interact with + # each other or change states (i.e., manipulation of scope, lexer states, etc.). # - # This is the debugging enabled version of parse(). All changes made to the - # parsing engine should be made here. For the non-debugging version, - # copy this code to a method parseopt() and delete all of the sections - # enclosed in: + # See: http://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html#Default-Reductions + def set_defaulted_states(self): + self.defaulted_states = {} + for state, actions in self.action.items(): + rules = list(actions.values()) + if len(rules) == 1 and rules[0] < 0: + self.defaulted_states[state] = rules[0] + + def disable_defaulted_states(self): + self.defaulted_states = {} + + # parse(). # - # #--! DEBUG - # statements - # #--! DEBUG - # - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - def parsedebug(self,input=None,lexer=None,debug=None,tracking=0,tokenfunc=None): - lookahead = None # Current lookahead symbol - lookaheadstack = [ ] # Stack of lookahead symbols - actions = self.action # Local reference to action table (to avoid lookup on self.) - goto = self.goto # Local reference to goto table (to avoid lookup on self.) - prod = self.productions # Local reference to production list (to avoid lookup on self.) - pslice = YaccProduction(None) # Production object passed to grammar rules - errorcount = 0 # Used during error recovery + # This is the core parsing engine. To operate, it requires a lexer object. + # Two options are provided. The debug flag turns on debugging so that you can + # see the various rule reductions and parsing steps. tracking turns on position + # tracking. In this mode, symbols will record the starting/ending line number and + # character index. + + def parse(self, input=None, lexer=None, debug=False, tracking=False): + # If debugging has been specified as a flag, turn it into a logging object + if isinstance(debug, int) and debug: + debug = PlyLogger(sys.stderr) + + lookahead = None # Current lookahead symbol + lookaheadstack = [] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + defaulted_states = self.defaulted_states # Local reference to defaulted states + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery - # --! DEBUG - debug.info("PLY: PARSE DEBUG START") - # --! DEBUG + if debug: + debug.info('PLY: PARSE DEBUG START') # If no lexer was given, we will try to use the lex module if not lexer: - lex = load_ply_lex() + from . import lex lexer = lex.lexer # Set up the lexer and parser objects on pslice @@ -305,72 +300,67 @@ class LRParser: if input is not None: lexer.input(input) - if tokenfunc is None: - # Tokenize function - get_token = lexer.token - else: - get_token = tokenfunc + # Set the token function + get_token = self.token = lexer.token # Set up the state and symbol stacks - - statestack = [ ] # Stack of parsing states - self.statestack = statestack - symstack = [ ] # Stack of grammar symbols - self.symstack = symstack - - pslice.stack = symstack # Put in the production - errtoken = None # Err token + statestack = self.statestack = [] # Stack of parsing states + symstack = self.symstack = [] # Stack of grammar symbols + pslice.stack = symstack # Put in the production + errtoken = None # Err token # The start state is assumed to be (0,$end) statestack.append(0) sym = YaccSymbol() - sym.type = "$end" + sym.type = '$end' symstack.append(sym) state = 0 - while 1: + while True: # Get the next symbol on the input. If a lookahead symbol # is already set, we just use that. Otherwise, we'll pull # the next token off of the lookaheadstack or from the lexer - # --! DEBUG - debug.debug('') - debug.debug('State : %s', state) - # --! DEBUG + if debug: + debug.debug('State : %s', state) - if not lookahead: - if not lookaheadstack: - lookahead = get_token() # Get the next token - else: - lookahead = lookaheadstack.pop() + if state not in defaulted_states: if not lookahead: - lookahead = YaccSymbol() - lookahead.type = "$end" - - # --! DEBUG - debug.debug('Stack : %s', - ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) - # --! DEBUG + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + else: + t = defaulted_states[state] + if debug: + debug.debug('Defaulted state %s: Reduce using %d', state, -t) - # Check the action table - ltype = lookahead.type - t = actions[state].get(ltype) + if debug: + debug.debug('Stack : %s', + ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) if t is not None: if t > 0: # shift a symbol on the stack statestack.append(t) state = t - - # --! DEBUG - debug.debug("Action : Shift and goto state %s", t) - # --! DEBUG + + if debug: + debug.debug('Action : Shift and goto state %s', t) symstack.append(lookahead) lookahead = None # Decrease error count on successful shift - if errorcount: errorcount -=1 + if errorcount: + errorcount -= 1 continue if t < 0: @@ -384,358 +374,69 @@ class LRParser: sym.type = pname # Production name sym.value = None - # --! DEBUG - if plen: - debug.info("Action : Reduce rule [%s] with %s and goto state %d", p.str, "["+",".join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+"]",-t) - else: - debug.info("Action : Reduce rule [%s] with %s and goto state %d", p.str, [],-t) - - # --! DEBUG + if debug: + if plen: + debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, + '['+','.join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+']', + goto[statestack[-1-plen]][pname]) + else: + debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, [], + goto[statestack[-1]][pname]) if plen: targ = symstack[-plen-1:] targ[0] = sym - # --! TRACKING if tracking: - t1 = targ[1] - sym.lineno = t1.lineno - sym.lexpos = t1.lexpos - t1 = targ[-1] - sym.endlineno = getattr(t1,"endlineno",t1.lineno) - sym.endlexpos = getattr(t1,"endlexpos",t1.lexpos) - - # --! TRACKING + t1 = targ[1] + sym.lineno = t1.lineno + sym.lexpos = t1.lexpos + t1 = targ[-1] + sym.endlineno = getattr(t1, 'endlineno', t1.lineno) + sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # The code enclosed in this section is duplicated + # The code enclosed in this section is duplicated # below as a performance optimization. Make sure # changes get made in both locations. pslice.slice = targ - - try: - # Call the grammar rule with our special slice object - del symstack[-plen:] - del statestack[-plen:] - p.callable(pslice) - # --! DEBUG - debug.info("Result : %s", format_result(pslice[0])) - # --! DEBUG - symstack.append(sym) - state = goto[statestack[-1]][pname] - statestack.append(state) - except SyntaxError: - # If an error was set. Enter error recovery state - lookaheadstack.append(lookahead) - symstack.pop() - statestack.pop() - state = statestack[-1] - sym.type = 'error' - lookahead = sym - errorcount = error_count - self.errorok = 0 - continue - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - else: - - # --! TRACKING - if tracking: - sym.lineno = lexer.lineno - sym.lexpos = lexer.lexpos - # --! TRACKING - - targ = [ sym ] - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # The code enclosed in this section is duplicated - # above as a performance optimization. Make sure - # changes get made in both locations. - - pslice.slice = targ try: # Call the grammar rule with our special slice object + del symstack[-plen:] + self.state = state p.callable(pslice) - # --! DEBUG - debug.info("Result : %s", format_result(pslice[0])) - # --! DEBUG + del statestack[-plen:] + if debug: + debug.info('Result : %s', format_result(pslice[0])) symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state - lookaheadstack.append(lookahead) - symstack.pop() - statestack.pop() + lookaheadstack.append(lookahead) # Save the current lookahead token + symstack.extend(targ[1:-1]) # Put the production slice back on the stack + statestack.pop() # Pop back one state (before the reduce) state = statestack[-1] sym.type = 'error' + sym.value = 'error' lookahead = sym errorcount = error_count - self.errorok = 0 - continue - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + self.errorok = False - if t == 0: - n = symstack[-1] - result = getattr(n,"value",None) - # --! DEBUG - debug.info("Done : Returning %s", format_result(result)) - debug.info("PLY: PARSE DEBUG END") - # --! DEBUG - return result - - if t == None: - - # --! DEBUG - debug.error('Error : %s', - ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) - # --! DEBUG - - # We have some kind of parsing error here. To handle - # this, we are going to push the current token onto - # the tokenstack and replace it with an 'error' token. - # If there are any synchronization rules, they may - # catch it. - # - # In addition to pushing the error token, we call call - # the user defined p_error() function if this is the - # first syntax error. This function is only called if - # errorcount == 0. - if errorcount == 0 or self.errorok: - errorcount = error_count - self.errorok = 0 - errtoken = lookahead - if errtoken.type == "$end": - errtoken = None # End of file! - if self.errorfunc: - global errok,token,restart - errok = self.errok # Set some special functions available in error recovery - token = get_token - restart = self.restart - if errtoken and not hasattr(errtoken,'lexer'): - errtoken.lexer = lexer - tok = self.errorfunc(errtoken) - del errok, token, restart # Delete special functions - - if self.errorok: - # User must have done some kind of panic - # mode recovery on their own. The - # returned token is the next lookahead - lookahead = tok - errtoken = None - continue - else: - if errtoken: - if hasattr(errtoken,"lineno"): lineno = lookahead.lineno - else: lineno = 0 - if lineno: - sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) - else: - sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) - else: - sys.stderr.write("yacc: Parse error in input. EOF\n") - return - - else: - errorcount = error_count - - # case 1: the statestack only has 1 entry on it. If we're in this state, the - # entire parse has been rolled back and we're completely hosed. The token is - # discarded and we just keep going. - - if len(statestack) <= 1 and lookahead.type != "$end": - lookahead = None - errtoken = None - state = 0 - # Nuke the pushback stack - del lookaheadstack[:] - continue - - # case 2: the statestack has a couple of entries on it, but we're - # at the end of the file. nuke the top entry and generate an error token - - # Start nuking entries on the stack - if lookahead.type == "$end": - # Whoa. We're really hosed here. Bail out - return - - if lookahead.type != 'error': - sym = symstack[-1] - if sym.type == 'error': - # Hmmm. Error is on top of stack, we'll just nuke input - # symbol and continue - lookahead = None continue - t = YaccSymbol() - t.type = 'error' - if hasattr(lookahead,"lineno"): - t.lineno = lookahead.lineno - t.value = lookahead - lookaheadstack.append(lookahead) - lookahead = t - else: - symstack.pop() - statestack.pop() - state = statestack[-1] # Potential bug fix - - continue - - # Call an error function here - raise RuntimeError("yacc: internal parser error!!!\n") - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # parseopt(). - # - # Optimized version of parse() method. DO NOT EDIT THIS CODE DIRECTLY. - # Edit the debug version above, then copy any modifications to the method - # below while removing #--! DEBUG sections. - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - - def parseopt(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None): - lookahead = None # Current lookahead symbol - lookaheadstack = [ ] # Stack of lookahead symbols - actions = self.action # Local reference to action table (to avoid lookup on self.) - goto = self.goto # Local reference to goto table (to avoid lookup on self.) - prod = self.productions # Local reference to production list (to avoid lookup on self.) - pslice = YaccProduction(None) # Production object passed to grammar rules - errorcount = 0 # Used during error recovery - - # If no lexer was given, we will try to use the lex module - if not lexer: - lex = load_ply_lex() - lexer = lex.lexer - - # Set up the lexer and parser objects on pslice - pslice.lexer = lexer - pslice.parser = self - - # If input was supplied, pass to lexer - if input is not None: - lexer.input(input) - - if tokenfunc is None: - # Tokenize function - get_token = lexer.token - else: - get_token = tokenfunc - - # Set up the state and symbol stacks - - statestack = [ ] # Stack of parsing states - self.statestack = statestack - symstack = [ ] # Stack of grammar symbols - self.symstack = symstack - - pslice.stack = symstack # Put in the production - errtoken = None # Err token - - # The start state is assumed to be (0,$end) - - statestack.append(0) - sym = YaccSymbol() - sym.type = '$end' - symstack.append(sym) - state = 0 - while 1: - # Get the next symbol on the input. If a lookahead symbol - # is already set, we just use that. Otherwise, we'll pull - # the next token off of the lookaheadstack or from the lexer - - if not lookahead: - if not lookaheadstack: - lookahead = get_token() # Get the next token - else: - lookahead = lookaheadstack.pop() - if not lookahead: - lookahead = YaccSymbol() - lookahead.type = '$end' - - # Check the action table - ltype = lookahead.type - t = actions[state].get(ltype) - - if t is not None: - if t > 0: - # shift a symbol on the stack - statestack.append(t) - state = t - - symstack.append(lookahead) - lookahead = None - - # Decrease error count on successful shift - if errorcount: errorcount -=1 - continue - - if t < 0: - # reduce a symbol on the stack, emit a production - p = prod[-t] - pname = p.name - plen = p.len - - # Get production function - sym = YaccSymbol() - sym.type = pname # Production name - sym.value = None - - if plen: - targ = symstack[-plen-1:] - targ[0] = sym - - # --! TRACKING - if tracking: - t1 = targ[1] - sym.lineno = t1.lineno - sym.lexpos = t1.lexpos - t1 = targ[-1] - sym.endlineno = getattr(t1,"endlineno",t1.lineno) - sym.endlexpos = getattr(t1,"endlexpos",t1.lexpos) - - # --! TRACKING - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # The code enclosed in this section is duplicated - # below as a performance optimization. Make sure - # changes get made in both locations. - pslice.slice = targ - - try: - # Call the grammar rule with our special slice object - del symstack[-plen:] - del statestack[-plen:] - p.callable(pslice) - symstack.append(sym) - state = goto[statestack[-1]][pname] - statestack.append(state) - except SyntaxError: - # If an error was set. Enter error recovery state - lookaheadstack.append(lookahead) - symstack.pop() - statestack.pop() - state = statestack[-1] - sym.type = 'error' - lookahead = sym - errorcount = error_count - self.errorok = 0 - continue - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - else: - # --! TRACKING if tracking: - sym.lineno = lexer.lineno - sym.lexpos = lexer.lexpos - # --! TRACKING + sym.lineno = lexer.lineno + sym.lexpos = lexer.lexpos - targ = [ sym ] + targ = [sym] # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # The code enclosed in this section is duplicated + # The code enclosed in this section is duplicated # above as a performance optimization. Make sure # changes get made in both locations. @@ -743,283 +444,41 @@ class LRParser: try: # Call the grammar rule with our special slice object + self.state = state p.callable(pslice) + if debug: + debug.info('Result : %s', format_result(pslice[0])) symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state - lookaheadstack.append(lookahead) - symstack.pop() - statestack.pop() + lookaheadstack.append(lookahead) # Save the current lookahead token + statestack.pop() # Pop back one state (before the reduce) state = statestack[-1] sym.type = 'error' + sym.value = 'error' lookahead = sym errorcount = error_count - self.errorok = 0 + self.errorok = False + continue - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! if t == 0: n = symstack[-1] - return getattr(n,"value",None) - - if t == None: - - # We have some kind of parsing error here. To handle - # this, we are going to push the current token onto - # the tokenstack and replace it with an 'error' token. - # If there are any synchronization rules, they may - # catch it. - # - # In addition to pushing the error token, we call call - # the user defined p_error() function if this is the - # first syntax error. This function is only called if - # errorcount == 0. - if errorcount == 0 or self.errorok: - errorcount = error_count - self.errorok = 0 - errtoken = lookahead - if errtoken.type == '$end': - errtoken = None # End of file! - if self.errorfunc: - global errok,token,restart - errok = self.errok # Set some special functions available in error recovery - token = get_token - restart = self.restart - if errtoken and not hasattr(errtoken,'lexer'): - errtoken.lexer = lexer - tok = self.errorfunc(errtoken) - del errok, token, restart # Delete special functions - - if self.errorok: - # User must have done some kind of panic - # mode recovery on their own. The - # returned token is the next lookahead - lookahead = tok - errtoken = None - continue - else: - if errtoken: - if hasattr(errtoken,"lineno"): lineno = lookahead.lineno - else: lineno = 0 - if lineno: - sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) - else: - sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) - else: - sys.stderr.write("yacc: Parse error in input. EOF\n") - return - - else: - errorcount = error_count - - # case 1: the statestack only has 1 entry on it. If we're in this state, the - # entire parse has been rolled back and we're completely hosed. The token is - # discarded and we just keep going. - - if len(statestack) <= 1 and lookahead.type != '$end': - lookahead = None - errtoken = None - state = 0 - # Nuke the pushback stack - del lookaheadstack[:] - continue + result = getattr(n, 'value', None) - # case 2: the statestack has a couple of entries on it, but we're - # at the end of the file. nuke the top entry and generate an error token - - # Start nuking entries on the stack - if lookahead.type == '$end': - # Whoa. We're really hosed here. Bail out - return - - if lookahead.type != 'error': - sym = symstack[-1] - if sym.type == 'error': - # Hmmm. Error is on top of stack, we'll just nuke input - # symbol and continue - lookahead = None - continue - t = YaccSymbol() - t.type = 'error' - if hasattr(lookahead,"lineno"): - t.lineno = lookahead.lineno - t.value = lookahead - lookaheadstack.append(lookahead) - lookahead = t - else: - symstack.pop() - statestack.pop() - state = statestack[-1] # Potential bug fix - - continue - - # Call an error function here - raise RuntimeError("yacc: internal parser error!!!\n") - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # parseopt_notrack(). - # - # Optimized version of parseopt() with line number tracking removed. - # DO NOT EDIT THIS CODE DIRECTLY. Copy the optimized version and remove - # code in the #--! TRACKING sections - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - def parseopt_notrack(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None): - lookahead = None # Current lookahead symbol - lookaheadstack = [ ] # Stack of lookahead symbols - actions = self.action # Local reference to action table (to avoid lookup on self.) - goto = self.goto # Local reference to goto table (to avoid lookup on self.) - prod = self.productions # Local reference to production list (to avoid lookup on self.) - pslice = YaccProduction(None) # Production object passed to grammar rules - errorcount = 0 # Used during error recovery - - # If no lexer was given, we will try to use the lex module - if not lexer: - lex = load_ply_lex() - lexer = lex.lexer - - # Set up the lexer and parser objects on pslice - pslice.lexer = lexer - pslice.parser = self - - # If input was supplied, pass to lexer - if input is not None: - lexer.input(input) - - if tokenfunc is None: - # Tokenize function - get_token = lexer.token - else: - get_token = tokenfunc - - # Set up the state and symbol stacks - - statestack = [ ] # Stack of parsing states - self.statestack = statestack - symstack = [ ] # Stack of grammar symbols - self.symstack = symstack - - pslice.stack = symstack # Put in the production - errtoken = None # Err token - - # The start state is assumed to be (0,$end) - - statestack.append(0) - sym = YaccSymbol() - sym.type = '$end' - symstack.append(sym) - state = 0 - while 1: - # Get the next symbol on the input. If a lookahead symbol - # is already set, we just use that. Otherwise, we'll pull - # the next token off of the lookaheadstack or from the lexer - - if not lookahead: - if not lookaheadstack: - lookahead = get_token() # Get the next token - else: - lookahead = lookaheadstack.pop() - if not lookahead: - lookahead = YaccSymbol() - lookahead.type = '$end' - - # Check the action table - ltype = lookahead.type - t = actions[state].get(ltype) - - if t is not None: - if t > 0: - # shift a symbol on the stack - statestack.append(t) - state = t - - symstack.append(lookahead) - lookahead = None - - # Decrease error count on successful shift - if errorcount: errorcount -=1 - continue + if debug: + debug.info('Done : Returning %s', format_result(result)) + debug.info('PLY: PARSE DEBUG END') - if t < 0: - # reduce a symbol on the stack, emit a production - p = prod[-t] - pname = p.name - plen = p.len - - # Get production function - sym = YaccSymbol() - sym.type = pname # Production name - sym.value = None - - if plen: - targ = symstack[-plen-1:] - targ[0] = sym - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # The code enclosed in this section is duplicated - # below as a performance optimization. Make sure - # changes get made in both locations. - - pslice.slice = targ - - try: - # Call the grammar rule with our special slice object - del symstack[-plen:] - del statestack[-plen:] - p.callable(pslice) - symstack.append(sym) - state = goto[statestack[-1]][pname] - statestack.append(state) - except SyntaxError: - # If an error was set. Enter error recovery state - lookaheadstack.append(lookahead) - symstack.pop() - statestack.pop() - state = statestack[-1] - sym.type = 'error' - lookahead = sym - errorcount = error_count - self.errorok = 0 - continue - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - else: - - targ = [ sym ] - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # The code enclosed in this section is duplicated - # above as a performance optimization. Make sure - # changes get made in both locations. - - pslice.slice = targ - - try: - # Call the grammar rule with our special slice object - p.callable(pslice) - symstack.append(sym) - state = goto[statestack[-1]][pname] - statestack.append(state) - except SyntaxError: - # If an error was set. Enter error recovery state - lookaheadstack.append(lookahead) - symstack.pop() - statestack.pop() - state = statestack[-1] - sym.type = 'error' - lookahead = sym - errorcount = error_count - self.errorok = 0 - continue - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + return result - if t == 0: - n = symstack[-1] - return getattr(n,"value",None) + if t is None: - if t == None: + if debug: + debug.error('Error : %s', + ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) # We have some kind of parsing error here. To handle # this, we are going to push the current token onto @@ -1033,20 +492,15 @@ class LRParser: # errorcount == 0. if errorcount == 0 or self.errorok: errorcount = error_count - self.errorok = 0 + self.errorok = False errtoken = lookahead if errtoken.type == '$end': errtoken = None # End of file! if self.errorfunc: - global errok,token,restart - errok = self.errok # Set some special functions available in error recovery - token = get_token - restart = self.restart - if errtoken and not hasattr(errtoken,'lexer'): + if errtoken and not hasattr(errtoken, 'lexer'): errtoken.lexer = lexer + self.state = state tok = self.errorfunc(errtoken) - del errok, token, restart # Delete special functions - if self.errorok: # User must have done some kind of panic # mode recovery on their own. The @@ -1056,14 +510,16 @@ class LRParser: continue else: if errtoken: - if hasattr(errtoken,"lineno"): lineno = lookahead.lineno - else: lineno = 0 + if hasattr(errtoken, 'lineno'): + lineno = lookahead.lineno + else: + lineno = 0 if lineno: - sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) + sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) else: - sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) + sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) else: - sys.stderr.write("yacc: Parse error in input. EOF\n") + sys.stderr.write('yacc: Parse error in input. EOF\n') return else: @@ -1094,34 +550,43 @@ class LRParser: if sym.type == 'error': # Hmmm. Error is on top of stack, we'll just nuke input # symbol and continue + if tracking: + sym.endlineno = getattr(lookahead, 'lineno', sym.lineno) + sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos) lookahead = None continue + + # Create the error symbol for the first time and make it the new lookahead symbol t = YaccSymbol() t.type = 'error' - if hasattr(lookahead,"lineno"): - t.lineno = lookahead.lineno + + if hasattr(lookahead, 'lineno'): + t.lineno = t.endlineno = lookahead.lineno + if hasattr(lookahead, 'lexpos'): + t.lexpos = t.endlexpos = lookahead.lexpos t.value = lookahead lookaheadstack.append(lookahead) lookahead = t else: - symstack.pop() + sym = symstack.pop() + if tracking: + lookahead.lineno = sym.lineno + lookahead.lexpos = sym.lexpos statestack.pop() - state = statestack[-1] # Potential bug fix + state = statestack[-1] continue - # Call an error function here - raise RuntimeError("yacc: internal parser error!!!\n") + # If we'r here, something really bad happened + raise RuntimeError('yacc: internal parser error!!!\n') # ----------------------------------------------------------------------------- # === Grammar Representation === # # The following functions, classes, and variables are used to represent and -# manipulate the rules that make up a grammar. +# manipulate the rules that make up a grammar. # ----------------------------------------------------------------------------- -import re - # regex matching identifiers _is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$') @@ -1131,7 +596,7 @@ _is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$') # This class stores the raw information about a single production or grammar rule. # A grammar rule refers to a specification such as this: # -# expr : expr PLUS term +# expr : expr PLUS term # # Here are the basic attributes defined on all productions # @@ -1151,7 +616,7 @@ _is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$') class Production(object): reduced = 0 - def __init__(self,number,name,prod,precedence=('right',0),func=None,file='',line=0): + def __init__(self, number, name, prod, precedence=('right', 0), func=None, file='', line=0): self.name = name self.prod = tuple(prod) self.number = number @@ -1162,11 +627,11 @@ class Production(object): self.prec = precedence # Internal settings used during table construction - + self.len = len(self.prod) # Length of the production # Create a list of unique production symbols used in the production - self.usyms = [ ] + self.usyms = [] for s in self.prod: if s not in self.usyms: self.usyms.append(s) @@ -1177,15 +642,15 @@ class Production(object): # Create a string representation if self.prod: - self.str = "%s -> %s" % (self.name," ".join(self.prod)) + self.str = '%s -> %s' % (self.name, ' '.join(self.prod)) else: - self.str = "%s -> <empty>" % self.name + self.str = '%s -> <empty>' % self.name def __str__(self): return self.str def __repr__(self): - return "Production("+str(self)+")" + return 'Production(' + str(self) + ')' def __len__(self): return len(self.prod) @@ -1193,62 +658,37 @@ class Production(object): def __nonzero__(self): return 1 - def __getitem__(self,index): + def __getitem__(self, index): return self.prod[index] - - # Return the nth lr_item from the production (or None if at the end) - def lr_item(self,n): - if n > len(self.prod): return None - p = LRItem(self,n) - # Precompute the list of productions immediately following. Hack. Remove later + # Return the nth lr_item from the production (or None if at the end) + def lr_item(self, n): + if n > len(self.prod): + return None + p = LRItem(self, n) + # Precompute the list of productions immediately following. try: - p.lr_after = Prodnames[p.prod[n+1]] - except (IndexError,KeyError): + p.lr_after = self.Prodnames[p.prod[n+1]] + except (IndexError, KeyError): p.lr_after = [] try: p.lr_before = p.prod[n-1] except IndexError: p.lr_before = None - return p - - # Bind the production function name to a callable - def bind(self,pdict): - if self.func: - self.callable = pdict[self.func] - -# This class serves as a minimal standin for Production objects when -# reading table data from files. It only contains information -# actually used by the LR parsing engine, plus some additional -# debugging information. -class MiniProduction(object): - def __init__(self,str,name,len,func,file,line): - self.name = name - self.len = len - self.func = func - self.callable = None - self.file = file - self.line = line - self.str = str - def __str__(self): - return self.str - def __repr__(self): - return "MiniProduction(%s)" % self.str # Bind the production function name to a callable - def bind(self,pdict): + def bind(self, pdict): if self.func: self.callable = pdict[self.func] - # ----------------------------------------------------------------------------- # class LRItem # # This class represents a specific stage of parsing a production rule. For -# example: +# example: # -# expr : expr . PLUS term +# expr : expr . PLUS term # # In the above, the "." represents the current location of the parse. Here # basic attributes: @@ -1267,26 +707,26 @@ class MiniProduction(object): # ----------------------------------------------------------------------------- class LRItem(object): - def __init__(self,p,n): + def __init__(self, p, n): self.name = p.name self.prod = list(p.prod) self.number = p.number self.lr_index = n - self.lookaheads = { } - self.prod.insert(n,".") + self.lookaheads = {} + self.prod.insert(n, '.') self.prod = tuple(self.prod) self.len = len(self.prod) self.usyms = p.usyms def __str__(self): if self.prod: - s = "%s -> %s" % (self.name," ".join(self.prod)) + s = '%s -> %s' % (self.name, ' '.join(self.prod)) else: - s = "%s -> <empty>" % self.name + s = '%s -> <empty>' % self.name return s def __repr__(self): - return "LRItem("+str(self)+")" + return 'LRItem(' + str(self) + ')' # ----------------------------------------------------------------------------- # rightmost_terminal() @@ -1309,41 +749,42 @@ def rightmost_terminal(symbols, terminals): # This data is used for critical parts of the table generation process later. # ----------------------------------------------------------------------------- -class GrammarError(YaccError): pass +class GrammarError(YaccError): + pass class Grammar(object): - def __init__(self,terminals): + def __init__(self, terminals): self.Productions = [None] # A list of all of the productions. The first - # entry is always reserved for the purpose of - # building an augmented grammar + # entry is always reserved for the purpose of + # building an augmented grammar - self.Prodnames = { } # A dictionary mapping the names of nonterminals to a list of all - # productions of that nonterminal. + self.Prodnames = {} # A dictionary mapping the names of nonterminals to a list of all + # productions of that nonterminal. - self.Prodmap = { } # A dictionary that is only used to detect duplicate - # productions. + self.Prodmap = {} # A dictionary that is only used to detect duplicate + # productions. - self.Terminals = { } # A dictionary mapping the names of terminal symbols to a - # list of the rules where they are used. + self.Terminals = {} # A dictionary mapping the names of terminal symbols to a + # list of the rules where they are used. for term in terminals: self.Terminals[term] = [] self.Terminals['error'] = [] - self.Nonterminals = { } # A dictionary mapping names of nonterminals to a list - # of rule numbers where they are used. + self.Nonterminals = {} # A dictionary mapping names of nonterminals to a list + # of rule numbers where they are used. - self.First = { } # A dictionary of precomputed FIRST(x) symbols + self.First = {} # A dictionary of precomputed FIRST(x) symbols - self.Follow = { } # A dictionary of precomputed FOLLOW(x) symbols + self.Follow = {} # A dictionary of precomputed FOLLOW(x) symbols - self.Precedence = { } # Precedence rules for each terminal. Contains tuples of the - # form ('right',level) or ('nonassoc', level) or ('left',level) + self.Precedence = {} # Precedence rules for each terminal. Contains tuples of the + # form ('right',level) or ('nonassoc', level) or ('left',level) - self.UsedPrecedence = { } # Precedence rules that were actually used by the grammer. - # This is only used to provide error checking and to generate - # a warning about unused precedence rules. + self.UsedPrecedence = set() # Precedence rules that were actually used by the grammer. + # This is only used to provide error checking and to generate + # a warning about unused precedence rules. self.Start = None # Starting symbol for the grammar @@ -1351,7 +792,7 @@ class Grammar(object): def __len__(self): return len(self.Productions) - def __getitem__(self,index): + def __getitem__(self, index): return self.Productions[index] # ----------------------------------------------------------------------------- @@ -1362,14 +803,14 @@ class Grammar(object): # # ----------------------------------------------------------------------------- - def set_precedence(self,term,assoc,level): - assert self.Productions == [None],"Must call set_precedence() before add_production()" + def set_precedence(self, term, assoc, level): + assert self.Productions == [None], 'Must call set_precedence() before add_production()' if term in self.Precedence: - raise GrammarError("Precedence already specified for terminal '%s'" % term) - if assoc not in ['left','right','nonassoc']: + raise GrammarError('Precedence already specified for terminal %r' % term) + if assoc not in ['left', 'right', 'nonassoc']: raise GrammarError("Associativity must be one of 'left','right', or 'nonassoc'") - self.Precedence[term] = (assoc,level) - + self.Precedence[term] = (assoc, level) + # ----------------------------------------------------------------------------- # add_production() # @@ -1387,72 +828,74 @@ class Grammar(object): # are valid and that %prec is used correctly. # ----------------------------------------------------------------------------- - def add_production(self,prodname,syms,func=None,file='',line=0): + def add_production(self, prodname, syms, func=None, file='', line=0): if prodname in self.Terminals: - raise GrammarError("%s:%d: Illegal rule name '%s'. Already defined as a token" % (file,line,prodname)) + raise GrammarError('%s:%d: Illegal rule name %r. Already defined as a token' % (file, line, prodname)) if prodname == 'error': - raise GrammarError("%s:%d: Illegal rule name '%s'. error is a reserved word" % (file,line,prodname)) + raise GrammarError('%s:%d: Illegal rule name %r. error is a reserved word' % (file, line, prodname)) if not _is_identifier.match(prodname): - raise GrammarError("%s:%d: Illegal rule name '%s'" % (file,line,prodname)) + raise GrammarError('%s:%d: Illegal rule name %r' % (file, line, prodname)) - # Look for literal tokens - for n,s in enumerate(syms): + # Look for literal tokens + for n, s in enumerate(syms): if s[0] in "'\"": - try: - c = eval(s) - if (len(c) > 1): - raise GrammarError("%s:%d: Literal token %s in rule '%s' may only be a single character" % (file,line,s, prodname)) - if not c in self.Terminals: - self.Terminals[c] = [] - syms[n] = c - continue - except SyntaxError: - pass + try: + c = eval(s) + if (len(c) > 1): + raise GrammarError('%s:%d: Literal token %s in rule %r may only be a single character' % + (file, line, s, prodname)) + if c not in self.Terminals: + self.Terminals[c] = [] + syms[n] = c + continue + except SyntaxError: + pass if not _is_identifier.match(s) and s != '%prec': - raise GrammarError("%s:%d: Illegal name '%s' in rule '%s'" % (file,line,s, prodname)) - + raise GrammarError('%s:%d: Illegal name %r in rule %r' % (file, line, s, prodname)) + # Determine the precedence level if '%prec' in syms: if syms[-1] == '%prec': - raise GrammarError("%s:%d: Syntax error. Nothing follows %%prec" % (file,line)) + raise GrammarError('%s:%d: Syntax error. Nothing follows %%prec' % (file, line)) if syms[-2] != '%prec': - raise GrammarError("%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule" % (file,line)) + raise GrammarError('%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule' % + (file, line)) precname = syms[-1] - prodprec = self.Precedence.get(precname,None) + prodprec = self.Precedence.get(precname) if not prodprec: - raise GrammarError("%s:%d: Nothing known about the precedence of '%s'" % (file,line,precname)) + raise GrammarError('%s:%d: Nothing known about the precedence of %r' % (file, line, precname)) else: - self.UsedPrecedence[precname] = 1 + self.UsedPrecedence.add(precname) del syms[-2:] # Drop %prec from the rule else: # If no %prec, precedence is determined by the rightmost terminal symbol - precname = rightmost_terminal(syms,self.Terminals) - prodprec = self.Precedence.get(precname,('right',0)) - + precname = rightmost_terminal(syms, self.Terminals) + prodprec = self.Precedence.get(precname, ('right', 0)) + # See if the rule is already in the rulemap - map = "%s -> %s" % (prodname,syms) + map = '%s -> %s' % (prodname, syms) if map in self.Prodmap: m = self.Prodmap[map] - raise GrammarError("%s:%d: Duplicate rule %s. " % (file,line, m) + - "Previous definition at %s:%d" % (m.file, m.line)) + raise GrammarError('%s:%d: Duplicate rule %s. ' % (file, line, m) + + 'Previous definition at %s:%d' % (m.file, m.line)) # From this point on, everything is valid. Create a new Production instance pnumber = len(self.Productions) - if not prodname in self.Nonterminals: - self.Nonterminals[prodname] = [ ] + if prodname not in self.Nonterminals: + self.Nonterminals[prodname] = [] # Add the production number to Terminals and Nonterminals for t in syms: if t in self.Terminals: self.Terminals[t].append(pnumber) else: - if not t in self.Nonterminals: - self.Nonterminals[t] = [ ] + if t not in self.Nonterminals: + self.Nonterminals[t] = [] self.Nonterminals[t].append(pnumber) # Create a production and add it to the list of productions - p = Production(pnumber,prodname,syms,prodprec,func,file,line) + p = Production(pnumber, prodname, syms, prodprec, func, file, line) self.Productions.append(p) self.Prodmap[map] = p @@ -1460,22 +903,21 @@ class Grammar(object): try: self.Prodnames[prodname].append(p) except KeyError: - self.Prodnames[prodname] = [ p ] - return 0 + self.Prodnames[prodname] = [p] # ----------------------------------------------------------------------------- # set_start() # - # Sets the starting symbol and creates the augmented grammar. Production + # Sets the starting symbol and creates the augmented grammar. Production # rule 0 is S' -> start where start is the start symbol. # ----------------------------------------------------------------------------- - def set_start(self,start=None): + def set_start(self, start=None): if not start: start = self.Productions[1].name if start not in self.Nonterminals: - raise GrammarError("start symbol %s undefined" % start) - self.Productions[0] = Production(0,"S'",[start]) + raise GrammarError('start symbol %s undefined' % start) + self.Productions[0] = Production(0, "S'", [start]) self.Nonterminals[start].append(0) self.Start = start @@ -1487,26 +929,20 @@ class Grammar(object): # ----------------------------------------------------------------------------- def find_unreachable(self): - + # Mark all symbols that are reachable from a symbol s def mark_reachable_from(s): - if reachable[s]: - # We've already reached symbol s. + if s in reachable: return - reachable[s] = 1 - for p in self.Prodnames.get(s,[]): + reachable.add(s) + for p in self.Prodnames.get(s, []): for r in p.prod: mark_reachable_from(r) - reachable = { } - for s in list(self.Terminals) + list(self.Nonterminals): - reachable[s] = 0 + reachable = set() + mark_reachable_from(self.Productions[0].prod[0]) + return [s for s in self.Nonterminals if s not in reachable] - mark_reachable_from( self.Productions[0].prod[0] ) - - return [s for s in list(self.Nonterminals) - if not reachable[s]] - # ----------------------------------------------------------------------------- # infinite_cycles() # @@ -1520,20 +956,20 @@ class Grammar(object): # Terminals: for t in self.Terminals: - terminates[t] = 1 + terminates[t] = True - terminates['$end'] = 1 + terminates['$end'] = True # Nonterminals: # Initialize to false: for n in self.Nonterminals: - terminates[n] = 0 + terminates[n] = False # Then propagate termination until no change: - while 1: - some_change = 0 - for (n,pl) in self.Prodnames.items(): + while True: + some_change = False + for (n, pl) in self.Prodnames.items(): # Nonterminal n terminates iff any of its productions terminates. for p in pl: # Production p terminates iff all of its rhs symbols terminate. @@ -1541,19 +977,19 @@ class Grammar(object): if not terminates[s]: # The symbol s does not terminate, # so production p does not terminate. - p_terminates = 0 + p_terminates = False break else: # didn't break from the loop, # so every symbol s terminates # so production p terminates. - p_terminates = 1 + p_terminates = True if p_terminates: # symbol n terminates! if not terminates[n]: - terminates[n] = 1 - some_change = 1 + terminates[n] = True + some_change = True # Don't need to consider any more productions for this n. break @@ -1561,9 +997,9 @@ class Grammar(object): break infinite = [] - for (s,term) in terminates.items(): + for (s, term) in terminates.items(): if not term: - if not s in self.Prodnames and not s in self.Terminals and s != 'error': + if s not in self.Prodnames and s not in self.Terminals and s != 'error': # s is used-but-not-defined, and we've already warned of that, # so it would be overkill to say that it's also non-terminating. pass @@ -1572,22 +1008,22 @@ class Grammar(object): return infinite - # ----------------------------------------------------------------------------- # undefined_symbols() # # Find all symbols that were used the grammar, but not defined as tokens or # grammar rules. Returns a list of tuples (sym, prod) where sym in the symbol - # and prod is the production where the symbol was used. + # and prod is the production where the symbol was used. # ----------------------------------------------------------------------------- def undefined_symbols(self): result = [] for p in self.Productions: - if not p: continue + if not p: + continue for s in p.prod: - if not s in self.Prodnames and not s in self.Terminals and s != 'error': - result.append((s,p)) + if s not in self.Prodnames and s not in self.Terminals and s != 'error': + result.append((s, p)) return result # ----------------------------------------------------------------------------- @@ -1598,7 +1034,7 @@ class Grammar(object): # ----------------------------------------------------------------------------- def unused_terminals(self): unused_tok = [] - for s,v in self.Terminals.items(): + for s, v in self.Terminals.items(): if s != 'error' and not v: unused_tok.append(s) @@ -1613,7 +1049,7 @@ class Grammar(object): def unused_rules(self): unused_prod = [] - for s,v in self.Nonterminals.items(): + for s, v in self.Nonterminals.items(): if not v: p = self.Prodnames[s][0] unused_prod.append(p) @@ -1625,15 +1061,15 @@ class Grammar(object): # Returns a list of tuples (term,precedence) corresponding to precedence # rules that were never used by the grammar. term is the name of the terminal # on which precedence was applied and precedence is a string such as 'left' or - # 'right' corresponding to the type of precedence. + # 'right' corresponding to the type of precedence. # ----------------------------------------------------------------------------- def unused_precedence(self): unused = [] for termname in self.Precedence: if not (termname in self.Terminals or termname in self.UsedPrecedence): - unused.append((termname,self.Precedence[termname][0])) - + unused.append((termname, self.Precedence[termname][0])) + return unused # ------------------------------------------------------------------------- @@ -1644,19 +1080,20 @@ class Grammar(object): # During execution of compute_first1, the result may be incomplete. # Afterward (e.g., when called from compute_follow()), it will be complete. # ------------------------------------------------------------------------- - def _first(self,beta): + def _first(self, beta): # We are computing First(x1,x2,x3,...,xn) - result = [ ] + result = [] for x in beta: - x_produces_empty = 0 + x_produces_empty = False # Add all the non-<empty> symbols of First[x] to the result. for f in self.First[x]: if f == '<empty>': - x_produces_empty = 1 + x_produces_empty = True else: - if f not in result: result.append(f) + if f not in result: + result.append(f) if x_produces_empty: # We have to consider the next x in beta, @@ -1695,17 +1132,17 @@ class Grammar(object): self.First[n] = [] # Then propagate symbols until no change: - while 1: - some_change = 0 + while True: + some_change = False for n in self.Nonterminals: for p in self.Prodnames[n]: for f in self._first(p.prod): if f not in self.First[n]: - self.First[n].append( f ) - some_change = 1 + self.First[n].append(f) + some_change = True if not some_change: break - + return self.First # --------------------------------------------------------------------- @@ -1715,7 +1152,7 @@ class Grammar(object): # follow set is the set of all symbols that might follow a given # non-terminal. See the Dragon book, 2nd Ed. p. 189. # --------------------------------------------------------------------- - def compute_follow(self,start=None): + def compute_follow(self, start=None): # If already computed, return the result if self.Follow: return self.Follow @@ -1726,36 +1163,36 @@ class Grammar(object): # Add '$end' to the follow list of the start symbol for k in self.Nonterminals: - self.Follow[k] = [ ] + self.Follow[k] = [] if not start: start = self.Productions[1].name - self.Follow[start] = [ '$end' ] + self.Follow[start] = ['$end'] - while 1: - didadd = 0 + while True: + didadd = False for p in self.Productions[1:]: # Here is the production set - for i in range(len(p.prod)): - B = p.prod[i] + for i, B in enumerate(p.prod): if B in self.Nonterminals: # Okay. We got a non-terminal in a production fst = self._first(p.prod[i+1:]) - hasempty = 0 + hasempty = False for f in fst: if f != '<empty>' and f not in self.Follow[B]: self.Follow[B].append(f) - didadd = 1 + didadd = True if f == '<empty>': - hasempty = 1 + hasempty = True if hasempty or i == (len(p.prod)-1): # Add elements of follow(a) to follow(b) for f in self.Follow[p.name]: if f not in self.Follow[B]: self.Follow[B].append(f) - didadd = 1 - if not didadd: break + didadd = True + if not didadd: + break return self.Follow @@ -1779,15 +1216,15 @@ class Grammar(object): lastlri = p i = 0 lr_items = [] - while 1: + while True: if i > len(p): lri = None else: - lri = LRItem(p,i) + lri = LRItem(p, i) # Precompute the list of productions immediately following try: lri.lr_after = self.Prodnames[lri.prod[i+1]] - except (IndexError,KeyError): + except (IndexError, KeyError): lri.lr_after = [] try: lri.lr_before = lri.prod[i-1] @@ -1795,86 +1232,17 @@ class Grammar(object): lri.lr_before = None lastlri.lr_next = lri - if not lri: break + if not lri: + break lr_items.append(lri) lastlri = lri i += 1 p.lr_items = lr_items # ----------------------------------------------------------------------------- -# == Class LRTable == -# -# This basic class represents a basic table of LR parsing information. -# Methods for generating the tables are not defined here. They are defined -# in the derived class LRGeneratedTable. -# ----------------------------------------------------------------------------- - -class VersionError(YaccError): pass - -class LRTable(object): - def __init__(self): - self.lr_action = None - self.lr_goto = None - self.lr_productions = None - self.lr_method = None - - def read_table(self,module): - if isinstance(module,types.ModuleType): - parsetab = module - else: - if sys.version_info[0] < 3: - exec("import %s as parsetab" % module) - else: - env = { } - exec("import %s as parsetab" % module, env, env) - parsetab = env['parsetab'] - - if parsetab._tabversion != __tabversion__: - raise VersionError("yacc table file version is out of date") - - self.lr_action = parsetab._lr_action - self.lr_goto = parsetab._lr_goto - - self.lr_productions = [] - for p in parsetab._lr_productions: - self.lr_productions.append(MiniProduction(*p)) - - self.lr_method = parsetab._lr_method - return parsetab._lr_signature - - def read_pickle(self,filename): - try: - import cPickle as pickle - except ImportError: - import pickle - - in_f = open(filename,"rb") - - tabversion = pickle.load(in_f) - if tabversion != __tabversion__: - raise VersionError("yacc table file version is out of date") - self.lr_method = pickle.load(in_f) - signature = pickle.load(in_f) - self.lr_action = pickle.load(in_f) - self.lr_goto = pickle.load(in_f) - productions = pickle.load(in_f) - - self.lr_productions = [] - for p in productions: - self.lr_productions.append(MiniProduction(*p)) - - in_f.close() - return signature - - # Bind all production function names to callable objects in pdict - def bind_callables(self,pdict): - for p in self.lr_productions: - p.bind(pdict) - -# ----------------------------------------------------------------------------- # === LR Generator === # -# The following classes and functions are used to generate LR parsing tables on +# The following classes and functions are used to generate LR parsing tables on # a grammar. # ----------------------------------------------------------------------------- @@ -1895,17 +1263,18 @@ class LRTable(object): # FP - Set-valued function # ------------------------------------------------------------------------------ -def digraph(X,R,FP): - N = { } +def digraph(X, R, FP): + N = {} for x in X: - N[x] = 0 + N[x] = 0 stack = [] - F = { } + F = {} for x in X: - if N[x] == 0: traverse(x,N,stack,F,X,R,FP) + if N[x] == 0: + traverse(x, N, stack, F, X, R, FP) return F -def traverse(x,N,stack,F,X,R,FP): +def traverse(x, N, stack, F, X, R, FP): stack.append(x) d = len(stack) N[x] = d @@ -1914,35 +1283,34 @@ def traverse(x,N,stack,F,X,R,FP): rel = R(x) # Get y's related to x for y in rel: if N[y] == 0: - traverse(y,N,stack,F,X,R,FP) - N[x] = min(N[x],N[y]) - for a in F.get(y,[]): - if a not in F[x]: F[x].append(a) + traverse(y, N, stack, F, X, R, FP) + N[x] = min(N[x], N[y]) + for a in F.get(y, []): + if a not in F[x]: + F[x].append(a) if N[x] == d: - N[stack[-1]] = MAXINT - F[stack[-1]] = F[x] - element = stack.pop() - while element != x: - N[stack[-1]] = MAXINT - F[stack[-1]] = F[x] - element = stack.pop() + N[stack[-1]] = MAXINT + F[stack[-1]] = F[x] + element = stack.pop() + while element != x: + N[stack[-1]] = MAXINT + F[stack[-1]] = F[x] + element = stack.pop() + +class LALRError(YaccError): + pass -class LALRError(YaccError): pass # ----------------------------------------------------------------------------- -# == LRGeneratedTable == +# == LRTable == # # This class implements the LR table generation algorithm. There are no -# public methods except for write() +# public methods. # ----------------------------------------------------------------------------- -class LRGeneratedTable(LRTable): - def __init__(self,grammar,method='LALR',log=None): - if method not in ['SLR','LALR']: - raise LALRError("Unsupported method %s" % method) - +class LRTable: + def __init__(self, grammar, log=None): self.grammar = grammar - self.lr_method = method # Set up the logger if not log: @@ -1958,7 +1326,7 @@ class LRGeneratedTable(LRTable): self._add_count = 0 # Internal counter used to detect cycles - # Diagonistic information filled in by the table generator + # Diagnostic information filled in by the table generator self.sr_conflict = 0 self.rr_conflict = 0 self.conflicts = [] # List of conflicts @@ -1972,23 +1340,29 @@ class LRGeneratedTable(LRTable): self.grammar.compute_follow() self.lr_parse_table() + # Bind all production function names to callable objects in pdict + def bind_callables(self, pdict): + for p in self.lr_productions: + p.bind(pdict) + # Compute the LR(0) closure operation on I, where I is a set of LR(0) items. - def lr0_closure(self,I): + def lr0_closure(self, I): self._add_count += 1 # Add everything in I to J J = I[:] - didadd = 1 + didadd = True while didadd: - didadd = 0 + didadd = False for j in J: for x in j.lr_after: - if getattr(x,"lr0_added",0) == self._add_count: continue + if getattr(x, 'lr0_added', 0) == self._add_count: + continue # Add B --> .G to J J.append(x.lr_next) x.lr0_added = self._add_count - didadd = 1 + didadd = True return J @@ -1999,43 +1373,43 @@ class LRGeneratedTable(LRTable): # objects). With uniqueness, we can later do fast set comparisons using # id(obj) instead of element-wise comparison. - def lr0_goto(self,I,x): + def lr0_goto(self, I, x): # First we look for a previously cached entry - g = self.lr_goto_cache.get((id(I),x),None) - if g: return g + g = self.lr_goto_cache.get((id(I), x)) + if g: + return g # Now we generate the goto set in a way that guarantees uniqueness # of the result - s = self.lr_goto_cache.get(x,None) + s = self.lr_goto_cache.get(x) if not s: - s = { } + s = {} self.lr_goto_cache[x] = s - gs = [ ] + gs = [] for p in I: n = p.lr_next if n and n.lr_before == x: - s1 = s.get(id(n),None) + s1 = s.get(id(n)) if not s1: - s1 = { } + s1 = {} s[id(n)] = s1 gs.append(n) s = s1 - g = s.get('$end',None) + g = s.get('$end') if not g: if gs: g = self.lr0_closure(gs) s['$end'] = g else: s['$end'] = gs - self.lr_goto_cache[(id(I),x)] = g + self.lr_goto_cache[(id(I), x)] = g return g # Compute the LR(0) sets of item function def lr0_items(self): - - C = [ self.lr0_closure([self.grammar.Productions[0].lr_next]) ] + C = [self.lr0_closure([self.grammar.Productions[0].lr_next])] i = 0 for I in C: self.lr0_cidhash[id(I)] = i @@ -2048,15 +1422,15 @@ class LRGeneratedTable(LRTable): i += 1 # Collect all of the symbols that could possibly be in the goto(I,X) sets - asyms = { } + asyms = {} for ii in I: for s in ii.usyms: asyms[s] = None for x in asyms: - g = self.lr0_goto(I,x) - if not g: continue - if id(g) in self.lr0_cidhash: continue + g = self.lr0_goto(I, x) + if not g or id(g) in self.lr0_cidhash: + continue self.lr0_cidhash[id(g)] = len(C) C.append(g) @@ -2091,19 +1465,21 @@ class LRGeneratedTable(LRTable): # ----------------------------------------------------------------------------- def compute_nullable_nonterminals(self): - nullable = {} + nullable = set() num_nullable = 0 - while 1: - for p in self.grammar.Productions[1:]: - if p.len == 0: - nullable[p.name] = 1 + while True: + for p in self.grammar.Productions[1:]: + if p.len == 0: + nullable.add(p.name) continue - for t in p.prod: - if not t in nullable: break - else: - nullable[p.name] = 1 - if len(nullable) == num_nullable: break - num_nullable = len(nullable) + for t in p.prod: + if t not in nullable: + break + else: + nullable.add(p.name) + if len(nullable) == num_nullable: + break + num_nullable = len(nullable) return nullable # ----------------------------------------------------------------------------- @@ -2117,16 +1493,16 @@ class LRGeneratedTable(LRTable): # The input C is the set of LR(0) items. # ----------------------------------------------------------------------------- - def find_nonterminal_transitions(self,C): - trans = [] - for state in range(len(C)): - for p in C[state]: - if p.lr_index < p.len - 1: - t = (state,p.prod[p.lr_index+1]) - if t[1] in self.grammar.Nonterminals: - if t not in trans: trans.append(t) - state = state + 1 - return trans + def find_nonterminal_transitions(self, C): + trans = [] + for stateno, state in enumerate(C): + for p in state: + if p.lr_index < p.len - 1: + t = (stateno, p.prod[p.lr_index+1]) + if t[1] in self.grammar.Nonterminals: + if t not in trans: + trans.append(t) + return trans # ----------------------------------------------------------------------------- # dr_relation() @@ -2137,21 +1513,21 @@ class LRGeneratedTable(LRTable): # Returns a list of terminals. # ----------------------------------------------------------------------------- - def dr_relation(self,C,trans,nullable): - dr_set = { } - state,N = trans + def dr_relation(self, C, trans, nullable): + state, N = trans terms = [] - g = self.lr0_goto(C[state],N) + g = self.lr0_goto(C[state], N) for p in g: - if p.lr_index < p.len - 1: - a = p.prod[p.lr_index+1] - if a in self.grammar.Terminals: - if a not in terms: terms.append(a) + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index+1] + if a in self.grammar.Terminals: + if a not in terms: + terms.append(a) # This extra bit is to handle the start state if state == 0 and N == self.grammar.Productions[0].prod[0]: - terms.append('$end') + terms.append('$end') return terms @@ -2161,18 +1537,18 @@ class LRGeneratedTable(LRTable): # Computes the READS() relation (p,A) READS (t,C). # ----------------------------------------------------------------------------- - def reads_relation(self,C, trans, empty): + def reads_relation(self, C, trans, empty): # Look for empty transitions rel = [] state, N = trans - g = self.lr0_goto(C[state],N) - j = self.lr0_cidhash.get(id(g),-1) + g = self.lr0_goto(C[state], N) + j = self.lr0_cidhash.get(id(g), -1) for p in g: if p.lr_index < p.len - 1: - a = p.prod[p.lr_index + 1] - if a in empty: - rel.append((j,a)) + a = p.prod[p.lr_index + 1] + if a in empty: + rel.append((j, a)) return rel @@ -2204,8 +1580,7 @@ class LRGeneratedTable(LRTable): # # ----------------------------------------------------------------------------- - def compute_lookback_includes(self,C,trans,nullable): - + def compute_lookback_includes(self, C, trans, nullable): lookdict = {} # Dictionary of lookback relations includedict = {} # Dictionary of include relations @@ -2215,11 +1590,12 @@ class LRGeneratedTable(LRTable): dtrans[t] = 1 # Loop over all transitions and compute lookbacks and includes - for state,N in trans: + for state, N in trans: lookb = [] includes = [] for p in C[state]: - if p.name != N: continue + if p.name != N: + continue # Okay, we have a name match. We now follow the production all the way # through the state machine until we get the . on the right hand side @@ -2227,44 +1603,50 @@ class LRGeneratedTable(LRTable): lr_index = p.lr_index j = state while lr_index < p.len - 1: - lr_index = lr_index + 1 - t = p.prod[lr_index] - - # Check to see if this symbol and state are a non-terminal transition - if (j,t) in dtrans: - # Yes. Okay, there is some chance that this is an includes relation - # the only way to know for certain is whether the rest of the - # production derives empty - - li = lr_index + 1 - while li < p.len: - if p.prod[li] in self.grammar.Terminals: break # No forget it - if not p.prod[li] in nullable: break - li = li + 1 - else: - # Appears to be a relation between (j,t) and (state,N) - includes.append((j,t)) - - g = self.lr0_goto(C[j],t) # Go to next set - j = self.lr0_cidhash.get(id(g),-1) # Go to next state + lr_index = lr_index + 1 + t = p.prod[lr_index] + + # Check to see if this symbol and state are a non-terminal transition + if (j, t) in dtrans: + # Yes. Okay, there is some chance that this is an includes relation + # the only way to know for certain is whether the rest of the + # production derives empty + + li = lr_index + 1 + while li < p.len: + if p.prod[li] in self.grammar.Terminals: + break # No forget it + if p.prod[li] not in nullable: + break + li = li + 1 + else: + # Appears to be a relation between (j,t) and (state,N) + includes.append((j, t)) + + g = self.lr0_goto(C[j], t) # Go to next set + j = self.lr0_cidhash.get(id(g), -1) # Go to next state # When we get here, j is the final state, now we have to locate the production for r in C[j]: - if r.name != p.name: continue - if r.len != p.len: continue - i = 0 - # This look is comparing a production ". A B C" with "A B C ." - while i < r.lr_index: - if r.prod[i] != p.prod[i+1]: break - i = i + 1 - else: - lookb.append((j,r)) + if r.name != p.name: + continue + if r.len != p.len: + continue + i = 0 + # This look is comparing a production ". A B C" with "A B C ." + while i < r.lr_index: + if r.prod[i] != p.prod[i+1]: + break + i = i + 1 + else: + lookb.append((j, r)) for i in includes: - if not i in includedict: includedict[i] = [] - includedict[i].append((state,N)) - lookdict[(state,N)] = lookb + if i not in includedict: + includedict[i] = [] + includedict[i].append((state, N)) + lookdict[(state, N)] = lookb - return lookdict,includedict + return lookdict, includedict # ----------------------------------------------------------------------------- # compute_read_sets() @@ -2278,10 +1660,10 @@ class LRGeneratedTable(LRTable): # Returns a set containing the read sets # ----------------------------------------------------------------------------- - def compute_read_sets(self,C, ntrans, nullable): - FP = lambda x: self.dr_relation(C,x,nullable) - R = lambda x: self.reads_relation(C,x,nullable) - F = digraph(ntrans,R,FP) + def compute_read_sets(self, C, ntrans, nullable): + FP = lambda x: self.dr_relation(C, x, nullable) + R = lambda x: self.reads_relation(C, x, nullable) + F = digraph(ntrans, R, FP) return F # ----------------------------------------------------------------------------- @@ -2300,11 +1682,11 @@ class LRGeneratedTable(LRTable): # Returns a set containing the follow sets # ----------------------------------------------------------------------------- - def compute_follow_sets(self,ntrans,readsets,inclsets): - FP = lambda x: readsets[x] - R = lambda x: inclsets.get(x,[]) - F = digraph(ntrans,R,FP) - return F + def compute_follow_sets(self, ntrans, readsets, inclsets): + FP = lambda x: readsets[x] + R = lambda x: inclsets.get(x, []) + F = digraph(ntrans, R, FP) + return F # ----------------------------------------------------------------------------- # add_lookaheads() @@ -2318,15 +1700,16 @@ class LRGeneratedTable(LRTable): # in the lookbacks set # ----------------------------------------------------------------------------- - def add_lookaheads(self,lookbacks,followset): - for trans,lb in lookbacks.items(): + def add_lookaheads(self, lookbacks, followset): + for trans, lb in lookbacks.items(): # Loop over productions in lookback - for state,p in lb: - if not state in p.lookaheads: - p.lookaheads[state] = [] - f = followset.get(trans,[]) - for a in f: - if a not in p.lookaheads[state]: p.lookaheads[state].append(a) + for state, p in lb: + if state not in p.lookaheads: + p.lookaheads[state] = [] + f = followset.get(trans, []) + for a in f: + if a not in p.lookaheads[state]: + p.lookaheads[state].append(a) # ----------------------------------------------------------------------------- # add_lalr_lookaheads() @@ -2335,7 +1718,7 @@ class LRGeneratedTable(LRTable): # with LALR parsing # ----------------------------------------------------------------------------- - def add_lalr_lookaheads(self,C): + def add_lalr_lookaheads(self, C): # Determine all of the nullable nonterminals nullable = self.compute_nullable_nonterminals() @@ -2343,16 +1726,16 @@ class LRGeneratedTable(LRTable): trans = self.find_nonterminal_transitions(C) # Compute read sets - readsets = self.compute_read_sets(C,trans,nullable) + readsets = self.compute_read_sets(C, trans, nullable) # Compute lookback/includes relations - lookd, included = self.compute_lookback_includes(C,trans,nullable) + lookd, included = self.compute_lookback_includes(C, trans, nullable) # Compute LALR FOLLOW sets - followsets = self.compute_follow_sets(trans,readsets,included) + followsets = self.compute_follow_sets(trans, readsets, included) # Add all of the lookaheads - self.add_lookaheads(lookd,followsets) + self.add_lookaheads(lookd, followsets) # ----------------------------------------------------------------------------- # lr_parse_table() @@ -2366,324 +1749,179 @@ class LRGeneratedTable(LRTable): action = self.lr_action # Action array log = self.log # Logger for output - actionp = { } # Action production array (temporary) - - log.info("Parsing method: %s", self.lr_method) + actionp = {} # Action production array (temporary) # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items # This determines the number of states C = self.lr0_items() - - if self.lr_method == 'LALR': - self.add_lalr_lookaheads(C) + self.add_lalr_lookaheads(C) # Build the parser table, state by state st = 0 for I in C: # Loop over each production in I - actlist = [ ] # List of actions - st_action = { } - st_actionp = { } - st_goto = { } - log.info("") - log.info("state %d", st) - log.info("") + actlist = [] # List of actions + st_action = {} + st_actionp = {} + st_goto = {} + log.info('') + log.info('state %d', st) + log.info('') for p in I: - log.info(" (%d) %s", p.number, str(p)) - log.info("") + log.info(' (%d) %s', p.number, p) + log.info('') for p in I: - if p.len == p.lr_index + 1: - if p.name == "S'": - # Start symbol. Accept! - st_action["$end"] = 0 - st_actionp["$end"] = p - else: - # We are at the end of a production. Reduce! - if self.lr_method == 'LALR': - laheads = p.lookaheads[st] - else: - laheads = self.grammar.Follow[p.name] - for a in laheads: - actlist.append((a,p,"reduce using rule %d (%s)" % (p.number,p))) - r = st_action.get(a,None) - if r is not None: - # Whoa. Have a shift/reduce or reduce/reduce conflict - if r > 0: - # Need to decide on shift or reduce here - # By default we favor shifting. Need to add - # some precedence rules here. - sprec,slevel = Productions[st_actionp[a].number].prec - rprec,rlevel = Precedence.get(a,('right',0)) - if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): - # We really need to reduce here. - st_action[a] = -p.number - st_actionp[a] = p - if not slevel and not rlevel: - log.info(" ! shift/reduce conflict for %s resolved as reduce",a) - self.sr_conflicts.append((st,a,'reduce')) - Productions[p.number].reduced += 1 - elif (slevel == rlevel) and (rprec == 'nonassoc'): - st_action[a] = None - else: - # Hmmm. Guess we'll keep the shift - if not rlevel: - log.info(" ! shift/reduce conflict for %s resolved as shift",a) - self.sr_conflicts.append((st,a,'shift')) - elif r < 0: - # Reduce/reduce conflict. In this case, we favor the rule - # that was defined first in the grammar file - oldp = Productions[-r] - pp = Productions[p.number] - if oldp.line > pp.line: - st_action[a] = -p.number - st_actionp[a] = p - chosenp,rejectp = pp,oldp - Productions[p.number].reduced += 1 - Productions[oldp.number].reduced -= 1 - else: - chosenp,rejectp = oldp,pp - self.rr_conflicts.append((st,chosenp,rejectp)) - log.info(" ! reduce/reduce conflict for %s resolved using rule %d (%s)", a,st_actionp[a].number, st_actionp[a]) + if p.len == p.lr_index + 1: + if p.name == "S'": + # Start symbol. Accept! + st_action['$end'] = 0 + st_actionp['$end'] = p + else: + # We are at the end of a production. Reduce! + laheads = p.lookaheads[st] + for a in laheads: + actlist.append((a, p, 'reduce using rule %d (%s)' % (p.number, p))) + r = st_action.get(a) + if r is not None: + # Whoa. Have a shift/reduce or reduce/reduce conflict + if r > 0: + # Need to decide on shift or reduce here + # By default we favor shifting. Need to add + # some precedence rules here. + + # Shift precedence comes from the token + sprec, slevel = Precedence.get(a, ('right', 0)) + + # Reduce precedence comes from rule being reduced (p) + rprec, rlevel = Productions[p.number].prec + + if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): + # We really need to reduce here. + st_action[a] = -p.number + st_actionp[a] = p + if not slevel and not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as reduce', a) + self.sr_conflicts.append((st, a, 'reduce')) + Productions[p.number].reduced += 1 + elif (slevel == rlevel) and (rprec == 'nonassoc'): + st_action[a] = None + else: + # Hmmm. Guess we'll keep the shift + if not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as shift', a) + self.sr_conflicts.append((st, a, 'shift')) + elif r < 0: + # Reduce/reduce conflict. In this case, we favor the rule + # that was defined first in the grammar file + oldp = Productions[-r] + pp = Productions[p.number] + if oldp.line > pp.line: + st_action[a] = -p.number + st_actionp[a] = p + chosenp, rejectp = pp, oldp + Productions[p.number].reduced += 1 + Productions[oldp.number].reduced -= 1 else: - raise LALRError("Unknown conflict in state %d" % st) + chosenp, rejectp = oldp, pp + self.rr_conflicts.append((st, chosenp, rejectp)) + log.info(' ! reduce/reduce conflict for %s resolved using rule %d (%s)', + a, st_actionp[a].number, st_actionp[a]) else: - st_action[a] = -p.number - st_actionp[a] = p - Productions[p.number].reduced += 1 - else: - i = p.lr_index - a = p.prod[i+1] # Get symbol right after the "." - if a in self.grammar.Terminals: - g = self.lr0_goto(I,a) - j = self.lr0_cidhash.get(id(g),-1) - if j >= 0: - # We are in a shift state - actlist.append((a,p,"shift and go to state %d" % j)) - r = st_action.get(a,None) - if r is not None: - # Whoa have a shift/reduce or shift/shift conflict - if r > 0: - if r != j: - raise LALRError("Shift/shift conflict in state %d" % st) - elif r < 0: - # Do a precedence check. - # - if precedence of reduce rule is higher, we reduce. - # - if precedence of reduce is same and left assoc, we reduce. - # - otherwise we shift - rprec,rlevel = Productions[st_actionp[a].number].prec - sprec,slevel = Precedence.get(a,('right',0)) - if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')): - # We decide to shift here... highest precedence to shift - Productions[st_actionp[a].number].reduced -= 1 - st_action[a] = j - st_actionp[a] = p - if not rlevel: - log.info(" ! shift/reduce conflict for %s resolved as shift",a) - self.sr_conflicts.append((st,a,'shift')) - elif (slevel == rlevel) and (rprec == 'nonassoc'): - st_action[a] = None - else: - # Hmmm. Guess we'll keep the reduce - if not slevel and not rlevel: - log.info(" ! shift/reduce conflict for %s resolved as reduce",a) - self.sr_conflicts.append((st,a,'reduce')) - + raise LALRError('Unknown conflict in state %d' % st) + else: + st_action[a] = -p.number + st_actionp[a] = p + Productions[p.number].reduced += 1 + else: + i = p.lr_index + a = p.prod[i+1] # Get symbol right after the "." + if a in self.grammar.Terminals: + g = self.lr0_goto(I, a) + j = self.lr0_cidhash.get(id(g), -1) + if j >= 0: + # We are in a shift state + actlist.append((a, p, 'shift and go to state %d' % j)) + r = st_action.get(a) + if r is not None: + # Whoa have a shift/reduce or shift/shift conflict + if r > 0: + if r != j: + raise LALRError('Shift/shift conflict in state %d' % st) + elif r < 0: + # Do a precedence check. + # - if precedence of reduce rule is higher, we reduce. + # - if precedence of reduce is same and left assoc, we reduce. + # - otherwise we shift + + # Shift precedence comes from the token + sprec, slevel = Precedence.get(a, ('right', 0)) + + # Reduce precedence comes from the rule that could have been reduced + rprec, rlevel = Productions[st_actionp[a].number].prec + + if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')): + # We decide to shift here... highest precedence to shift + Productions[st_actionp[a].number].reduced -= 1 + st_action[a] = j + st_actionp[a] = p + if not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as shift', a) + self.sr_conflicts.append((st, a, 'shift')) + elif (slevel == rlevel) and (rprec == 'nonassoc'): + st_action[a] = None else: - raise LALRError("Unknown conflict in state %d" % st) + # Hmmm. Guess we'll keep the reduce + if not slevel and not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as reduce', a) + self.sr_conflicts.append((st, a, 'reduce')) + else: - st_action[a] = j - st_actionp[a] = p + raise LALRError('Unknown conflict in state %d' % st) + else: + st_action[a] = j + st_actionp[a] = p # Print the actions associated with each terminal - _actprint = { } - for a,p,m in actlist: + _actprint = {} + for a, p, m in actlist: if a in st_action: if p is st_actionp[a]: - log.info(" %-15s %s",a,m) - _actprint[(a,m)] = 1 - log.info("") + log.info(' %-15s %s', a, m) + _actprint[(a, m)] = 1 + log.info('') # Print the actions that were not used. (debugging) not_used = 0 - for a,p,m in actlist: + for a, p, m in actlist: if a in st_action: if p is not st_actionp[a]: - if not (a,m) in _actprint: - log.debug(" ! %-15s [ %s ]",a,m) + if not (a, m) in _actprint: + log.debug(' ! %-15s [ %s ]', a, m) not_used = 1 - _actprint[(a,m)] = 1 + _actprint[(a, m)] = 1 if not_used: - log.debug("") + log.debug('') # Construct the goto table for this state - nkeys = { } + nkeys = {} for ii in I: for s in ii.usyms: if s in self.grammar.Nonterminals: nkeys[s] = None for n in nkeys: - g = self.lr0_goto(I,n) - j = self.lr0_cidhash.get(id(g),-1) + g = self.lr0_goto(I, n) + j = self.lr0_cidhash.get(id(g), -1) if j >= 0: st_goto[n] = j - log.info(" %-30s shift and go to state %d",n,j) + log.info(' %-30s shift and go to state %d', n, j) action[st] = st_action actionp[st] = st_actionp goto[st] = st_goto st += 1 - - # ----------------------------------------------------------------------------- - # write() - # - # This function writes the LR parsing tables to a file - # ----------------------------------------------------------------------------- - - def write_table(self,modulename,outputdir='',signature=""): - basemodulename = modulename.split(".")[-1] - filename = os.path.join(outputdir,basemodulename) + ".py" - try: - f = open(filename,"w") - - f.write(""" -# %s -# This file is automatically generated. Do not edit. -_tabversion = %r - -_lr_method = %r - -_lr_signature = %r - """ % (filename, __tabversion__, self.lr_method, signature)) - - # Change smaller to 0 to go back to original tables - smaller = 1 - - # Factor out names to try and make smaller - if smaller: - items = { } - - for s,nd in self.lr_action.items(): - for name,v in nd.items(): - i = items.get(name) - if not i: - i = ([],[]) - items[name] = i - i[0].append(s) - i[1].append(v) - - f.write("\n_lr_action_items = {") - for k,v in items.items(): - f.write("%r:([" % k) - for i in v[0]: - f.write("%r," % i) - f.write("],[") - for i in v[1]: - f.write("%r," % i) - - f.write("]),") - f.write("}\n") - - f.write(""" -_lr_action = { } -for _k, _v in _lr_action_items.items(): - for _x,_y in zip(_v[0],_v[1]): - if not _x in _lr_action: _lr_action[_x] = { } - _lr_action[_x][_k] = _y -del _lr_action_items -""") - - else: - f.write("\n_lr_action = { "); - for k,v in self.lr_action.items(): - f.write("(%r,%r):%r," % (k[0],k[1],v)) - f.write("}\n"); - - if smaller: - # Factor out names to try and make smaller - items = { } - - for s,nd in self.lr_goto.items(): - for name,v in nd.items(): - i = items.get(name) - if not i: - i = ([],[]) - items[name] = i - i[0].append(s) - i[1].append(v) - - f.write("\n_lr_goto_items = {") - for k,v in items.items(): - f.write("%r:([" % k) - for i in v[0]: - f.write("%r," % i) - f.write("],[") - for i in v[1]: - f.write("%r," % i) - - f.write("]),") - f.write("}\n") - - f.write(""" -_lr_goto = { } -for _k, _v in _lr_goto_items.items(): - for _x,_y in zip(_v[0],_v[1]): - if not _x in _lr_goto: _lr_goto[_x] = { } - _lr_goto[_x][_k] = _y -del _lr_goto_items -""") - else: - f.write("\n_lr_goto = { "); - for k,v in self.lr_goto.items(): - f.write("(%r,%r):%r," % (k[0],k[1],v)) - f.write("}\n"); - - # Write production table - f.write("_lr_productions = [\n") - for p in self.lr_productions: - if p.func: - f.write(" (%r,%r,%d,%r,%r,%d),\n" % (p.str,p.name, p.len, p.func,p.file,p.line)) - else: - f.write(" (%r,%r,%d,None,None,None),\n" % (str(p),p.name, p.len)) - f.write("]\n") - f.close() - - except IOError: - e = sys.exc_info()[1] - sys.stderr.write("Unable to create '%s'\n" % filename) - sys.stderr.write(str(e)+"\n") - return - - - # ----------------------------------------------------------------------------- - # pickle_table() - # - # This function pickles the LR parsing tables to a supplied file object - # ----------------------------------------------------------------------------- - - def pickle_table(self,filename,signature=""): - try: - import cPickle as pickle - except ImportError: - import pickle - outf = open(filename,"wb") - pickle.dump(__tabversion__,outf,pickle_protocol) - pickle.dump(self.lr_method,outf,pickle_protocol) - pickle.dump(signature,outf,pickle_protocol) - pickle.dump(self.lr_action,outf,pickle_protocol) - pickle.dump(self.lr_goto,outf,pickle_protocol) - - outp = [] - for p in self.lr_productions: - if p.func: - outp.append((p.str,p.name, p.len, p.func,p.file,p.line)) - else: - outp.append((str(p),p.name,p.len,None,None,None)) - pickle.dump(outp,outf,pickle_protocol) - outf.close() - # ----------------------------------------------------------------------------- # === INTROSPECTION === # @@ -2700,26 +1938,18 @@ del _lr_goto_items # ----------------------------------------------------------------------------- def get_caller_module_dict(levels): - try: - raise RuntimeError - except RuntimeError: - e,b,t = sys.exc_info() - f = t.tb_frame - while levels > 0: - f = f.f_back - levels -= 1 - ldict = f.f_globals.copy() - if f.f_globals != f.f_locals: - ldict.update(f.f_locals) - - return ldict + f = sys._getframe(levels) + ldict = f.f_globals.copy() + if f.f_globals != f.f_locals: + ldict.update(f.f_locals) + return ldict # ----------------------------------------------------------------------------- # parse_grammar() # # This takes a raw grammar rule string and parses it into production data # ----------------------------------------------------------------------------- -def parse_grammar(doc,file,line): +def parse_grammar(doc, file, line): grammar = [] # Split the doc string into lines pstrings = doc.splitlines() @@ -2728,12 +1958,13 @@ def parse_grammar(doc,file,line): for ps in pstrings: dline += 1 p = ps.split() - if not p: continue + if not p: + continue try: if p[0] == '|': # This is a continuation of a previous rule if not lastp: - raise SyntaxError("%s:%d: Misplaced '|'" % (file,dline)) + raise SyntaxError("%s:%d: Misplaced '|'" % (file, dline)) prodname = lastp syms = p[1:] else: @@ -2742,13 +1973,13 @@ def parse_grammar(doc,file,line): syms = p[2:] assign = p[1] if assign != ':' and assign != '::=': - raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file,dline)) + raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file, dline)) - grammar.append((file,dline,prodname,syms)) + grammar.append((file, dline, prodname, syms)) except SyntaxError: raise except Exception: - raise SyntaxError("%s:%d: Syntax error in rule '%s'" % (file,dline,ps.strip())) + raise SyntaxError('%s:%d: Syntax error in rule %r' % (file, dline, ps.strip())) return grammar @@ -2760,14 +1991,14 @@ def parse_grammar(doc,file,line): # etc. # ----------------------------------------------------------------------------- class ParserReflect(object): - def __init__(self,pdict,log=None): + def __init__(self, pdict, log=None): self.pdict = pdict self.start = None self.error_func = None self.tokens = None - self.files = {} + self.modules = set() self.grammar = [] - self.error = 0 + self.error = False if log is None: self.log = PlyLogger(sys.stderr) @@ -2781,7 +2012,7 @@ class ParserReflect(object): self.get_tokens() self.get_precedence() self.get_pfunctions() - + # Validate all of the information def validate_all(self): self.validate_start() @@ -2789,32 +2020,28 @@ class ParserReflect(object): self.validate_tokens() self.validate_precedence() self.validate_pfunctions() - self.validate_files() + self.validate_modules() return self.error # Compute a signature over the grammar def signature(self): + parts = [] try: - from hashlib import md5 - except ImportError: - from md5 import md5 - try: - sig = md5() if self.start: - sig.update(self.start.encode('latin-1')) + parts.append(self.start) if self.prec: - sig.update("".join(["".join(p) for p in self.prec]).encode('latin-1')) + parts.append(''.join([''.join(p) for p in self.prec])) if self.tokens: - sig.update(" ".join(self.tokens).encode('latin-1')) + parts.append(' '.join(self.tokens)) for f in self.pfuncs: if f[3]: - sig.update(f[3].encode('latin-1')) - except (TypeError,ValueError): + parts.append(f[3]) + except (TypeError, ValueError): pass - return sig.digest() + return ''.join(parts) # ----------------------------------------------------------------------------- - # validate_file() + # validate_modules() # # This method checks to see if there are duplicated p_rulename() functions # in the parser module file. Without this function, it is really easy for @@ -2824,32 +2051,29 @@ class ParserReflect(object): # to try and detect duplicates. # ----------------------------------------------------------------------------- - def validate_files(self): + def validate_modules(self): # Match def p_funcname( fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(') - for filename in self.files.keys(): - base,ext = os.path.splitext(filename) - if ext != '.py': return 1 # No idea. Assume it's okay. - + for module in self.modules: try: - f = open(filename) - lines = f.readlines() - f.close() + lines, linen = inspect.getsourcelines(module) except IOError: continue - counthash = { } - for linen,l in enumerate(lines): + counthash = {} + for linen, line in enumerate(lines): linen += 1 - m = fre.match(l) + m = fre.match(line) if m: name = m.group(1) prev = counthash.get(name) if not prev: counthash[name] = linen else: - self.log.warning("%s:%d: Function %s redefined. Previously defined on line %d", filename,linen,name,prev) + filename = inspect.getsourcefile(module) + self.log.warning('%s:%d: Function %s redefined. Previously defined on line %d', + filename, linen, name, prev) # Get the start symbol def get_start(self): @@ -2858,7 +2082,7 @@ class ParserReflect(object): # Validate the start symbol def validate_start(self): if self.start is not None: - if not isinstance(self.start,str): + if not isinstance(self.start, str): self.log.error("'start' must be a string") # Look for error handler @@ -2868,162 +2092,173 @@ class ParserReflect(object): # Validate the error function def validate_error_func(self): if self.error_func: - if isinstance(self.error_func,types.FunctionType): + if isinstance(self.error_func, types.FunctionType): ismethod = 0 elif isinstance(self.error_func, types.MethodType): ismethod = 1 else: self.log.error("'p_error' defined, but is not a function or method") - self.error = 1 + self.error = True return - eline = func_code(self.error_func).co_firstlineno - efile = func_code(self.error_func).co_filename - self.files[efile] = 1 + eline = self.error_func.__code__.co_firstlineno + efile = self.error_func.__code__.co_filename + module = inspect.getmodule(self.error_func) + self.modules.add(module) - if (func_code(self.error_func).co_argcount != 1+ismethod): - self.log.error("%s:%d: p_error() requires 1 argument",efile,eline) - self.error = 1 + argcount = self.error_func.__code__.co_argcount - ismethod + if argcount != 1: + self.log.error('%s:%d: p_error() requires 1 argument', efile, eline) + self.error = True # Get the tokens map def get_tokens(self): - tokens = self.pdict.get("tokens",None) + tokens = self.pdict.get('tokens') if not tokens: - self.log.error("No token list is defined") - self.error = 1 + self.log.error('No token list is defined') + self.error = True return - if not isinstance(tokens,(list, tuple)): - self.log.error("tokens must be a list or tuple") - self.error = 1 + if not isinstance(tokens, (list, tuple)): + self.log.error('tokens must be a list or tuple') + self.error = True return - + if not tokens: - self.log.error("tokens is empty") - self.error = 1 + self.log.error('tokens is empty') + self.error = True return - self.tokens = tokens + self.tokens = sorted(tokens) # Validate the tokens def validate_tokens(self): # Validate the tokens. if 'error' in self.tokens: self.log.error("Illegal token name 'error'. Is a reserved word") - self.error = 1 + self.error = True return - terminals = {} + terminals = set() for n in self.tokens: if n in terminals: - self.log.warning("Token '%s' multiply defined", n) - terminals[n] = 1 + self.log.warning('Token %r multiply defined', n) + terminals.add(n) # Get the precedence map (if any) def get_precedence(self): - self.prec = self.pdict.get("precedence",None) + self.prec = self.pdict.get('precedence') # Validate and parse the precedence map def validate_precedence(self): preclist = [] if self.prec: - if not isinstance(self.prec,(list,tuple)): - self.log.error("precedence must be a list or tuple") - self.error = 1 + if not isinstance(self.prec, (list, tuple)): + self.log.error('precedence must be a list or tuple') + self.error = True return - for level,p in enumerate(self.prec): - if not isinstance(p,(list,tuple)): - self.log.error("Bad precedence table") - self.error = 1 + for level, p in enumerate(self.prec): + if not isinstance(p, (list, tuple)): + self.log.error('Bad precedence table') + self.error = True return if len(p) < 2: - self.log.error("Malformed precedence entry %s. Must be (assoc, term, ..., term)",p) - self.error = 1 + self.log.error('Malformed precedence entry %s. Must be (assoc, term, ..., term)', p) + self.error = True return assoc = p[0] - if not isinstance(assoc,str): - self.log.error("precedence associativity must be a string") - self.error = 1 + if not isinstance(assoc, str): + self.log.error('precedence associativity must be a string') + self.error = True return for term in p[1:]: - if not isinstance(term,str): - self.log.error("precedence items must be strings") - self.error = 1 + if not isinstance(term, str): + self.log.error('precedence items must be strings') + self.error = True return - preclist.append((term,assoc,level+1)) + preclist.append((term, assoc, level+1)) self.preclist = preclist # Get all p_functions from the grammar def get_pfunctions(self): p_functions = [] for name, item in self.pdict.items(): - if name[:2] != 'p_': continue - if name == 'p_error': continue - if isinstance(item,(types.FunctionType,types.MethodType)): - line = func_code(item).co_firstlineno - file = func_code(item).co_filename - p_functions.append((line,file,name,item.__doc__)) - - # Sort all of the actions by line number - p_functions.sort() + if not name.startswith('p_') or name == 'p_error': + continue + if isinstance(item, (types.FunctionType, types.MethodType)): + line = getattr(item, 'co_firstlineno', item.__code__.co_firstlineno) + module = inspect.getmodule(item) + p_functions.append((line, module, name, item.__doc__)) + + # Sort all of the actions by line number; make sure to stringify + # modules to make them sortable, since `line` may not uniquely sort all + # p functions + p_functions.sort(key=lambda p_function: ( + p_function[0], + str(p_function[1]), + p_function[2], + p_function[3])) self.pfuncs = p_functions - # Validate all of the p_functions def validate_pfunctions(self): grammar = [] # Check for non-empty symbols if len(self.pfuncs) == 0: - self.log.error("no rules of the form p_rulename are defined") - self.error = 1 - return - - for line, file, name, doc in self.pfuncs: + self.log.error('no rules of the form p_rulename are defined') + self.error = True + return + + for line, module, name, doc in self.pfuncs: + file = inspect.getsourcefile(module) func = self.pdict[name] if isinstance(func, types.MethodType): reqargs = 2 else: reqargs = 1 - if func_code(func).co_argcount > reqargs: - self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,func.__name__) - self.error = 1 - elif func_code(func).co_argcount < reqargs: - self.log.error("%s:%d: Rule '%s' requires an argument",file,line,func.__name__) - self.error = 1 + if func.__code__.co_argcount > reqargs: + self.log.error('%s:%d: Rule %r has too many arguments', file, line, func.__name__) + self.error = True + elif func.__code__.co_argcount < reqargs: + self.log.error('%s:%d: Rule %r requires an argument', file, line, func.__name__) + self.error = True elif not func.__doc__: - self.log.warning("%s:%d: No documentation string specified in function '%s' (ignored)",file,line,func.__name__) + self.log.warning('%s:%d: No documentation string specified in function %r (ignored)', + file, line, func.__name__) else: try: - parsed_g = parse_grammar(doc,file,line) + parsed_g = parse_grammar(doc, file, line) for g in parsed_g: grammar.append((name, g)) - except SyntaxError: - e = sys.exc_info()[1] + except SyntaxError as e: self.log.error(str(e)) - self.error = 1 + self.error = True # Looks like a valid grammar rule # Mark the file in which defined. - self.files[file] = 1 + self.modules.add(module) # Secondary validation step that looks for p_ definitions that are not functions # or functions that look like they might be grammar rules. - for n,v in self.pdict.items(): - if n[0:2] == 'p_' and isinstance(v, (types.FunctionType, types.MethodType)): continue - if n[0:2] == 't_': continue - if n[0:2] == 'p_' and n != 'p_error': - self.log.warning("'%s' not defined as a function", n) - if ((isinstance(v,types.FunctionType) and func_code(v).co_argcount == 1) or - (isinstance(v,types.MethodType) and func_code(v).co_argcount == 2)): - try: - doc = v.__doc__.split(" ") - if doc[1] == ':': - self.log.warning("%s:%d: Possible grammar rule '%s' defined without p_ prefix", - func_code(v).co_filename, func_code(v).co_firstlineno,n) - except Exception: - pass + for n, v in self.pdict.items(): + if n.startswith('p_') and isinstance(v, (types.FunctionType, types.MethodType)): + continue + if n.startswith('t_'): + continue + if n.startswith('p_') and n != 'p_error': + self.log.warning('%r not defined as a function', n) + if ((isinstance(v, types.FunctionType) and v.__code__.co_argcount == 1) or + (isinstance(v, types.MethodType) and v.__func__.__code__.co_argcount == 2)): + if v.__doc__: + try: + doc = v.__doc__.split(' ') + if doc[1] == ':': + self.log.warning('%s:%d: Possible grammar rule %r defined without p_ prefix', + v.__code__.co_filename, v.__code__.co_firstlineno, n) + except IndexError: + pass self.grammar = grammar @@ -3033,76 +2268,61 @@ class ParserReflect(object): # Build a parser # ----------------------------------------------------------------------------- -def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, start=None, - check_recursion=1, optimize=0, write_tables=1, debugfile=debug_file,outputdir='', - debuglog=None, errorlog = None, picklefile=None): - - global parse # Reference to the parsing method of the last built parser - - # If pickling is enabled, table files are not created +def yacc(*, debug=yaccdebug, module=None, start=None, + check_recursion=True, optimize=False, debugfile=debug_file, + debuglog=None, errorlog=None): - if picklefile: - write_tables = 0 + # Reference to the parsing method of the last built parser + global parse if errorlog is None: errorlog = PlyLogger(sys.stderr) # Get the module dictionary used for the parser if module: - _items = [(k,getattr(module,k)) for k in dir(module)] + _items = [(k, getattr(module, k)) for k in dir(module)] pdict = dict(_items) + # If no __file__ or __package__ attributes are available, try to obtain them + # from the __module__ instead + if '__file__' not in pdict: + pdict['__file__'] = sys.modules[pdict['__module__']].__file__ + if '__package__' not in pdict and '__module__' in pdict: + if hasattr(sys.modules[pdict['__module__']], '__package__'): + pdict['__package__'] = sys.modules[pdict['__module__']].__package__ else: pdict = get_caller_module_dict(2) + # Set start symbol if it's specified directly using an argument + if start is not None: + pdict['start'] = start + # Collect parser information from the dictionary - pinfo = ParserReflect(pdict,log=errorlog) + pinfo = ParserReflect(pdict, log=errorlog) pinfo.get_all() if pinfo.error: - raise YaccError("Unable to build parser") - - # Check signature against table files (if any) - signature = pinfo.signature() - - # Read the tables - try: - lr = LRTable() - if picklefile: - read_signature = lr.read_pickle(picklefile) - else: - read_signature = lr.read_table(tabmodule) - if optimize or (read_signature == signature): - try: - lr.bind_callables(pinfo.pdict) - parser = LRParser(lr,pinfo.error_func) - parse = parser.parse - return parser - except Exception: - e = sys.exc_info()[1] - errorlog.warning("There was a problem loading the table file: %s", repr(e)) - except VersionError: - e = sys.exc_info() - errorlog.warning(str(e)) - except Exception: - pass + raise YaccError('Unable to build parser') if debuglog is None: if debug: - debuglog = PlyLogger(open(debugfile,"w")) + try: + debuglog = PlyLogger(open(debugfile, 'w')) + except IOError as e: + errorlog.warning("Couldn't open %r. %s" % (debugfile, e)) + debuglog = NullLogger() else: debuglog = NullLogger() - debuglog.info("Created by PLY version %s (http://www.dabeaz.com/ply)", __version__) + debuglog.info('Created by PLY (http://www.dabeaz.com/ply)') - - errors = 0 + errors = False # Validate the parser information if pinfo.validate_all(): - raise YaccError("Unable to build parser") - + raise YaccError('Unable to build parser') + if not pinfo.error_func: - errorlog.warning("no p_error() function is defined") + errorlog.warning('no p_error() function is defined') # Create a grammar object grammar = Grammar(pinfo.tokens) @@ -3110,20 +2330,18 @@ def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, star # Set precedence level for terminals for term, assoc, level in pinfo.preclist: try: - grammar.set_precedence(term,assoc,level) - except GrammarError: - e = sys.exc_info()[1] - errorlog.warning("%s",str(e)) + grammar.set_precedence(term, assoc, level) + except GrammarError as e: + errorlog.warning('%s', e) # Add productions to the grammar for funcname, gram in pinfo.grammar: file, line, prodname, syms = gram try: - grammar.add_production(prodname,syms,funcname,file,line) - except GrammarError: - e = sys.exc_info()[1] - errorlog.error("%s",str(e)) - errors = 1 + grammar.add_production(prodname, syms, funcname, file, line) + except GrammarError as e: + errorlog.error('%s', e) + errors = True # Set the grammar start symbols try: @@ -3131,146 +2349,134 @@ def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, star grammar.set_start(pinfo.start) else: grammar.set_start(start) - except GrammarError: - e = sys.exc_info()[1] + except GrammarError as e: errorlog.error(str(e)) - errors = 1 + errors = True if errors: - raise YaccError("Unable to build parser") + raise YaccError('Unable to build parser') # Verify the grammar structure undefined_symbols = grammar.undefined_symbols() for sym, prod in undefined_symbols: - errorlog.error("%s:%d: Symbol '%s' used, but not defined as a token or a rule",prod.file,prod.line,sym) - errors = 1 + errorlog.error('%s:%d: Symbol %r used, but not defined as a token or a rule', prod.file, prod.line, sym) + errors = True unused_terminals = grammar.unused_terminals() if unused_terminals: - debuglog.info("") - debuglog.info("Unused terminals:") - debuglog.info("") + debuglog.info('') + debuglog.info('Unused terminals:') + debuglog.info('') for term in unused_terminals: - errorlog.warning("Token '%s' defined, but not used", term) - debuglog.info(" %s", term) + errorlog.warning('Token %r defined, but not used', term) + debuglog.info(' %s', term) # Print out all productions to the debug log if debug: - debuglog.info("") - debuglog.info("Grammar") - debuglog.info("") - for n,p in enumerate(grammar.Productions): - debuglog.info("Rule %-5d %s", n, p) + debuglog.info('') + debuglog.info('Grammar') + debuglog.info('') + for n, p in enumerate(grammar.Productions): + debuglog.info('Rule %-5d %s', n, p) # Find unused non-terminals unused_rules = grammar.unused_rules() for prod in unused_rules: - errorlog.warning("%s:%d: Rule '%s' defined, but not used", prod.file, prod.line, prod.name) + errorlog.warning('%s:%d: Rule %r defined, but not used', prod.file, prod.line, prod.name) if len(unused_terminals) == 1: - errorlog.warning("There is 1 unused token") + errorlog.warning('There is 1 unused token') if len(unused_terminals) > 1: - errorlog.warning("There are %d unused tokens", len(unused_terminals)) + errorlog.warning('There are %d unused tokens', len(unused_terminals)) if len(unused_rules) == 1: - errorlog.warning("There is 1 unused rule") + errorlog.warning('There is 1 unused rule') if len(unused_rules) > 1: - errorlog.warning("There are %d unused rules", len(unused_rules)) + errorlog.warning('There are %d unused rules', len(unused_rules)) if debug: - debuglog.info("") - debuglog.info("Terminals, with rules where they appear") - debuglog.info("") + debuglog.info('') + debuglog.info('Terminals, with rules where they appear') + debuglog.info('') terms = list(grammar.Terminals) terms.sort() for term in terms: - debuglog.info("%-20s : %s", term, " ".join([str(s) for s in grammar.Terminals[term]])) - - debuglog.info("") - debuglog.info("Nonterminals, with rules where they appear") - debuglog.info("") + debuglog.info('%-20s : %s', term, ' '.join([str(s) for s in grammar.Terminals[term]])) + + debuglog.info('') + debuglog.info('Nonterminals, with rules where they appear') + debuglog.info('') nonterms = list(grammar.Nonterminals) nonterms.sort() for nonterm in nonterms: - debuglog.info("%-20s : %s", nonterm, " ".join([str(s) for s in grammar.Nonterminals[nonterm]])) - debuglog.info("") + debuglog.info('%-20s : %s', nonterm, ' '.join([str(s) for s in grammar.Nonterminals[nonterm]])) + debuglog.info('') if check_recursion: unreachable = grammar.find_unreachable() for u in unreachable: - errorlog.warning("Symbol '%s' is unreachable",u) + errorlog.warning('Symbol %r is unreachable', u) infinite = grammar.infinite_cycles() for inf in infinite: - errorlog.error("Infinite recursion detected for symbol '%s'", inf) - errors = 1 - + errorlog.error('Infinite recursion detected for symbol %r', inf) + errors = True + unused_prec = grammar.unused_precedence() for term, assoc in unused_prec: - errorlog.error("Precedence rule '%s' defined for unknown symbol '%s'", assoc, term) - errors = 1 + errorlog.error('Precedence rule %r defined for unknown symbol %r', assoc, term) + errors = True if errors: - raise YaccError("Unable to build parser") - - # Run the LRGeneratedTable on the grammar - if debug: - errorlog.debug("Generating %s tables", method) - - lr = LRGeneratedTable(grammar,method,debuglog) + raise YaccError('Unable to build parser') + + # Run the LRTable on the grammar + lr = LRTable(grammar, debuglog) if debug: num_sr = len(lr.sr_conflicts) # Report shift/reduce and reduce/reduce conflicts if num_sr == 1: - errorlog.warning("1 shift/reduce conflict") + errorlog.warning('1 shift/reduce conflict') elif num_sr > 1: - errorlog.warning("%d shift/reduce conflicts", num_sr) + errorlog.warning('%d shift/reduce conflicts', num_sr) num_rr = len(lr.rr_conflicts) if num_rr == 1: - errorlog.warning("1 reduce/reduce conflict") + errorlog.warning('1 reduce/reduce conflict') elif num_rr > 1: - errorlog.warning("%d reduce/reduce conflicts", num_rr) + errorlog.warning('%d reduce/reduce conflicts', num_rr) # Write out conflicts to the output file if debug and (lr.sr_conflicts or lr.rr_conflicts): - debuglog.warning("") - debuglog.warning("Conflicts:") - debuglog.warning("") + debuglog.warning('') + debuglog.warning('Conflicts:') + debuglog.warning('') for state, tok, resolution in lr.sr_conflicts: - debuglog.warning("shift/reduce conflict for %s in state %d resolved as %s", tok, state, resolution) - - already_reported = {} + debuglog.warning('shift/reduce conflict for %s in state %d resolved as %s', tok, state, resolution) + + already_reported = set() for state, rule, rejected in lr.rr_conflicts: - if (state,id(rule),id(rejected)) in already_reported: + if (state, id(rule), id(rejected)) in already_reported: continue - debuglog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule) - debuglog.warning("rejected rule (%s) in state %d", rejected,state) - errorlog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule) - errorlog.warning("rejected rule (%s) in state %d", rejected, state) - already_reported[state,id(rule),id(rejected)] = 1 - + debuglog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) + debuglog.warning('rejected rule (%s) in state %d', rejected, state) + errorlog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) + errorlog.warning('rejected rule (%s) in state %d', rejected, state) + already_reported.add((state, id(rule), id(rejected))) + warned_never = [] for state, rule, rejected in lr.rr_conflicts: if not rejected.reduced and (rejected not in warned_never): - debuglog.warning("Rule (%s) is never reduced", rejected) - errorlog.warning("Rule (%s) is never reduced", rejected) + debuglog.warning('Rule (%s) is never reduced', rejected) + errorlog.warning('Rule (%s) is never reduced', rejected) warned_never.append(rejected) - # Write the table file if requested - if write_tables: - lr.write_table(tabmodule,outputdir,signature) - - # Write a pickled version of the tables - if picklefile: - lr.pickle_table(picklefile,signature) - # Build the parser lr.bind_callables(pinfo.pdict) - parser = LRParser(lr,pinfo.error_func) + parser = LRParser(lr, pinfo.error_func) parse = parser.parse return parser diff --git a/components/script/dom/bindings/codegen/pythonpath.py b/components/script/dom/bindings/codegen/pythonpath.py deleted file mode 100644 index 793089551b5..00000000000 --- a/components/script/dom/bindings/codegen/pythonpath.py +++ /dev/null @@ -1,61 +0,0 @@ -# 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 http://mozilla.org/MPL/2.0/. - -""" -Run a python script, adding extra directories to the python path. -""" - - -def main(args): - def usage(): - print >>sys.stderr, "pythonpath.py -I directory script.py [args...]" - sys.exit(150) - - paths = [] - - while True: - try: - arg = args[0] - except IndexError: - usage() - - if arg == '-I': - args.pop(0) - try: - path = args.pop(0) - except IndexError: - usage() - - paths.append(os.path.abspath(path)) - continue - - if arg.startswith('-I'): - paths.append(os.path.abspath(args.pop(0)[2:])) - continue - - if arg.startswith('-D'): - os.chdir(args.pop(0)[2:]) - continue - - break - - script = args[0] - - sys.path[0:0] = [os.path.abspath(os.path.dirname(script))] + paths - sys.argv = args - sys.argc = len(args) - - frozenglobals['__name__'] = '__main__' - frozenglobals['__file__'] = script - - execfile(script, frozenglobals) - -# Freeze scope here ... why this makes things work I have no idea ... -frozenglobals = globals() - -import sys -import os - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/components/script/dom/bindings/codegen/run.py b/components/script/dom/bindings/codegen/run.py new file mode 100644 index 00000000000..7f58de15d69 --- /dev/null +++ b/components/script/dom/bindings/codegen/run.py @@ -0,0 +1,119 @@ +# 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/. + +import os +import sys +import json + + +def main(): + os.chdir(os.path.join(os.path.dirname(__file__))) + sys.path[0:0] = ["./parser", "./ply"] + + css_properties_json, out_dir = sys.argv[1:] + doc_servo = "../../../../../target/doc/servo" + webidls_dir = "../../webidls" + config_file = "Bindings.conf" + + import WebIDL + from Configuration import Configuration + from CodegenRust import CGBindingRoot + + parser = WebIDL.Parser(make_dir(os.path.join(out_dir, "cache"))) + webidls = [name for name in os.listdir(webidls_dir) if name.endswith(".webidl")] + for webidl in webidls: + filename = os.path.join(webidls_dir, webidl) + with open(filename, "rb") as f: + parser.parse(f.read(), filename) + + add_css_properties_attributes(css_properties_json, parser) + parser_results = parser.finish() + config = Configuration(config_file, parser_results) + make_dir(os.path.join(out_dir, "Bindings")) + + for name, filename in [ + ("PrototypeList", "PrototypeList.rs"), + ("RegisterBindings", "RegisterBindings.rs"), + ("InterfaceObjectMap", "InterfaceObjectMap.rs"), + ("InterfaceObjectMapData", "InterfaceObjectMapData.json"), + ("InterfaceTypes", "InterfaceTypes.rs"), + ("InheritTypes", "InheritTypes.rs"), + ("Bindings", "Bindings/mod.rs"), + ("UnionTypes", "UnionTypes.rs"), + ]: + generate(config, name, os.path.join(out_dir, filename)) + make_dir(doc_servo) + generate(config, "SupportedDomApis", os.path.join(doc_servo, "apis.html")) + + for webidl in webidls: + filename = os.path.join(webidls_dir, webidl) + prefix = "Bindings/%sBinding" % webidl[:-len(".webidl")] + module = CGBindingRoot(config, prefix, filename).define() + if module: + with open(os.path.join(out_dir, prefix + ".rs"), "wb") as f: + f.write(module.encode("utf-8")) + + +def make_dir(path): + if not os.path.exists(path): + os.makedirs(path) + return path + + +def generate(config, name, filename): + from CodegenRust import GlobalGenRoots + root = getattr(GlobalGenRoots, name)(config) + code = root.define() + with open(filename, "wb") as f: + f.write(code.encode("utf-8")) + + +def add_css_properties_attributes(css_properties_json, parser): + css_properties = json.load(open(css_properties_json, "rb")) + idl = "partial interface CSSStyleDeclaration {\n%s\n};\n" % "\n".join( + " [%sCEReactions, SetterThrows] attribute [TreatNullAs=EmptyString] DOMString %s;" % ( + ('Pref="%s", ' % data["pref"] if data["pref"] else ""), + attribute_name + ) + for (kind, properties_list) in sorted(css_properties.items()) + for (property_name, data) in sorted(properties_list.items()) + for attribute_name in attribute_names(property_name) + ) + parser.parse(idl.encode("utf-8"), "CSSStyleDeclaration_generated.webidl") + + +def attribute_names(property_name): + # https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-dashed-attribute + if property_name != "float": + yield property_name + else: + yield "_float" + + # https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-camel-cased-attribute + if "-" in property_name: + yield "".join(camel_case(property_name)) + + # https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-webkit-cased-attribute + if property_name.startswith("-webkit-"): + yield "".join(camel_case(property_name), True) + + +# https://drafts.csswg.org/cssom/#css-property-to-idl-attribute +def camel_case(chars, webkit_prefixed=False): + if webkit_prefixed: + chars = chars[1:] + next_is_uppercase = False + for c in chars: + if c == '-': + next_is_uppercase = True + elif next_is_uppercase: + next_is_uppercase = False + # Should be ASCII-uppercase, but all non-custom CSS property names are within ASCII + yield c.upper() + else: + yield c + + +if __name__ == "__main__": + main() diff --git a/components/script/dom/bindings/constant.rs b/components/script/dom/bindings/constant.rs index 42f10055080..bce6e3d7844 100644 --- a/components/script/dom/bindings/constant.rs +++ b/components/script/dom/bindings/constant.rs @@ -1,13 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! WebIDL constants. -use js::jsapi::{HandleObject, JSContext, JSPROP_ENUMERATE, JSPROP_PERMANENT}; -use js::jsapi::{JSPROP_READONLY, JS_DefineProperty}; +use crate::script_runtime::JSContext; +use js::jsapi::JSPROP_READONLY; +use js::jsapi::{JSPROP_ENUMERATE, JSPROP_PERMANENT}; use js::jsval::{BooleanValue, DoubleValue, Int32Value, JSVal, NullValue, UInt32Value}; -use libc; +use js::rust::wrappers::JS_DefineProperty; +use js::rust::HandleObject; /// Representation of an IDL constant. #[derive(Clone)] @@ -49,18 +51,17 @@ impl ConstantSpec { /// Defines constants on `obj`. /// Fails on JSAPI failure. -pub unsafe fn define_constants( - cx: *mut JSContext, - obj: HandleObject, - constants: &[ConstantSpec]) { +pub fn define_constants(cx: JSContext, obj: HandleObject, constants: &[ConstantSpec]) { for spec in constants { - rooted!(in(cx) let value = spec.get_value()); - assert!(JS_DefineProperty(cx, - obj, - spec.name.as_ptr() as *const libc::c_char, - value.handle(), - JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT, - None, - None)); + rooted!(in(*cx) let value = spec.get_value()); + unsafe { + assert!(JS_DefineProperty( + *cx, + obj, + spec.name.as_ptr() as *const libc::c_char, + value.handle(), + (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT) as u32 + )); + } } } diff --git a/components/script/dom/bindings/conversions.rs b/components/script/dom/bindings/conversions.rs index d77f6ea931c..4964180753d 100644 --- a/components/script/dom/bindings/conversions.rs +++ b/components/script/dom/bindings/conversions.rs @@ -1,71 +1,80 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Conversions of Rust values to and from `JSVal`. //! //! | IDL type | Argument type | Return type | //! |-------------------------|-----------------|----------------| -//! | any | `JSVal` | -//! | boolean | `bool` | -//! | byte | `i8` | -//! | octet | `u8` | -//! | short | `i16` | -//! | unsigned short | `u16` | -//! | long | `i32` | -//! | unsigned long | `u32` | -//! | long long | `i64` | -//! | unsigned long long | `u64` | -//! | unrestricted float | `f32` | -//! | float | `Finite<f32>` | -//! | unrestricted double | `f64` | -//! | double | `Finite<f64>` | -//! | DOMString | `DOMString` | -//! | USVString | `USVString` | -//! | ByteString | `ByteString` | -//! | object | `*mut JSObject` | -//! | interface types | `&T` | `Root<T>` | +//! | any | `JSVal` | | +//! | boolean | `bool` | | +//! | byte | `i8` | | +//! | octet | `u8` | | +//! | short | `i16` | | +//! | unsigned short | `u16` | | +//! | long | `i32` | | +//! | unsigned long | `u32` | | +//! | long long | `i64` | | +//! | unsigned long long | `u64` | | +//! | unrestricted float | `f32` | | +//! | float | `Finite<f32>` | | +//! | unrestricted double | `f64` | | +//! | double | `Finite<f64>` | | +//! | DOMString | `DOMString` | | +//! | USVString | `USVString` | | +//! | ByteString | `ByteString` | | +//! | object | `*mut JSObject` | | +//! | interface types | `&T` | `DomRoot<T>` | //! | dictionary types | `&T` | *unsupported* | -//! | enumeration types | `T` | -//! | callback function types | `Rc<T>` | -//! | nullable types | `Option<T>` | -//! | sequences | `Vec<T>` | -//! | union types | `T` | - -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{DomObject, Reflector}; -use dom::bindings::str::{ByteString, DOMString, USVString}; -use dom::bindings::trace::{JSTraceable, RootedTraceableBox}; -use dom::bindings::utils::DOMClass; -use js; -pub use js::conversions::{FromJSValConvertible, ToJSValConvertible, ConversionResult}; -pub use js::conversions::ConversionBehavior; +//! | enumeration types | `T` | | +//! | callback function types | `Rc<T>` | | +//! | nullable types | `Option<T>` | | +//! | sequences | `Vec<T>` | | +//! | union types | `T` | | + +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::{ByteString, DOMString, USVString}; +use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox}; +use crate::dom::bindings::utils::DOMClass; +use crate::dom::filelist::FileList; +use crate::dom::htmlcollection::HTMLCollection; +use crate::dom::htmlformcontrolscollection::HTMLFormControlsCollection; +use crate::dom::htmloptionscollection::HTMLOptionsCollection; +use crate::dom::nodelist::NodeList; +use crate::dom::windowproxy::WindowProxy; use js::conversions::latin1_to_string; +pub use js::conversions::ConversionBehavior; +pub use js::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible}; use js::error::throw_type_error; -use js::glue::{GetProxyPrivate, IsWrapper}; +use js::glue::GetProxyReservedSlot; +use js::glue::JS_GetReservedSlot; +use js::glue::{IsWrapper, UnwrapObjectDynamic}; use js::glue::{RUST_JSID_IS_INT, RUST_JSID_TO_INT}; -use js::glue::{RUST_JSID_IS_STRING, RUST_JSID_TO_STRING, UnwrapObject}; -use js::jsapi::{HandleId, HandleObject, HandleValue, JSContext, JSObject, JSString}; -use js::jsapi::{JS_GetLatin1StringCharsAndLength, JS_GetReservedSlot}; -use js::jsapi::{JS_GetTwoByteStringCharsAndLength, JS_IsArrayObject}; -use js::jsapi::{JS_NewStringCopyN, JS_StringHasLatin1Chars, MutableHandleValue}; -use js::jsval::{ObjectValue, StringValue}; -use js::rust::{ToString, get_object_class, is_dom_class, is_dom_object, maybe_wrap_value}; -use libc; +use js::glue::{RUST_JSID_IS_STRING, RUST_JSID_TO_STRING}; +use js::jsapi::{Heap, JSContext, JSObject, JSString}; +use js::jsapi::{IsWindowProxy, JS_DeprecatedStringHasLatin1Chars, JS_NewStringCopyN}; +use js::jsapi::{ + JS_GetLatin1StringCharsAndLength, JS_GetTwoByteStringCharsAndLength, JS_IsExceptionPending, +}; +use js::jsval::{ObjectValue, StringValue, UndefinedValue}; +use js::rust::wrappers::{IsArrayObject, JS_GetProperty, JS_HasProperty}; +use js::rust::{get_object_class, is_dom_class, is_dom_object, maybe_wrap_value, ToString}; +use js::rust::{HandleId, HandleObject, HandleValue, MutableHandleValue}; use num_traits::Float; use servo_config::opts; -use std::{char, ptr, slice}; +use std::{char, ffi, ptr, slice}; /// A trait to check whether a given `JSObject` implements an IDL interface. pub trait IDLInterface { /// Returns whether the given DOM class derives that interface. - fn derives(&'static DOMClass) -> bool; + fn derives(_: &'static DOMClass) -> bool; } /// A trait to mark an IDL interface as deriving from another one. -#[rustc_on_unimplemented = "The IDL interface `{Self}` is not derived from `{T}`."] pub trait DerivedFrom<T: Castable>: Castable {} impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> { @@ -76,20 +85,21 @@ impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> { } } -impl<T: Float + FromJSValConvertible<Config=()>> FromJSValConvertible for Finite<T> { +impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> { type Config = (); - unsafe fn from_jsval(cx: *mut JSContext, - value: HandleValue, - option: ()) - -> Result<ConversionResult<Finite<T>>, ()> { - let result = match FromJSValConvertible::from_jsval(cx, value, option) { - Ok(ConversionResult::Success(v)) => v, - Ok(ConversionResult::Failure(error)) => { + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + option: (), + ) -> Result<ConversionResult<Finite<T>>, ()> { + let result = match FromJSValConvertible::from_jsval(cx, value, option)? { + ConversionResult::Success(v) => v, + ConversionResult::Failure(error) => { + // FIXME(emilio): Why throwing instead of propagating the error? throw_type_error(cx, &error); return Err(()); - } - _ => return Err(()), + }, }; match Finite::new(result) { Some(v) => Ok(ConversionResult::Success(v)), @@ -101,47 +111,47 @@ impl<T: Float + FromJSValConvertible<Config=()>> FromJSValConvertible for Finite } } -impl <T: DomObject + IDLInterface> FromJSValConvertible for Root<T> { +impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> { type Config = (); - unsafe fn from_jsval(_cx: *mut JSContext, - value: HandleValue, - _config: Self::Config) - -> Result<ConversionResult<Root<T>>, ()> { - Ok(match root_from_handlevalue(value) { + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + _config: Self::Config, + ) -> Result<ConversionResult<DomRoot<T>>, ()> { + Ok(match root_from_handlevalue(value, cx) { Ok(result) => ConversionResult::Success(result), Err(()) => ConversionResult::Failure("value is not an object".into()), }) } } -impl <T: FromJSValConvertible + JSTraceable> FromJSValConvertible for RootedTraceableBox<T> { - type Config = T::Config; - - unsafe fn from_jsval(cx: *mut JSContext, - value: HandleValue, - config: Self::Config) - -> Result<ConversionResult<Self>, ()> { - T::from_jsval(cx, value, config).map(|result| { - match result { - ConversionResult::Success(v) => ConversionResult::Success(RootedTraceableBox::new(v)), - ConversionResult::Failure(e) => ConversionResult::Failure(e), - } - }) +impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { + let value = &**self; + value.to_jsval(cx, rval); } } -/// Convert `id` to a `DOMString`, assuming it is string-valued. -/// -/// Handling of invalid UTF-16 in strings depends on the relevant option. -/// -/// # Panics -/// -/// Panics if `id` is not string-valued. -pub fn string_jsid_to_string(cx: *mut JSContext, id: HandleId) -> DOMString { - unsafe { - assert!(RUST_JSID_IS_STRING(id)); - jsstring_to_str(cx, RUST_JSID_TO_STRING(id)) +impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>> +where + T: FromJSValConvertible + js::rust::GCMethods + Copy, + Heap<T>: JSTraceable + Default, +{ + type Config = T::Config; + + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + config: Self::Config, + ) -> Result<ConversionResult<Self>, ()> { + T::from_jsval(cx, value, config).map(|result| match result { + ConversionResult::Success(inner) => { + ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner))) + }, + ConversionResult::Failure(msg) => ConversionResult::Failure(msg), + }) } } @@ -150,12 +160,13 @@ pub fn string_jsid_to_string(cx: *mut JSContext, id: HandleId) -> DOMString { /// /// Handling of invalid UTF-16 in strings depends on the relevant option. pub unsafe fn jsid_to_string(cx: *mut JSContext, id: HandleId) -> Option<DOMString> { - if RUST_JSID_IS_STRING(id) { - return Some(jsstring_to_str(cx, RUST_JSID_TO_STRING(id))); + let id_raw = id.into(); + if RUST_JSID_IS_STRING(id_raw) { + return Some(jsstring_to_str(cx, RUST_JSID_TO_STRING(id_raw))); } - if RUST_JSID_IS_INT(id) { - return Some(RUST_JSID_TO_INT(id).to_string().into()); + if RUST_JSID_IS_INT(id_raw) { + return Some(RUST_JSID_TO_INT(id_raw).to_string().into()); } None @@ -169,7 +180,7 @@ impl ToJSValConvertible for USVString { } /// Behavior for stringification of `JSVal`s. -#[derive(PartialEq, Clone)] +#[derive(Clone, PartialEq)] pub enum StringificationBehavior { /// Convert `null` to the string `"null"`. Default, @@ -187,12 +198,12 @@ impl ToJSValConvertible for DOMString { // https://heycam.github.io/webidl/#es-DOMString impl FromJSValConvertible for DOMString { type Config = StringificationBehavior; - unsafe fn from_jsval(cx: *mut JSContext, - value: HandleValue, - null_behavior: StringificationBehavior) - -> Result<ConversionResult<DOMString>, ()> { - if null_behavior == StringificationBehavior::Empty && - value.get().is_null() { + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + null_behavior: StringificationBehavior, + ) -> Result<ConversionResult<DOMString>, ()> { + if null_behavior == StringificationBehavior::Empty && value.get().is_null() { Ok(ConversionResult::Success(DOMString::new())) } else { let jsstr = ToString(cx, value); @@ -209,7 +220,7 @@ impl FromJSValConvertible for DOMString { /// Convert the given `JSString` to a `DOMString`. Fails if the string does not /// contain valid UTF-16. pub unsafe fn jsstring_to_str(cx: *mut JSContext, s: *mut JSString) -> DOMString { - let latin1 = JS_StringHasLatin1Chars(s); + let latin1 = JS_DeprecatedStringHasLatin1Chars(s); DOMString::from_string(if latin1 { latin1_to_string(cx, s) } else { @@ -228,16 +239,19 @@ pub unsafe fn jsstring_to_str(cx: *mut JSContext, s: *mut JSString) -> DOMString "Found an unpaired surrogate in a DOM string. \ If you see this in real web content, \ please comment on https://github.com/servo/servo/issues/6564" - } + }; } if opts::get().replace_surrogates { error!(message!()); s.push('\u{FFFD}'); } else { - panic!(concat!(message!(), " Use `-Z replace-surrogates` \ - on the command line to make this non-fatal.")); + panic!(concat!( + message!(), + " Use `-Z replace-surrogates` \ + on the command line to make this non-fatal." + )); } - } + }, } } s @@ -247,33 +261,41 @@ pub unsafe fn jsstring_to_str(cx: *mut JSContext, s: *mut JSString) -> DOMString // http://heycam.github.io/webidl/#es-USVString impl FromJSValConvertible for USVString { type Config = (); - unsafe fn from_jsval(cx: *mut JSContext, value: HandleValue, _: ()) - -> Result<ConversionResult<USVString>, ()> { + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + _: (), + ) -> Result<ConversionResult<USVString>, ()> { let jsstr = ToString(cx, value); if jsstr.is_null() { debug!("ToString failed"); return Err(()); } - let latin1 = JS_StringHasLatin1Chars(jsstr); + let latin1 = JS_DeprecatedStringHasLatin1Chars(jsstr); if latin1 { // FIXME(ajeffrey): Convert directly from DOMString to USVString - return Ok(ConversionResult::Success( - USVString(String::from(jsstring_to_str(cx, jsstr))))); + return Ok(ConversionResult::Success(USVString(String::from( + jsstring_to_str(cx, jsstr), + )))); } let mut length = 0; let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr, &mut length); assert!(!chars.is_null()); let char_vec = slice::from_raw_parts(chars as *const u16, length as usize); - Ok(ConversionResult::Success(USVString(String::from_utf16_lossy(char_vec)))) + Ok(ConversionResult::Success(USVString( + String::from_utf16_lossy(char_vec), + ))) } } // http://heycam.github.io/webidl/#es-ByteString impl ToJSValConvertible for ByteString { - unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { - let jsstr = JS_NewStringCopyN(cx, - self.as_ptr() as *const libc::c_char, - self.len() as libc::size_t); + unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) { + let jsstr = JS_NewStringCopyN( + cx, + self.as_ptr() as *const libc::c_char, + self.len() as libc::size_t, + ); if jsstr.is_null() { panic!("JS_NewStringCopyN failed"); } @@ -284,24 +306,27 @@ impl ToJSValConvertible for ByteString { // http://heycam.github.io/webidl/#es-ByteString impl FromJSValConvertible for ByteString { type Config = (); - unsafe fn from_jsval(cx: *mut JSContext, - value: HandleValue, - _option: ()) - -> Result<ConversionResult<ByteString>, ()> { + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + _option: (), + ) -> Result<ConversionResult<ByteString>, ()> { let string = ToString(cx, value); if string.is_null() { debug!("ToString failed"); return Err(()); } - let latin1 = JS_StringHasLatin1Chars(string); + let latin1 = JS_DeprecatedStringHasLatin1Chars(string); if latin1 { let mut length = 0; let chars = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), string, &mut length); assert!(!chars.is_null()); let char_slice = slice::from_raw_parts(chars as *mut u8, length as usize); - return Ok(ConversionResult::Success(ByteString::new(char_slice.to_vec()))); + return Ok(ConversionResult::Success(ByteString::new( + char_slice.to_vec(), + ))); } let mut length = 0; @@ -312,15 +337,15 @@ impl FromJSValConvertible for ByteString { throw_type_error(cx, "Invalid ByteString"); Err(()) } else { - Ok(ConversionResult::Success( - ByteString::new(char_vec.iter().map(|&c| c as u8).collect()))) + Ok(ConversionResult::Success(ByteString::new( + char_vec.iter().map(|&c| c as u8).collect(), + ))) } } } - impl ToJSValConvertible for Reflector { - unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { + unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) { let obj = self.get_jsobject().get(); assert!(!obj.is_null()); rval.set(ObjectValue(obj)); @@ -345,11 +370,12 @@ pub const DOM_OBJECT_SLOT: u32 = 0; /// Get the private pointer of a DOM object from a given reflector. pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void { - let value = if is_dom_object(obj) { - JS_GetReservedSlot(obj, DOM_OBJECT_SLOT) + let mut value = UndefinedValue(); + if is_dom_object(obj) { + JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value); } else { debug_assert!(is_dom_proxy(obj)); - GetProxyPrivate(obj) + GetProxyReservedSlot(obj, 0, &mut value); }; if value.is_undefined() { ptr::null() @@ -360,7 +386,7 @@ pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void { /// Get the `DOMClass` from `obj`, or `Err(())` if `obj` is not a DOM object. pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> { - use dom::bindings::utils::DOMJSClass; + use crate::dom::bindings::utils::DOMJSClass; use js::glue::GetProxyHandlerExtra; let clasp = get_object_class(obj); @@ -378,6 +404,11 @@ pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> Err(()) } +pub(crate) enum PrototypeCheck { + Derive(fn(&'static DOMClass) -> bool), + Depth { depth: usize, proto_id: u16 }, +} + /// Get a `*const libc::c_void` for the given DOM object, unwrapping any /// wrapper around it first, and checking if the object is of the correct type. /// @@ -385,15 +416,15 @@ pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> /// not an object for a DOM object of the given type (as defined by the /// proto_id and proto_depth). #[inline] -pub unsafe fn private_from_proto_check<F>(mut obj: *mut JSObject, - proto_check: F) - -> Result<*const libc::c_void, ()> - where F: Fn(&'static DOMClass) -> bool -{ - let dom_class = try!(get_dom_class(obj).or_else(|_| { +pub(crate) unsafe fn private_from_proto_check( + mut obj: *mut JSObject, + cx: *mut JSContext, + proto_check: PrototypeCheck, +) -> Result<*const libc::c_void, ()> { + let dom_class = get_dom_class(obj).or_else(|_| { if IsWrapper(obj) { trace!("found wrapper"); - obj = UnwrapObject(obj, /* stopAtWindowProxy = */ 0); + obj = UnwrapObjectDynamic(obj, cx, /* stopAtWindowProxy = */ 0); if obj.is_null() { trace!("unwrapping security wrapper failed"); Err(()) @@ -406,8 +437,35 @@ pub unsafe fn private_from_proto_check<F>(mut obj: *mut JSObject, trace!("not a dom wrapper"); Err(()) } - })); + })?; + + let prototype_matches = match proto_check { + PrototypeCheck::Derive(f) => (f)(dom_class), + PrototypeCheck::Depth { depth, proto_id } => { + dom_class.interface_chain[depth] as u16 == proto_id + }, + }; + + if prototype_matches { + trace!("good prototype"); + Ok(private_from_object(obj)) + } else { + trace!("bad prototype"); + Err(()) + } +} +/// Get a `*const libc::c_void` for the given DOM object, unless it is a DOM +/// wrapper, and checking if the object is of the correct type. +/// +/// Returns Err(()) if `obj` is a wrapper or if the object is not an object +/// for a DOM object of the given type (as defined by the proto_id and proto_depth). +#[inline] +unsafe fn private_from_proto_check_static( + obj: *mut JSObject, + proto_check: fn(&'static DOMClass) -> bool, +) -> Result<*const libc::c_void, ()> { + let dom_class = get_dom_class(obj).map_err(|_| ())?; if proto_check(dom_class) { trace!("good prototype"); Ok(private_from_object(obj)) @@ -418,66 +476,189 @@ pub unsafe fn private_from_proto_check<F>(mut obj: *mut JSObject, } /// Get a `*const T` for a DOM object accessible from a `JSObject`. -pub fn native_from_object<T>(obj: *mut JSObject) -> Result<*const T, ()> - where T: DomObject + IDLInterface +pub fn native_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()> +where + T: DomObject + IDLInterface, { unsafe { - private_from_proto_check(obj, T::derives).map(|ptr| ptr as *const T) + private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives)) + .map(|ptr| ptr as *const T) } } -/// Get a `Root<T>` for the given DOM object, unwrapping any wrapper +/// Get a `*const T` for a DOM object accessible from a `JSObject`, where the DOM object +/// is guaranteed not to be a wrapper. +pub fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()> +where + T: DomObject + IDLInterface, +{ + unsafe { private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T) } +} + +/// Get a `DomRoot<T>` for the given DOM object, unwrapping any wrapper +/// around it first, and checking if the object is of the correct type. +/// +/// Returns Err(()) if `obj` is an opaque security wrapper or if the object is +/// not a reflector for a DOM object of the given type (as defined by the +/// proto_id and proto_depth). +pub fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()> +where + T: DomObject + IDLInterface, +{ + native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) }) +} + +/// Get a `DomRoot<T>` for the given DOM object, unwrapping any wrapper /// around it first, and checking if the object is of the correct type. /// /// Returns Err(()) if `obj` is an opaque security wrapper or if the object is /// not a reflector for a DOM object of the given type (as defined by the /// proto_id and proto_depth). -pub fn root_from_object<T>(obj: *mut JSObject) -> Result<Root<T>, ()> - where T: DomObject + IDLInterface +pub fn root_from_object_static<T>(obj: *mut JSObject) -> Result<DomRoot<T>, ()> +where + T: DomObject + IDLInterface, { - native_from_object(obj).map(|ptr| unsafe { Root::from_ref(&*ptr) }) + native_from_object_static(obj).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) }) } /// Get a `*const T` for a DOM object accessible from a `HandleValue`. /// Caller is responsible for throwing a JS exception if needed in case of error. -pub fn native_from_handlevalue<T>(v: HandleValue) -> Result<*const T, ()> - where T: DomObject + IDLInterface +pub fn native_from_handlevalue<T>(v: HandleValue, cx: *mut JSContext) -> Result<*const T, ()> +where + T: DomObject + IDLInterface, { if !v.get().is_object() { return Err(()); } - native_from_object(v.get().to_object()) + native_from_object(v.get().to_object(), cx) } -/// Get a `Root<T>` for a DOM object accessible from a `HandleValue`. +/// Get a `DomRoot<T>` for a DOM object accessible from a `HandleValue`. /// Caller is responsible for throwing a JS exception if needed in case of error. -pub fn root_from_handlevalue<T>(v: HandleValue) -> Result<Root<T>, ()> - where T: DomObject + IDLInterface +pub fn root_from_handlevalue<T>(v: HandleValue, cx: *mut JSContext) -> Result<DomRoot<T>, ()> +where + T: DomObject + IDLInterface, { if !v.get().is_object() { return Err(()); } - root_from_object(v.get().to_object()) + root_from_object(v.get().to_object(), cx) } -/// Get a `Root<T>` for a DOM object accessible from a `HandleObject`. -pub fn root_from_handleobject<T>(obj: HandleObject) -> Result<Root<T>, ()> - where T: DomObject + IDLInterface +/// Get a `DomRoot<T>` for a DOM object accessible from a `HandleObject`. +pub fn root_from_handleobject<T>(obj: HandleObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()> +where + T: DomObject + IDLInterface, { - root_from_object(obj.get()) + root_from_object(obj.get(), cx) } -impl<T: DomObject> ToJSValConvertible for Root<T> { +impl<T: DomObject> ToJSValConvertible for DomRoot<T> { unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { self.reflector().to_jsval(cx, rval); } } -/// Returns whether `value` is an array-like object. -/// Note: Currently only Arrays are supported. -/// TODO: Expand this to support sequences and other array-like objects +/// Returns whether `value` is an array-like object (Array, FileList, +/// HTMLCollection, HTMLFormControlsCollection, HTMLOptionsCollection, +/// NodeList). pub unsafe fn is_array_like(cx: *mut JSContext, value: HandleValue) -> bool { - let mut result = false; - assert!(JS_IsArrayObject(cx, value, &mut result)); - result + let mut is_array = false; + assert!(IsArrayObject(cx, value, &mut is_array)); + if is_array { + return true; + } + + let object: *mut JSObject = match FromJSValConvertible::from_jsval(cx, value, ()).unwrap() { + ConversionResult::Success(object) => object, + _ => return false, + }; + + if root_from_object::<FileList>(object, cx).is_ok() { + return true; + } + if root_from_object::<HTMLCollection>(object, cx).is_ok() { + return true; + } + if root_from_object::<HTMLFormControlsCollection>(object, cx).is_ok() { + return true; + } + if root_from_object::<HTMLOptionsCollection>(object, cx).is_ok() { + return true; + } + if root_from_object::<NodeList>(object, cx).is_ok() { + return true; + } + + false +} + +/// Get a property from a JS object. +pub unsafe fn get_property_jsval( + cx: *mut JSContext, + object: HandleObject, + name: &str, + mut rval: MutableHandleValue, +) -> Fallible<()> { + rval.set(UndefinedValue()); + let cname = match ffi::CString::new(name) { + Ok(cname) => cname, + Err(_) => return Ok(()), + }; + let mut found = false; + if JS_HasProperty(cx, object, cname.as_ptr(), &mut found) && found { + JS_GetProperty(cx, object, cname.as_ptr(), rval); + if JS_IsExceptionPending(cx) { + return Err(Error::JSFailed); + } + Ok(()) + } else if JS_IsExceptionPending(cx) { + Err(Error::JSFailed) + } else { + Ok(()) + } +} + +/// Get a property from a JS object, and convert it to a Rust value. +pub unsafe fn get_property<T>( + cx: *mut JSContext, + object: HandleObject, + name: &str, + option: T::Config, +) -> Fallible<Option<T>> +where + T: FromJSValConvertible, +{ + debug!("Getting property {}.", name); + rooted!(in(cx) let mut result = UndefinedValue()); + get_property_jsval(cx, object, name, result.handle_mut())?; + if result.is_undefined() { + debug!("No property {}.", name); + return Ok(None); + } + debug!("Converting property {}.", name); + match T::from_jsval(cx, result.handle(), option) { + Ok(ConversionResult::Success(value)) => Ok(Some(value)), + Ok(ConversionResult::Failure(_)) => Ok(None), + Err(()) => Err(Error::JSFailed), + } +} + +/// Get a `DomRoot<T>` for a WindowProxy accessible from a `HandleValue`. +/// Caller is responsible for throwing a JS exception if needed in case of error. +pub unsafe fn windowproxy_from_handlevalue( + v: HandleValue, + _cx: *mut JSContext, +) -> Result<DomRoot<WindowProxy>, ()> { + if !v.get().is_object() { + return Err(()); + } + let object = v.get().to_object(); + if !IsWindowProxy(object) { + return Err(()); + } + let mut value = UndefinedValue(); + GetProxyReservedSlot(object, 0, &mut value); + let ptr = value.to_private() as *const WindowProxy; + Ok(DomRoot::from_ref(&*ptr)) } diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index d38a55c31be..79cc8fbd869 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -1,31 +1,48 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Utilities to throw exceptions from Rust bindings. -use dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionMethods; -use dom::bindings::codegen::PrototypeList::proto_id_to_name; -use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible}; -use dom::bindings::conversions::root_from_object; -use dom::bindings::str::USVString; -use dom::domexception::{DOMErrorName, DOMException}; -use dom::globalscope::GlobalScope; +#[cfg(feature = "js_backtrace")] +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::PrototypeList::proto_id_to_name; +use crate::dom::bindings::conversions::root_from_object; +use crate::dom::bindings::conversions::{ + ConversionResult, FromJSValConvertible, ToJSValConvertible, +}; +use crate::dom::bindings::str::USVString; +use crate::dom::domexception::{DOMErrorName, DOMException}; +use crate::dom::globalscope::GlobalScope; +use crate::realms::InRealm; +use crate::script_runtime::JSContext as SafeJSContext; +#[cfg(feature = "js_backtrace")] +use backtrace::Backtrace; use js::error::{throw_range_error, throw_type_error}; -use js::jsapi::HandleObject; +use js::jsapi::ExceptionStackBehavior; use js::jsapi::JSContext; use js::jsapi::JS_ClearPendingException; -use js::jsapi::JS_ErrorFromException; -use js::jsapi::JS_GetPendingException; use js::jsapi::JS_IsExceptionPending; -use js::jsapi::JS_SetPendingException; -use js::jsapi::MutableHandleValue; +#[cfg(feature = "js_backtrace")] +use js::jsapi::StackFormat as JSStackFormat; use js::jsval::UndefinedValue; +use js::rust::wrappers::JS_ErrorFromException; +use js::rust::wrappers::JS_GetPendingException; +use js::rust::wrappers::JS_SetPendingException; +use js::rust::HandleObject; +use js::rust::MutableHandleValue; use libc::c_uint; use std::slice::from_raw_parts; +#[cfg(feature = "js_backtrace")] +thread_local! { + /// An optional stringified JS backtrace and stringified native backtrace from the + /// the last DOM exception that was reported. + static LAST_EXCEPTION_BACKTRACE: DomRefCell<Option<(Option<String>, String)>> = DomRefCell::new(None); +} + /// DOM exceptions that can be thrown by a native DOM method. -#[derive(Debug, Clone, HeapSizeOf)] +#[derive(Clone, Debug, MallocSizeOf)] pub enum Error { /// IndexSizeError DOMException IndexSize, @@ -69,6 +86,10 @@ pub enum Error { TypeMismatch, /// InvalidModificationError DOMException InvalidModification, + /// NotReadableError DOMException + NotReadable, + /// OperationError DOMException + Operation, /// TypeError JavaScript Error Type(String), @@ -87,7 +108,17 @@ pub type Fallible<T> = Result<T, Error>; pub type ErrorResult = Fallible<()>; /// Set a pending exception for the given `result` on `cx`. -pub unsafe fn throw_dom_exception(cx: *mut JSContext, global: &GlobalScope, result: Error) { +pub fn throw_dom_exception(cx: SafeJSContext, global: &GlobalScope, result: Error) { + #[cfg(feature = "js_backtrace")] + unsafe { + capture_stack!(in(*cx) let stack); + let js_stack = stack.and_then(|s| s.as_string(None, JSStackFormat::Default)); + let rust_stack = Backtrace::new(); + LAST_EXCEPTION_BACKTRACE.with(|backtrace| { + *backtrace.borrow_mut() = Some((js_stack, format!("{:?}", rust_stack))); + }); + } + let code = match result { Error::IndexSize => DOMErrorName::IndexSizeError, Error::NotFound => DOMErrorName::NotFoundError, @@ -110,27 +141,31 @@ pub unsafe fn throw_dom_exception(cx: *mut JSContext, global: &GlobalScope, resu Error::QuotaExceeded => DOMErrorName::QuotaExceededError, Error::TypeMismatch => DOMErrorName::TypeMismatchError, Error::InvalidModification => DOMErrorName::InvalidModificationError, - Error::Type(message) => { - assert!(!JS_IsExceptionPending(cx)); - throw_type_error(cx, &message); + Error::NotReadable => DOMErrorName::NotReadableError, + Error::Operation => DOMErrorName::OperationError, + Error::Type(message) => unsafe { + assert!(!JS_IsExceptionPending(*cx)); + throw_type_error(*cx, &message); return; }, - Error::Range(message) => { - assert!(!JS_IsExceptionPending(cx)); - throw_range_error(cx, &message); + Error::Range(message) => unsafe { + assert!(!JS_IsExceptionPending(*cx)); + throw_range_error(*cx, &message); return; }, - Error::JSFailed => { - assert!(JS_IsExceptionPending(cx)); + Error::JSFailed => unsafe { + assert!(JS_IsExceptionPending(*cx)); return; - } + }, }; - assert!(!JS_IsExceptionPending(cx)); - let exception = DOMException::new(global, code); - rooted!(in(cx) let mut thrown = UndefinedValue()); - exception.to_jsval(cx, thrown.handle_mut()); - JS_SetPendingException(cx, thrown.handle()); + unsafe { + assert!(!JS_IsExceptionPending(*cx)); + let exception = DOMException::new(global, code); + rooted!(in(*cx) let mut thrown = UndefinedValue()); + exception.to_jsval(*cx, thrown.handle_mut()); + JS_SetPendingException(*cx, thrown.handle(), ExceptionStackBehavior::Capture); + } } /// A struct encapsulating information about a runtime script error. @@ -146,15 +181,14 @@ pub struct ErrorInfo { } impl ErrorInfo { - unsafe fn from_native_error(cx: *mut JSContext, object: HandleObject) - -> Option<ErrorInfo> { + unsafe fn from_native_error(cx: *mut JSContext, object: HandleObject) -> Option<ErrorInfo> { let report = JS_ErrorFromException(cx, object); if report.is_null() { return None; } let filename = { - let filename = (*report).filename as *const u8; + let filename = (*report)._base.filename as *const u8; if !filename.is_null() { let length = (0..).find(|idx| *filename.offset(*idx) == 0).unwrap(); let filename = from_raw_parts(filename, length as usize); @@ -164,14 +198,14 @@ impl ErrorInfo { } }; - let lineno = (*report).lineno; - let column = (*report).column; + let lineno = (*report)._base.lineno; + let column = (*report)._base.column; let message = { - let message = (*report).ucmessage; + let message = (*report)._base.message_.data_ as *const u8; let length = (0..).find(|idx| *message.offset(*idx) == 0).unwrap(); let message = from_raw_parts(message, length as usize); - String::from_utf16_lossy(message) + String::from_utf8_lossy(message).into_owned() }; Some(ErrorInfo { @@ -182,15 +216,15 @@ impl ErrorInfo { }) } - fn from_dom_exception(object: HandleObject) -> Option<ErrorInfo> { - let exception = match root_from_object::<DOMException>(object.get()) { + fn from_dom_exception(object: HandleObject, cx: *mut JSContext) -> Option<ErrorInfo> { + let exception = match root_from_object::<DOMException>(object.get(), cx) { Ok(exception) => exception, Err(_) => return None, }; Some(ErrorInfo { filename: "".to_string(), - message: exception.Stringifier().into(), + message: exception.stringifier().into(), lineno: 0, column: 0, }) @@ -201,8 +235,10 @@ impl ErrorInfo { /// /// The `dispatch_event` argument is temporary and non-standard; passing false /// prevents dispatching the `error` event. -pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool) { - if !JS_IsExceptionPending(cx) { return; } +pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool, realm: InRealm) { + if !JS_IsExceptionPending(cx) { + return; + } rooted!(in(cx) let mut value = UndefinedValue()); if !JS_GetPendingException(cx, value.handle_mut()) { @@ -215,24 +251,20 @@ pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool) let error_info = if value.is_object() { rooted!(in(cx) let object = value.to_object()); ErrorInfo::from_native_error(cx, object.handle()) - .or_else(|| ErrorInfo::from_dom_exception(object.handle())) - .unwrap_or_else(|| { - ErrorInfo { - message: format!("uncaught exception: unknown (can't convert to string)"), - filename: String::new(), - lineno: 0, - column: 0, - } + .or_else(|| ErrorInfo::from_dom_exception(object.handle(), cx)) + .unwrap_or_else(|| ErrorInfo { + message: format!("uncaught exception: unknown (can't convert to string)"), + filename: String::new(), + lineno: 0, + column: 0, }) } else { match USVString::from_jsval(cx, value.handle(), ()) { - Ok(ConversionResult::Success(USVString(string))) => { - ErrorInfo { - message: format!("uncaught exception: {}", string), - filename: String::new(), - lineno: 0, - column: 0, - } + Ok(ConversionResult::Success(USVString(string))) => ErrorInfo { + message: format!("uncaught exception: {}", string), + filename: String::new(), + lineno: 0, + column: 0, }, _ => { panic!("Uncaught exception: failed to stringify primitive"); @@ -240,40 +272,51 @@ pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool) } }; - error!("Error at {}:{}:{} {}", - error_info.filename, - error_info.lineno, - error_info.column, - error_info.message); + error!( + "Error at {}:{}:{} {}", + error_info.filename, error_info.lineno, error_info.column, error_info.message + ); + #[cfg(feature = "js_backtrace")] + { + LAST_EXCEPTION_BACKTRACE.with(|backtrace| { + if let Some((js_backtrace, rust_backtrace)) = backtrace.borrow_mut().take() { + if let Some(stack) = js_backtrace { + eprintln!("JS backtrace:\n{}", stack); + } + eprintln!("Rust backtrace:\n{}", rust_backtrace); + } + }); + } if dispatch_event { - GlobalScope::from_context(cx) - .report_an_error(error_info, value.handle()); + GlobalScope::from_context(cx, realm).report_an_error(error_info, value.handle()); } } -/// Throw an exception to signal that a `JSVal` can not be converted to any of -/// the types in an IDL union type. -pub unsafe fn throw_not_in_union(cx: *mut JSContext, names: &'static str) { - assert!(!JS_IsExceptionPending(cx)); - let error = format!("argument could not be converted to any of: {}", names); - throw_type_error(cx, &error); -} - /// Throw an exception to signal that a `JSObject` can not be converted to a /// given DOM type. pub unsafe fn throw_invalid_this(cx: *mut JSContext, proto_id: u16) { debug_assert!(!JS_IsExceptionPending(cx)); - let error = format!("\"this\" object does not implement interface {}.", - proto_id_to_name(proto_id)); + let error = format!( + "\"this\" object does not implement interface {}.", + proto_id_to_name(proto_id) + ); throw_type_error(cx, &error); } impl Error { /// Convert this error value to a JS value, consuming it in the process. - pub unsafe fn to_jsval(self, cx: *mut JSContext, global: &GlobalScope, rval: MutableHandleValue) { - assert!(!JS_IsExceptionPending(cx)); - throw_dom_exception(cx, global, self); + pub unsafe fn to_jsval( + self, + cx: *mut JSContext, + global: &GlobalScope, + rval: MutableHandleValue, + ) { + match self { + Error::JSFailed => (), + _ => assert!(!JS_IsExceptionPending(cx)), + } + throw_dom_exception(SafeJSContext::from_ptr(cx), global, self); assert!(JS_IsExceptionPending(cx)); assert!(JS_GetPendingException(cx, rval)); JS_ClearPendingException(cx); diff --git a/components/script/dom/bindings/guard.rs b/components/script/dom/bindings/guard.rs index 25a52fbe84c..17f3a3d20bc 100644 --- a/components/script/dom/bindings/guard.rs +++ b/components/script/dom/bindings/guard.rs @@ -1,11 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Machinery to conditionally expose things. -use js::jsapi::{HandleObject, JSContext}; -use servo_config::prefs::PREFS; +use crate::dom::bindings::codegen::InterfaceObjectMap; +use crate::dom::bindings::interface::is_exposed_in; +use crate::dom::globalscope::GlobalScope; +use crate::realms::AlreadyInRealm; +use crate::realms::InRealm; +use crate::script_runtime::JSContext; +use js::rust::HandleObject; +use servo_config::prefs; /// A container with a condition. pub struct Guard<T: Clone + Copy> { @@ -25,8 +31,8 @@ impl<T: Clone + Copy> Guard<T> { /// Expose the value if the condition is satisfied. /// /// The passed handle is the object on which the value may be exposed. - pub unsafe fn expose(&self, cx: *mut JSContext, obj: HandleObject) -> Option<T> { - if self.condition.is_satisfied(cx, obj) { + pub fn expose(&self, cx: JSContext, obj: HandleObject, global: HandleObject) -> Option<T> { + if self.condition.is_satisfied(cx, obj, global) { Some(self.value) } else { None @@ -37,18 +43,30 @@ impl<T: Clone + Copy> Guard<T> { /// A condition to expose things. pub enum Condition { /// The condition is satisfied if the function returns true. - Func(unsafe fn(*mut JSContext, HandleObject) -> bool), + Func(fn(JSContext, HandleObject) -> bool), /// The condition is satisfied if the preference is set. Pref(&'static str), + // The condition is satisfied if the interface is exposed in the global. + Exposed(InterfaceObjectMap::Globals), + SecureContext(), /// The condition is always satisfied. Satisfied, } +fn is_secure_context(cx: JSContext) -> bool { + unsafe { + let in_realm_proof = AlreadyInRealm::assert_for_cx(JSContext::from_ptr(*cx)); + GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)).is_secure_context() + } +} + impl Condition { - unsafe fn is_satisfied(&self, cx: *mut JSContext, obj: HandleObject) -> bool { + pub fn is_satisfied(&self, cx: JSContext, obj: HandleObject, global: HandleObject) -> bool { match *self { - Condition::Pref(name) => PREFS.get(name).as_boolean().unwrap_or(false), + Condition::Pref(name) => prefs::pref_map().get(name).as_bool().unwrap_or(false), Condition::Func(f) => f(cx, obj), + Condition::Exposed(globals) => is_exposed_in(global, globals), + Condition::SecureContext() => is_secure_context(cx), Condition::Satisfied => true, } } diff --git a/components/script/dom/bindings/htmlconstructor.rs b/components/script/dom/bindings/htmlconstructor.rs new file mode 100644 index 00000000000..76d717ff4b7 --- /dev/null +++ b/components/script/dom/bindings/htmlconstructor.rs @@ -0,0 +1,460 @@ +/* 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 crate::dom::bindings::codegen::Bindings::HTMLAnchorElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLAreaElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLAudioElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLBRElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLBaseElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLBodyElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLDListElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLDataElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLDataListElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLDetailsElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLDialogElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLDirectoryElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLDivElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLEmbedElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLFontElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLFrameElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLFrameSetElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLHRElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLHeadElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLHeadingElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLHtmlElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLLIElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLLegendElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLLinkElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLMapElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLMenuElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLMetaElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLMeterElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLModElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLOListElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLObjectElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLOutputElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLParagraphElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLParamElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLPictureElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLPreElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLProgressElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLQuoteElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLSourceElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLSpanElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLStyleElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTableCaptionElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTableCellElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTableColElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTableElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTableRowElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTimeElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTitleElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLTrackElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLUListElementBinding; +use crate::dom::bindings::codegen::Bindings::HTMLVideoElementBinding; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::conversions::DerivedFrom; +use crate::dom::bindings::error::{throw_dom_exception, Error}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::DomRoot; +use crate::dom::create::create_native_html_element; +use crate::dom::customelementregistry::{ConstructionStackEntry, CustomElementState}; +use crate::dom::element::{Element, ElementCreator}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::window::Window; +use crate::script_runtime::JSContext; +use crate::script_thread::ScriptThread; +use html5ever::interface::QualName; +use html5ever::LocalName; +use js::conversions::ToJSValConvertible; +use js::glue::{UnwrapObjectDynamic, UnwrapObjectStatic}; +use js::jsapi::{CallArgs, CurrentGlobalOrNull}; +use js::jsapi::{JSAutoRealm, JSObject}; +use js::jsval::UndefinedValue; +use js::rust::wrappers::{JS_GetProperty, JS_SetPrototype, JS_WrapObject}; +use js::rust::{HandleObject, MutableHandleObject, MutableHandleValue}; +use std::ptr; + +// https://html.spec.whatwg.org/multipage/#htmlconstructor +unsafe fn html_constructor( + cx: JSContext, + window: &Window, + call_args: &CallArgs, + check_type: fn(&Element) -> bool, + get_proto_object: fn(JSContext, HandleObject, MutableHandleObject), +) -> Result<(), ()> { + let document = window.Document(); + let global = window.upcast::<GlobalScope>(); + + // Step 1 + let registry = window.CustomElements(); + + // Step 2 https://html.spec.whatwg.org/multipage/#htmlconstructor + // The custom element definition cannot use an element interface as its constructor + + // The new_target might be a cross-compartment wrapper. Get the underlying object + // so we can do the spec's object-identity checks. + rooted!(in(*cx) let new_target_unwrapped = UnwrapObjectDynamic(call_args.new_target().to_object(), *cx, 1)); + if new_target_unwrapped.is_null() { + throw_dom_exception(cx, global, Error::Type("new.target is null".to_owned())); + return Err(()); + } + if call_args.callee() == new_target_unwrapped.get() { + throw_dom_exception( + cx, + global, + Error::Type("new.target must not be the active function object".to_owned()), + ); + return Err(()); + } + + // Step 3 + rooted!(in(*cx) let new_target = call_args.new_target().to_object()); + let definition = match registry.lookup_definition_by_constructor(new_target.handle()) { + Some(definition) => definition, + None => { + throw_dom_exception( + cx, + global, + Error::Type("No custom element definition found for new.target".to_owned()), + ); + return Err(()); + }, + }; + + rooted!(in(*cx) let callee = UnwrapObjectStatic(call_args.callee())); + if callee.is_null() { + throw_dom_exception(cx, global, Error::Security); + return Err(()); + } + + { + let _ac = JSAutoRealm::new(*cx, callee.get()); + rooted!(in(*cx) let mut constructor = ptr::null_mut::<JSObject>()); + rooted!(in(*cx) let global_object = CurrentGlobalOrNull(*cx)); + + if definition.is_autonomous() { + // Step 4 + // Since this element is autonomous, its active function object must be the HTMLElement + + // Retrieve the constructor object for HTMLElement + HTMLElementBinding::GetConstructorObject( + cx, + global_object.handle(), + constructor.handle_mut(), + ); + } else { + // Step 5 + get_constructor_object_from_local_name( + definition.local_name.clone(), + cx, + global_object.handle(), + constructor.handle_mut(), + ); + } + // Callee must be the same as the element interface's constructor object. + if constructor.get() != callee.get() { + throw_dom_exception( + cx, + global, + Error::Type("Custom element does not extend the proper interface".to_owned()), + ); + return Err(()); + } + } + + // Step 6 + rooted!(in(*cx) let mut prototype = ptr::null_mut::<JSObject>()); + { + rooted!(in(*cx) let mut proto_val = UndefinedValue()); + let _ac = JSAutoRealm::new(*cx, new_target_unwrapped.get()); + if !JS_GetProperty( + *cx, + new_target_unwrapped.handle(), + b"prototype\0".as_ptr() as *const _, + proto_val.handle_mut(), + ) { + return Err(()); + } + + if !proto_val.is_object() { + // Step 7 of https://html.spec.whatwg.org/multipage/#htmlconstructor. + // This fallback behavior is designed to match analogous behavior for the + // JavaScript built-ins. So we enter the realm of our underlying + // newTarget object and fall back to the prototype object from that global. + // XXX The spec says to use GetFunctionRealm(), which is not actually + // the same thing as what we have here (e.g. in the case of scripted callable proxies + // whose target is not same-realm with the proxy, or bound functions, etc). + // https://bugzilla.mozilla.org/show_bug.cgi?id=1317658 + + rooted!(in(*cx) let global_object = CurrentGlobalOrNull(*cx)); + get_proto_object(cx, global_object.handle(), prototype.handle_mut()); + } else { + // Step 6 + prototype.set(proto_val.to_object()); + } + } + + // Wrap prototype in this context since it is from the newTarget realm + if !JS_WrapObject(*cx, prototype.handle_mut()) { + return Err(()); + } + + let entry = definition.construction_stack.borrow().last().cloned(); + let result = match entry { + // Step 8 + None => { + // Step 8.1 + let name = QualName::new(None, ns!(html), definition.local_name.clone()); + let element = if definition.is_autonomous() { + DomRoot::upcast(HTMLElement::new(name.local, None, &*document)) + } else { + create_native_html_element(name, None, &*document, ElementCreator::ScriptCreated) + }; + + // Step 8.2 is performed in the generated caller code. + + // Step 8.3 + element.set_custom_element_state(CustomElementState::Custom); + + // Step 8.4 + element.set_custom_element_definition(definition.clone()); + + // Step 8.5 + if !check_type(&*element) { + throw_dom_exception(cx, global, Error::InvalidState); + return Err(()); + } else { + element + } + }, + // Step 9 + Some(ConstructionStackEntry::Element(element)) => { + // Step 11 is performed in the generated caller code. + + // Step 12 + let mut construction_stack = definition.construction_stack.borrow_mut(); + construction_stack.pop(); + construction_stack.push(ConstructionStackEntry::AlreadyConstructedMarker); + + // Step 13 + if !check_type(&*element) { + throw_dom_exception(cx, global, Error::InvalidState); + return Err(()); + } else { + element + } + }, + // Step 10 + Some(ConstructionStackEntry::AlreadyConstructedMarker) => { + let s = "Top of construction stack marked AlreadyConstructed due to \ + a custom element constructor constructing itself after super()" + .to_string(); + throw_dom_exception(cx, global, Error::Type(s)); + return Err(()); + }, + }; + + rooted!(in(*cx) let mut element = result.reflector().get_jsobject().get()); + if !JS_WrapObject(*cx, element.handle_mut()) { + return Err(()); + } + + JS_SetPrototype(*cx, element.handle(), prototype.handle()); + + result.to_jsval(*cx, MutableHandleValue::from_raw(call_args.rval())); + Ok(()) +} + +/// Returns the constructor object for the element associated with the given local name. +/// This list should only include elements marked with the [HTMLConstructor] extended attribute. +pub fn get_constructor_object_from_local_name( + name: LocalName, + cx: JSContext, + global: HandleObject, + rval: MutableHandleObject, +) -> bool { + macro_rules! get_constructor( + ($binding:ident) => ({ + $binding::GetConstructorObject(cx, global, rval); + true + }) + ); + + match name { + local_name!("a") => get_constructor!(HTMLAnchorElementBinding), + local_name!("abbr") => get_constructor!(HTMLElementBinding), + local_name!("acronym") => get_constructor!(HTMLElementBinding), + local_name!("address") => get_constructor!(HTMLElementBinding), + local_name!("area") => get_constructor!(HTMLAreaElementBinding), + local_name!("article") => get_constructor!(HTMLElementBinding), + local_name!("aside") => get_constructor!(HTMLElementBinding), + local_name!("audio") => get_constructor!(HTMLAudioElementBinding), + local_name!("b") => get_constructor!(HTMLElementBinding), + local_name!("base") => get_constructor!(HTMLBaseElementBinding), + local_name!("bdi") => get_constructor!(HTMLElementBinding), + local_name!("bdo") => get_constructor!(HTMLElementBinding), + local_name!("big") => get_constructor!(HTMLElementBinding), + local_name!("blockquote") => get_constructor!(HTMLQuoteElementBinding), + local_name!("body") => get_constructor!(HTMLBodyElementBinding), + local_name!("br") => get_constructor!(HTMLBRElementBinding), + local_name!("button") => get_constructor!(HTMLButtonElementBinding), + local_name!("canvas") => get_constructor!(HTMLCanvasElementBinding), + local_name!("caption") => get_constructor!(HTMLTableCaptionElementBinding), + local_name!("center") => get_constructor!(HTMLElementBinding), + local_name!("cite") => get_constructor!(HTMLElementBinding), + local_name!("code") => get_constructor!(HTMLElementBinding), + local_name!("col") => get_constructor!(HTMLTableColElementBinding), + local_name!("colgroup") => get_constructor!(HTMLTableColElementBinding), + local_name!("data") => get_constructor!(HTMLDataElementBinding), + local_name!("datalist") => get_constructor!(HTMLDataListElementBinding), + local_name!("dd") => get_constructor!(HTMLElementBinding), + local_name!("del") => get_constructor!(HTMLModElementBinding), + local_name!("details") => get_constructor!(HTMLDetailsElementBinding), + local_name!("dfn") => get_constructor!(HTMLElementBinding), + local_name!("dialog") => get_constructor!(HTMLDialogElementBinding), + local_name!("dir") => get_constructor!(HTMLDirectoryElementBinding), + local_name!("div") => get_constructor!(HTMLDivElementBinding), + local_name!("dl") => get_constructor!(HTMLDListElementBinding), + local_name!("dt") => get_constructor!(HTMLElementBinding), + local_name!("em") => get_constructor!(HTMLElementBinding), + local_name!("embed") => get_constructor!(HTMLEmbedElementBinding), + local_name!("fieldset") => get_constructor!(HTMLFieldSetElementBinding), + local_name!("figcaption") => get_constructor!(HTMLElementBinding), + local_name!("figure") => get_constructor!(HTMLElementBinding), + local_name!("font") => get_constructor!(HTMLFontElementBinding), + local_name!("footer") => get_constructor!(HTMLElementBinding), + local_name!("form") => get_constructor!(HTMLFormElementBinding), + local_name!("frame") => get_constructor!(HTMLFrameElementBinding), + local_name!("frameset") => get_constructor!(HTMLFrameSetElementBinding), + local_name!("h1") => get_constructor!(HTMLHeadingElementBinding), + local_name!("h2") => get_constructor!(HTMLHeadingElementBinding), + local_name!("h3") => get_constructor!(HTMLHeadingElementBinding), + local_name!("h4") => get_constructor!(HTMLHeadingElementBinding), + local_name!("h5") => get_constructor!(HTMLHeadingElementBinding), + local_name!("h6") => get_constructor!(HTMLHeadingElementBinding), + local_name!("head") => get_constructor!(HTMLHeadElementBinding), + local_name!("header") => get_constructor!(HTMLElementBinding), + local_name!("hgroup") => get_constructor!(HTMLElementBinding), + local_name!("hr") => get_constructor!(HTMLHRElementBinding), + local_name!("html") => get_constructor!(HTMLHtmlElementBinding), + local_name!("i") => get_constructor!(HTMLElementBinding), + local_name!("iframe") => get_constructor!(HTMLIFrameElementBinding), + local_name!("img") => get_constructor!(HTMLImageElementBinding), + local_name!("input") => get_constructor!(HTMLInputElementBinding), + local_name!("ins") => get_constructor!(HTMLModElementBinding), + local_name!("kbd") => get_constructor!(HTMLElementBinding), + local_name!("label") => get_constructor!(HTMLLabelElementBinding), + local_name!("legend") => get_constructor!(HTMLLegendElementBinding), + local_name!("li") => get_constructor!(HTMLLIElementBinding), + local_name!("link") => get_constructor!(HTMLLinkElementBinding), + local_name!("listing") => get_constructor!(HTMLPreElementBinding), + local_name!("main") => get_constructor!(HTMLElementBinding), + local_name!("map") => get_constructor!(HTMLMapElementBinding), + local_name!("mark") => get_constructor!(HTMLElementBinding), + local_name!("marquee") => get_constructor!(HTMLElementBinding), + local_name!("menu") => get_constructor!(HTMLMenuElementBinding), + local_name!("meta") => get_constructor!(HTMLMetaElementBinding), + local_name!("meter") => get_constructor!(HTMLMeterElementBinding), + local_name!("nav") => get_constructor!(HTMLElementBinding), + local_name!("nobr") => get_constructor!(HTMLElementBinding), + local_name!("noframes") => get_constructor!(HTMLElementBinding), + local_name!("noscript") => get_constructor!(HTMLElementBinding), + local_name!("object") => get_constructor!(HTMLObjectElementBinding), + local_name!("ol") => get_constructor!(HTMLOListElementBinding), + local_name!("optgroup") => get_constructor!(HTMLOptGroupElementBinding), + local_name!("option") => get_constructor!(HTMLOptionElementBinding), + local_name!("output") => get_constructor!(HTMLOutputElementBinding), + local_name!("p") => get_constructor!(HTMLParagraphElementBinding), + local_name!("param") => get_constructor!(HTMLParamElementBinding), + local_name!("picture") => get_constructor!(HTMLPictureElementBinding), + local_name!("plaintext") => get_constructor!(HTMLPreElementBinding), + local_name!("pre") => get_constructor!(HTMLPreElementBinding), + local_name!("progress") => get_constructor!(HTMLProgressElementBinding), + local_name!("q") => get_constructor!(HTMLQuoteElementBinding), + local_name!("rp") => get_constructor!(HTMLElementBinding), + local_name!("rt") => get_constructor!(HTMLElementBinding), + local_name!("ruby") => get_constructor!(HTMLElementBinding), + local_name!("s") => get_constructor!(HTMLElementBinding), + local_name!("samp") => get_constructor!(HTMLElementBinding), + local_name!("script") => get_constructor!(HTMLScriptElementBinding), + local_name!("section") => get_constructor!(HTMLElementBinding), + local_name!("select") => get_constructor!(HTMLSelectElementBinding), + local_name!("small") => get_constructor!(HTMLElementBinding), + local_name!("source") => get_constructor!(HTMLSourceElementBinding), + local_name!("span") => get_constructor!(HTMLSpanElementBinding), + local_name!("strike") => get_constructor!(HTMLElementBinding), + local_name!("strong") => get_constructor!(HTMLElementBinding), + local_name!("style") => get_constructor!(HTMLStyleElementBinding), + local_name!("sub") => get_constructor!(HTMLElementBinding), + local_name!("summary") => get_constructor!(HTMLElementBinding), + local_name!("sup") => get_constructor!(HTMLElementBinding), + local_name!("table") => get_constructor!(HTMLTableElementBinding), + local_name!("tbody") => get_constructor!(HTMLTableSectionElementBinding), + local_name!("td") => get_constructor!(HTMLTableCellElementBinding), + local_name!("template") => get_constructor!(HTMLTemplateElementBinding), + local_name!("textarea") => get_constructor!(HTMLTextAreaElementBinding), + local_name!("tfoot") => get_constructor!(HTMLTableSectionElementBinding), + local_name!("th") => get_constructor!(HTMLTableCellElementBinding), + local_name!("thead") => get_constructor!(HTMLTableSectionElementBinding), + local_name!("time") => get_constructor!(HTMLTimeElementBinding), + local_name!("title") => get_constructor!(HTMLTitleElementBinding), + local_name!("tr") => get_constructor!(HTMLTableRowElementBinding), + local_name!("tt") => get_constructor!(HTMLElementBinding), + local_name!("track") => get_constructor!(HTMLTrackElementBinding), + local_name!("u") => get_constructor!(HTMLElementBinding), + local_name!("ul") => get_constructor!(HTMLUListElementBinding), + local_name!("var") => get_constructor!(HTMLElementBinding), + local_name!("video") => get_constructor!(HTMLVideoElementBinding), + local_name!("wbr") => get_constructor!(HTMLElementBinding), + local_name!("xmp") => get_constructor!(HTMLPreElementBinding), + _ => false, + } +} + +pub fn pop_current_element_queue() { + ScriptThread::pop_current_element_queue(); +} + +pub fn push_new_element_queue() { + ScriptThread::push_new_element_queue(); +} + +pub(crate) unsafe fn call_html_constructor<T: DerivedFrom<Element> + DomObject>( + cx: JSContext, + args: &CallArgs, + global: &Window, + get_proto_object: fn(JSContext, HandleObject, MutableHandleObject), +) -> bool { + fn element_derives_interface<T: DerivedFrom<Element>>(element: &Element) -> bool { + element.is::<T>() + } + + html_constructor( + cx, + global, + args, + element_derives_interface::<T>, + get_proto_object, + ) + .is_ok() +} diff --git a/components/script/dom/bindings/inheritance.rs b/components/script/dom/bindings/inheritance.rs index d97843bf42a..b9091221221 100644 --- a/components/script/dom/bindings/inheritance.rs +++ b/components/script/dom/bindings/inheritance.rs @@ -1,14 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The `Castable` trait. -pub use dom::bindings::codegen::InheritTypes::*; +pub use crate::dom::bindings::codegen::InheritTypes::*; -use dom::bindings::conversions::{DerivedFrom, IDLInterface}; -use dom::bindings::conversions::get_dom_class; -use dom::bindings::reflector::DomObject; +use crate::dom::bindings::conversions::get_dom_class; +use crate::dom::bindings::conversions::{DerivedFrom, IDLInterface}; +use crate::dom::bindings::reflector::DomObject; use std::mem; /// A trait to hold the cast functions of IDL interfaces that either derive @@ -16,7 +16,8 @@ use std::mem; pub trait Castable: IDLInterface + DomObject + Sized { /// Check whether a DOM object implements one of its deriving interfaces. fn is<T>(&self) -> bool - where T: DerivedFrom<Self> + where + T: DerivedFrom<Self>, { let class = unsafe { get_dom_class(self.reflector().get_jsobject().get()).unwrap() }; T::derives(class) @@ -24,15 +25,17 @@ pub trait Castable: IDLInterface + DomObject + Sized { /// Cast a DOM object upwards to one of the interfaces it derives from. fn upcast<T>(&self) -> &T - where T: Castable, - Self: DerivedFrom<T> + where + T: Castable, + Self: DerivedFrom<T>, { unsafe { mem::transmute(self) } } /// Cast a DOM object downwards to one of the interfaces it might implement. fn downcast<T>(&self) -> Option<&T> - where T: DerivedFrom<Self> + where + T: DerivedFrom<Self>, { if self.is::<T>() { Some(unsafe { mem::transmute(self) }) @@ -41,3 +44,8 @@ pub trait Castable: IDLInterface + DomObject + Sized { } } } + +pub trait HasParent { + type Parent; + fn as_parent(&self) -> &Self::Parent; +} diff --git a/components/script/dom/bindings/interface.rs b/components/script/dom/bindings/interface.rs index 3138c00cb89..e982a001fb0 100644 --- a/components/script/dom/bindings/interface.rs +++ b/components/script/dom/bindings/interface.rs @@ -1,42 +1,52 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Machinery to initialise interface prototype objects and interface objects. -use dom::bindings::codegen::InterfaceObjectMap::Globals; -use dom::bindings::codegen::PrototypeList; -use dom::bindings::constant::{ConstantSpec, define_constants}; -use dom::bindings::conversions::{DOM_OBJECT_SLOT, get_dom_class}; -use dom::bindings::guard::Guard; -use dom::bindings::utils::{DOM_PROTOTYPE_SLOT, ProtoOrIfaceArray, get_proto_or_iface_array}; +use crate::dom::bindings::codegen::InterfaceObjectMap::Globals; +use crate::dom::bindings::codegen::PrototypeList; +use crate::dom::bindings::constant::{define_constants, ConstantSpec}; +use crate::dom::bindings::conversions::{get_dom_class, DOM_OBJECT_SLOT}; +use crate::dom::bindings::guard::Guard; +use crate::dom::bindings::utils::{ProtoOrIfaceArray, DOM_PROTOTYPE_SLOT}; +use crate::script_runtime::JSContext as SafeJSContext; use dom::window::Window; use js::error::throw_type_error; -use js::glue::{CreateRustJSPrincipal, RUST_SYMBOL_TO_JSID, UncheckedUnwrapObject}; -use js::jsapi::{Class, ClassOps, CompartmentOptions, GetGlobalForObjectCrossCompartment}; -use js::jsapi::{GetWellKnownSymbol, HandleObject, HandleValue, JSAutoCompartment}; -use js::jsapi::{JSClass, JSContext, JSFUN_CONSTRUCTOR, JSFunctionSpec, JSObject}; -use js::jsapi::{JSPROP_PERMANENT, JSPROP_READONLY, JSPROP_RESOLVING}; -use js::jsapi::{JSPropertySpec, JSString, JSTracer, JSVersion, JS_AtomizeAndPinString}; -use js::jsapi::{JS_DefineProperty, JS_DefineProperty1, JS_DefineProperty2}; -use js::jsapi::{JS_DefineProperty4, JS_DefinePropertyById3, JS_FireOnNewGlobalObject}; -use js::jsapi::{JS_GetFunctionObject, JS_GetPrototype}; -use js::jsapi::{JS_LinkConstructorAndPrototype, JS_NewFunction, JS_NewGlobalObject}; -use js::jsapi::{JS_NewObject, JS_NewObjectWithUniqueType, JS_NewPlainObject}; -use js::jsapi::{JS_NewStringCopyN, JS_SetReservedSlot, MutableHandleObject}; -use js::jsapi::{MutableHandleValue, ObjectOps, OnNewGlobalHookOption, SymbolCode}; +use js::glue::UncheckedUnwrapObject; +use js::jsapi::GetWellKnownSymbol; +use js::jsapi::HandleObject as RawHandleObject; +use js::jsapi::{jsid, JSClass, JSClassOps}; +use js::jsapi::{ + Compartment, CompartmentSpecifier, IsSharableCompartment, IsSystemCompartment, + JS_IterateCompartments, JS::CompartmentIterResult, +}; +use js::jsapi::{JSAutoRealm, JSContext, JSFunctionSpec, JSObject, JSFUN_CONSTRUCTOR}; +use js::jsapi::{JSPropertySpec, JSString, JSTracer, JS_AtomizeAndPinString}; +use js::jsapi::{JS_GetFunctionObject, JS_NewFunction, JS_NewGlobalObject}; +use js::jsapi::{JS_NewObject, JS_NewPlainObject}; +use js::jsapi::{JS_NewStringCopyN, JS_SetReservedSlot}; +use js::jsapi::{ObjectOps, OnNewGlobalHookOption, SymbolCode}; use js::jsapi::{TrueHandleValue, Value}; +use js::jsapi::{JSPROP_PERMANENT, JSPROP_READONLY, JSPROP_RESOLVING}; use js::jsval::{JSVal, PrivateValue}; +use js::rust::wrappers::JS_FireOnNewGlobalObject; +use js::rust::wrappers::RUST_SYMBOL_TO_JSID; +use js::rust::wrappers::{JS_DefineProperty, JS_DefineProperty5}; +use js::rust::wrappers::{JS_DefineProperty3, JS_DefineProperty4, JS_DefinePropertyById5}; +use js::rust::wrappers::{JS_LinkConstructorAndPrototype, JS_NewObjectWithGivenProto}; use js::rust::{define_methods, define_properties, get_object_class}; +use js::rust::{HandleObject, HandleValue, MutableHandleObject, RealmOptions}; use libc; use servo_url::MutableOrigin; +use std::convert::TryFrom; use std::ptr; /// The class of a non-callback interface object. -#[derive(Copy, Clone)] +#[derive(Clone, Copy)] pub struct NonCallbackInterfaceObjectClass { - /// The SpiderMonkey Class structure. - pub class: Class, + /// The SpiderMonkey class structure. + pub class: JSClass, /// The prototype id of that interface, used in the hasInstance hook. pub proto_id: PrototypeList::ID, /// The prototype depth of that interface, used in the hasInstance hook. @@ -49,18 +59,19 @@ unsafe impl Sync for NonCallbackInterfaceObjectClass {} impl NonCallbackInterfaceObjectClass { /// Create a new `NonCallbackInterfaceObjectClass` structure. - pub const fn new(constructor_behavior: &'static InterfaceConstructorBehavior, - string_rep: &'static [u8], - proto_id: PrototypeList::ID, - proto_depth: u16) - -> NonCallbackInterfaceObjectClass { + pub const fn new( + constructor_behavior: &'static InterfaceConstructorBehavior, + string_rep: &'static [u8], + proto_id: PrototypeList::ID, + proto_depth: u16, + ) -> NonCallbackInterfaceObjectClass { NonCallbackInterfaceObjectClass { - class: Class { + class: JSClass { name: b"Function\0" as *const _ as *const libc::c_char, flags: 0, cOps: &constructor_behavior.0, - spec: ptr::null(), - ext: ptr::null(), + spec: 0 as *const _, + ext: 0 as *const _, oOps: &OBJECT_OPS, }, proto_id: proto_id, @@ -71,9 +82,7 @@ impl NonCallbackInterfaceObjectClass { /// cast own reference to `JSClass` reference pub fn as_jsclass(&self) -> &JSClass { - unsafe { - &*(self as *const _ as *const JSClass) - } + unsafe { &*(self as *const _ as *const JSClass) } } } @@ -82,263 +91,363 @@ pub type ConstructorClassHook = unsafe extern "C" fn(cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool; /// The constructor behavior of a non-callback interface object. -pub struct InterfaceConstructorBehavior(ClassOps); +pub struct InterfaceConstructorBehavior(JSClassOps); impl InterfaceConstructorBehavior { /// An interface constructor that unconditionally throws a type error. pub const fn throw() -> Self { - InterfaceConstructorBehavior(ClassOps { + InterfaceConstructorBehavior(JSClassOps { addProperty: None, delProperty: None, - getProperty: None, - setProperty: None, enumerate: None, + newEnumerate: None, resolve: None, mayResolve: None, finalize: None, call: Some(invalid_constructor), construct: Some(invalid_constructor), - hasInstance: Some(has_instance_hook), + hasInstance: None, // heycam/webidl#356 trace: None, }) } /// An interface constructor that calls a native Rust function. pub const fn call(hook: ConstructorClassHook) -> Self { - InterfaceConstructorBehavior(ClassOps { + InterfaceConstructorBehavior(JSClassOps { addProperty: None, delProperty: None, - getProperty: None, - setProperty: None, enumerate: None, + newEnumerate: None, resolve: None, mayResolve: None, finalize: None, call: Some(non_new_constructor), construct: Some(hook), - hasInstance: Some(has_instance_hook), + hasInstance: None, // heycam/webidl#356 trace: None, }) } } /// A trace hook. -pub type TraceHook = - unsafe extern "C" fn(trc: *mut JSTracer, obj: *mut JSObject); +pub type TraceHook = unsafe extern "C" fn(trc: *mut JSTracer, obj: *mut JSObject); /// Create a global object with the given class. pub unsafe fn create_global_object( - cx: *mut JSContext, - class: &'static JSClass, - private: *const libc::c_void, - trace: TraceHook, - rval: MutableHandleObject, - origin: &MutableOrigin) { + cx: SafeJSContext, + class: &'static JSClass, + private: *const libc::c_void, + trace: TraceHook, + mut rval: MutableHandleObject, + origin: &MutableOrigin, +) { assert!(rval.is_null()); - let mut options = CompartmentOptions::default(); - options.behaviors_.version_ = JSVersion::JSVERSION_ECMA_5; + let mut options = RealmOptions::default(); options.creationOptions_.traceGlobal_ = Some(trace); - options.creationOptions_.sharedMemoryAndAtomics_ = true; + options.creationOptions_.sharedMemoryAndAtomics_ = false; + options.creationOptions_.streams_ = true; + select_compartment(cx, &mut options); let origin = Box::new(origin.clone()); - let mut principal = CreateRustJSPrincipal(Box::into_raw(origin) as *const ::libc::c_void, - None, - None); - - rval.set(JS_NewGlobalObject(cx, - class, - principal, - OnNewGlobalHookOption::DontFireOnNewGlobalHook, - &options)); + let mut principal = + CreateRustJSPrincipal(Box::into_raw(origin) as *const ::libc::c_void, None, None); + + rval.set(JS_NewGlobalObject( + *cx, + class, + principal, + OnNewGlobalHookOption::DontFireOnNewGlobalHook, + &*options, + )); assert!(!rval.is_null()); // Initialize the reserved slots before doing anything that can GC, to // avoid getting trace hooks called on a partially initialized object. - JS_SetReservedSlot(rval.get(), DOM_OBJECT_SLOT, PrivateValue(private)); + let private_val = PrivateValue(private); + JS_SetReservedSlot(rval.get(), DOM_OBJECT_SLOT, &private_val); let proto_array: Box<ProtoOrIfaceArray> = - box [0 as *mut JSObject; PrototypeList::PROTO_OR_IFACE_LENGTH]; - JS_SetReservedSlot(rval.get(), - DOM_PROTOTYPE_SLOT, - PrivateValue(Box::into_raw(proto_array) as *const libc::c_void)); + Box::new([0 as *mut JSObject; PrototypeList::PROTO_OR_IFACE_LENGTH]); + let val = PrivateValue(Box::into_raw(proto_array) as *const libc::c_void); + JS_SetReservedSlot(rval.get(), DOM_PROTOTYPE_SLOT, &val); - let _ac = JSAutoCompartment::new(cx, rval.get()); - JS_FireOnNewGlobalObject(cx, rval.handle()); + let _ac = JSAutoRealm::new(*cx, rval.get()); + JS_FireOnNewGlobalObject(*cx, rval.handle()); +} + +/// Choose the compartment to create a new global object in. +fn select_compartment(cx: SafeJSContext, options: &mut RealmOptions) { + type Data = *mut Compartment; + unsafe extern "C" fn callback( + _cx: *mut JSContext, + data: *mut libc::c_void, + compartment: *mut Compartment, + ) -> CompartmentIterResult { + let data = data as *mut Data; + + if !IsSharableCompartment(compartment) || IsSystemCompartment(compartment) { + return CompartmentIterResult::KeepGoing; + } + + // Choose any sharable, non-system compartment in this context to allow + // same-agent documents to share JS and DOM objects. + *data = compartment; + CompartmentIterResult::Stop + } + + let mut compartment: Data = ptr::null_mut(); + unsafe { + JS_IterateCompartments( + *cx, + (&mut compartment) as *mut Data as *mut libc::c_void, + Some(callback), + ); + } + + if compartment.is_null() { + options.creationOptions_.compSpec_ = CompartmentSpecifier::NewCompartmentAndZone; + } else { + options.creationOptions_.compSpec_ = CompartmentSpecifier::ExistingCompartment; + options.creationOptions_.__bindgen_anon_1.comp_ = compartment; + } } /// Create and define the interface object of a callback interface. -pub unsafe fn create_callback_interface_object( - cx: *mut JSContext, - global: HandleObject, - constants: &[Guard<&[ConstantSpec]>], - name: &[u8], - rval: MutableHandleObject) { +pub fn create_callback_interface_object( + cx: SafeJSContext, + global: HandleObject, + constants: &[Guard<&[ConstantSpec]>], + name: &[u8], + mut rval: MutableHandleObject, +) { assert!(!constants.is_empty()); - rval.set(JS_NewObject(cx, ptr::null())); - assert!(!rval.ptr.is_null()); - define_guarded_constants(cx, rval.handle(), constants); + unsafe { + rval.set(JS_NewObject(*cx, ptr::null())); + } + assert!(!rval.is_null()); + define_guarded_constants(cx, rval.handle(), constants, global); define_name(cx, rval.handle(), name); define_on_global_object(cx, global, name, rval.handle()); } /// Create the interface prototype object of a non-callback interface. -pub unsafe fn create_interface_prototype_object( - cx: *mut JSContext, - proto: HandleObject, - class: &'static JSClass, - regular_methods: &[Guard<&'static [JSFunctionSpec]>], - regular_properties: &[Guard<&'static [JSPropertySpec]>], - constants: &[Guard<&[ConstantSpec]>], - unscopable_names: &[&[u8]], - rval: MutableHandleObject) { - create_object(cx, proto, class, regular_methods, regular_properties, constants, rval); +pub fn create_interface_prototype_object( + cx: SafeJSContext, + global: HandleObject, + proto: HandleObject, + class: &'static JSClass, + regular_methods: &[Guard<&'static [JSFunctionSpec]>], + regular_properties: &[Guard<&'static [JSPropertySpec]>], + constants: &[Guard<&[ConstantSpec]>], + unscopable_names: &[&[u8]], + rval: MutableHandleObject, +) { + create_object( + cx, + global, + proto, + class, + regular_methods, + regular_properties, + constants, + rval, + ); if !unscopable_names.is_empty() { - rooted!(in(cx) let mut unscopable_obj = ptr::null_mut()); + rooted!(in(*cx) let mut unscopable_obj = ptr::null_mut::<JSObject>()); create_unscopable_object(cx, unscopable_names, unscopable_obj.handle_mut()); - - let unscopable_symbol = GetWellKnownSymbol(cx, SymbolCode::unscopables); - assert!(!unscopable_symbol.is_null()); - - rooted!(in(cx) let unscopable_id = RUST_SYMBOL_TO_JSID(unscopable_symbol)); - assert!(JS_DefinePropertyById3( - cx, rval.handle(), unscopable_id.handle(), unscopable_obj.handle(), - JSPROP_READONLY, None, None)) + unsafe { + let unscopable_symbol = GetWellKnownSymbol(*cx, SymbolCode::unscopables); + assert!(!unscopable_symbol.is_null()); + + rooted!(in(*cx) let mut unscopable_id: jsid); + RUST_SYMBOL_TO_JSID(unscopable_symbol, unscopable_id.handle_mut()); + + assert!(JS_DefinePropertyById5( + *cx, + rval.handle(), + unscopable_id.handle(), + unscopable_obj.handle(), + JSPROP_READONLY as u32 + )) + } } } /// Create and define the interface object of a non-callback interface. -pub unsafe fn create_noncallback_interface_object( - cx: *mut JSContext, - global: HandleObject, - proto: HandleObject, - class: &'static NonCallbackInterfaceObjectClass, - static_methods: &[Guard<&'static [JSFunctionSpec]>], - static_properties: &[Guard<&'static [JSPropertySpec]>], - constants: &[Guard<&[ConstantSpec]>], - interface_prototype_object: HandleObject, - name: &[u8], - length: u32, - rval: MutableHandleObject) { - create_object(cx, - proto, - class.as_jsclass(), - static_methods, - static_properties, - constants, - rval); - assert!(JS_LinkConstructorAndPrototype(cx, rval.handle(), interface_prototype_object)); +pub fn create_noncallback_interface_object( + cx: SafeJSContext, + global: HandleObject, + proto: HandleObject, + class: &'static NonCallbackInterfaceObjectClass, + static_methods: &[Guard<&'static [JSFunctionSpec]>], + static_properties: &[Guard<&'static [JSPropertySpec]>], + constants: &[Guard<&[ConstantSpec]>], + interface_prototype_object: HandleObject, + name: &[u8], + length: u32, + legacy_window_alias_names: &[&[u8]], + rval: MutableHandleObject, +) { + create_object( + cx, + global, + proto, + class.as_jsclass(), + static_methods, + static_properties, + constants, + rval, + ); + unsafe { + assert!(JS_LinkConstructorAndPrototype( + *cx, + rval.handle(), + interface_prototype_object + )); + } define_name(cx, rval.handle(), name); - define_length(cx, rval.handle(), length); + define_length(cx, rval.handle(), i32::try_from(length).expect("overflow")); define_on_global_object(cx, global, name, rval.handle()); + + if is_exposed_in(global, Globals::WINDOW) { + for legacy_window_alias in legacy_window_alias_names { + define_on_global_object(cx, global, legacy_window_alias, rval.handle()); + } + } } /// Create and define the named constructors of a non-callback interface. -pub unsafe fn create_named_constructors( - cx: *mut JSContext, - global: HandleObject, - named_constructors: &[(ConstructorClassHook, &[u8], u32)], - interface_prototype_object: HandleObject) { - rooted!(in(cx) let mut constructor = ptr::null_mut()); +pub fn create_named_constructors( + cx: SafeJSContext, + global: HandleObject, + named_constructors: &[(ConstructorClassHook, &[u8], u32)], + interface_prototype_object: HandleObject, +) { + rooted!(in(*cx) let mut constructor = ptr::null_mut::<JSObject>()); for &(native, name, arity) in named_constructors { - assert!(*name.last().unwrap() == b'\0'); - - let fun = JS_NewFunction(cx, - Some(native), - arity, - JSFUN_CONSTRUCTOR, - name.as_ptr() as *const libc::c_char); - assert!(!fun.is_null()); - constructor.set(JS_GetFunctionObject(fun)); - assert!(!constructor.is_null()); - - assert!(JS_DefineProperty1(cx, - constructor.handle(), - b"prototype\0".as_ptr() as *const libc::c_char, - interface_prototype_object, - JSPROP_PERMANENT | JSPROP_READONLY, - None, - None)); + assert_eq!(*name.last().unwrap(), b'\0'); + + unsafe { + let fun = JS_NewFunction( + *cx, + Some(native), + arity, + JSFUN_CONSTRUCTOR, + name.as_ptr() as *const libc::c_char, + ); + assert!(!fun.is_null()); + constructor.set(JS_GetFunctionObject(fun)); + assert!(!constructor.is_null()); + + assert!(JS_DefineProperty3( + *cx, + constructor.handle(), + b"prototype\0".as_ptr() as *const libc::c_char, + interface_prototype_object, + (JSPROP_PERMANENT | JSPROP_READONLY) as u32 + )); + } define_on_global_object(cx, global, name, constructor.handle()); } } /// Create a new object with a unique type. -pub unsafe fn create_object( - cx: *mut JSContext, - proto: HandleObject, - class: &'static JSClass, - methods: &[Guard<&'static [JSFunctionSpec]>], - properties: &[Guard<&'static [JSPropertySpec]>], - constants: &[Guard<&[ConstantSpec]>], - rval: MutableHandleObject) { - rval.set(JS_NewObjectWithUniqueType(cx, class, proto)); - assert!(!rval.ptr.is_null()); - define_guarded_methods(cx, rval.handle(), methods); - define_guarded_properties(cx, rval.handle(), properties); - define_guarded_constants(cx, rval.handle(), constants); +pub fn create_object( + cx: SafeJSContext, + global: HandleObject, + proto: HandleObject, + class: &'static JSClass, + methods: &[Guard<&'static [JSFunctionSpec]>], + properties: &[Guard<&'static [JSPropertySpec]>], + constants: &[Guard<&[ConstantSpec]>], + mut rval: MutableHandleObject, +) { + unsafe { + rval.set(JS_NewObjectWithGivenProto(*cx, class, proto)); + } + assert!(!rval.is_null()); + define_guarded_methods(cx, rval.handle(), methods, global); + define_guarded_properties(cx, rval.handle(), properties, global); + define_guarded_constants(cx, rval.handle(), constants, global); } /// Conditionally define constants on an object. -pub unsafe fn define_guarded_constants( - cx: *mut JSContext, - obj: HandleObject, - constants: &[Guard<&[ConstantSpec]>]) { +pub fn define_guarded_constants( + cx: SafeJSContext, + obj: HandleObject, + constants: &[Guard<&[ConstantSpec]>], + global: HandleObject, +) { for guard in constants { - if let Some(specs) = guard.expose(cx, obj) { + if let Some(specs) = guard.expose(cx, obj, global) { define_constants(cx, obj, specs); } } } /// Conditionally define methods on an object. -pub unsafe fn define_guarded_methods( - cx: *mut JSContext, - obj: HandleObject, - methods: &[Guard<&'static [JSFunctionSpec]>]) { +pub fn define_guarded_methods( + cx: SafeJSContext, + obj: HandleObject, + methods: &[Guard<&'static [JSFunctionSpec]>], + global: HandleObject, +) { for guard in methods { - if let Some(specs) = guard.expose(cx, obj) { - define_methods(cx, obj, specs).unwrap(); + if let Some(specs) = guard.expose(cx, obj, global) { + unsafe { + define_methods(*cx, obj, specs).unwrap(); + } } } } /// Conditionally define properties on an object. -pub unsafe fn define_guarded_properties( - cx: *mut JSContext, - obj: HandleObject, - properties: &[Guard<&'static [JSPropertySpec]>]) { +pub fn define_guarded_properties( + cx: SafeJSContext, + obj: HandleObject, + properties: &[Guard<&'static [JSPropertySpec]>], + global: HandleObject, +) { for guard in properties { - if let Some(specs) = guard.expose(cx, obj) { - define_properties(cx, obj, specs).unwrap(); + if let Some(specs) = guard.expose(cx, obj, global) { + unsafe { + define_properties(*cx, obj, specs).unwrap(); + } } } } /// Returns whether an interface with exposure set given by `globals` should /// be exposed in the global object `obj`. -pub unsafe fn is_exposed_in(object: HandleObject, globals: Globals) -> bool { - let unwrapped = UncheckedUnwrapObject(object.get(), /* stopAtWindowProxy = */ 0); - let dom_class = get_dom_class(unwrapped).unwrap(); - globals.contains(dom_class.global) +pub fn is_exposed_in(object: HandleObject, globals: Globals) -> bool { + unsafe { + let unwrapped = UncheckedUnwrapObject(object.get(), /* stopAtWindowProxy = */ 0); + let dom_class = get_dom_class(unwrapped).unwrap(); + globals.contains(dom_class.global) + } } /// Define a property with a given name on the global object. Should be called /// through the resolve hook. -pub unsafe fn define_on_global_object( - cx: *mut JSContext, - global: HandleObject, - name: &[u8], - obj: HandleObject) { - assert!(*name.last().unwrap() == b'\0'); - assert!(JS_DefineProperty1(cx, - global, - name.as_ptr() as *const libc::c_char, - obj, - JSPROP_RESOLVING, - None, None)); +pub fn define_on_global_object( + cx: SafeJSContext, + global: HandleObject, + name: &[u8], + obj: HandleObject, +) { + assert_eq!(*name.last().unwrap(), b'\0'); + unsafe { + assert!(JS_DefineProperty3( + *cx, + global, + name.as_ptr() as *const libc::c_char, + obj, + JSPROP_RESOLVING + )); + } } const OBJECT_OPS: ObjectOps = ObjectOps { @@ -349,17 +458,15 @@ const OBJECT_OPS: ObjectOps = ObjectOps { setProperty: None, getOwnPropertyDescriptor: None, deleteProperty: None, - watch: None, - unwatch: None, getElements: None, - enumerate: None, funToString: Some(fun_to_string_hook), }; -unsafe extern "C" fn fun_to_string_hook(cx: *mut JSContext, - obj: HandleObject, - _indent: u32) - -> *mut JSString { +unsafe extern "C" fn fun_to_string_hook( + cx: *mut JSContext, + obj: RawHandleObject, + _is_to_source: bool, +) -> *mut JSString { let js_class = get_object_class(obj.get()); assert!(!js_class.is_null()); let repr = (*(js_class as *const NonCallbackInterfaceObjectClass)).representation; @@ -369,116 +476,66 @@ unsafe extern "C" fn fun_to_string_hook(cx: *mut JSContext, ret } -/// Hook for instanceof on interface objects. -unsafe extern "C" fn has_instance_hook(cx: *mut JSContext, - obj: HandleObject, - value: MutableHandleValue, - rval: *mut bool) -> bool { - match has_instance(cx, obj, value.handle()) { - Ok(result) => { - *rval = result; - true +fn create_unscopable_object(cx: SafeJSContext, names: &[&[u8]], mut rval: MutableHandleObject) { + assert!(!names.is_empty()); + assert!(rval.is_null()); + unsafe { + rval.set(JS_NewPlainObject(*cx)); + assert!(!rval.is_null()); + for &name in names { + assert_eq!(*name.last().unwrap(), b'\0'); + assert!(JS_DefineProperty( + *cx, + rval.handle(), + name.as_ptr() as *const libc::c_char, + HandleValue::from_raw(TrueHandleValue), + JSPROP_READONLY as u32, + )); } - Err(()) => false, } } -/// Return whether a value is an instance of a given prototype. -/// http://heycam.github.io/webidl/#es-interface-hasinstance -unsafe fn has_instance( - cx: *mut JSContext, - interface_object: HandleObject, - value: HandleValue) - -> Result<bool, ()> { - if !value.is_object() { - // Step 1. - return Ok(false); +fn define_name(cx: SafeJSContext, obj: HandleObject, name: &[u8]) { + assert_eq!(*name.last().unwrap(), b'\0'); + unsafe { + rooted!(in(*cx) let name = JS_AtomizeAndPinString(*cx, name.as_ptr() as *const libc::c_char)); + assert!(!name.is_null()); + assert!(JS_DefineProperty4( + *cx, + obj, + b"name\0".as_ptr() as *const libc::c_char, + name.handle().into(), + JSPROP_READONLY as u32 + )); } - rooted!(in(cx) let mut value = value.to_object()); - - let js_class = get_object_class(interface_object.get()); - let object_class = &*(js_class as *const NonCallbackInterfaceObjectClass); - - if let Ok(dom_class) = get_dom_class(UncheckedUnwrapObject(value.get(), - /* stopAtWindowProxy = */ 0)) { - if dom_class.interface_chain[object_class.proto_depth as usize] == object_class.proto_id { - // Step 4. - return Ok(true); - } - } - - // Step 2. - let global = GetGlobalForObjectCrossCompartment(interface_object.get()); - assert!(!global.is_null()); - let proto_or_iface_array = get_proto_or_iface_array(global); - rooted!(in(cx) let prototype = (*proto_or_iface_array)[object_class.proto_id as usize]); - assert!(!prototype.is_null()); - // Step 3 only concern legacy callback interface objects (i.e. NodeFilter). - - while JS_GetPrototype(cx, value.handle(), value.handle_mut()) { - if value.is_null() { - // Step 5.2. - return Ok(false); - } else if value.get() as *const _ == prototype.get() { - // Step 5.3. - return Ok(true); - } - } - // JS_GetPrototype threw an exception. - Err(()) } -unsafe fn create_unscopable_object( - cx: *mut JSContext, - names: &[&[u8]], - rval: MutableHandleObject) { - assert!(!names.is_empty()); - assert!(rval.is_null()); - rval.set(JS_NewPlainObject(cx)); - assert!(!rval.ptr.is_null()); - for &name in names { - assert!(*name.last().unwrap() == b'\0'); - assert!(JS_DefineProperty( - cx, rval.handle(), name.as_ptr() as *const libc::c_char, TrueHandleValue, - JSPROP_READONLY, None, None)); +fn define_length(cx: SafeJSContext, obj: HandleObject, length: i32) { + unsafe { + assert!(JS_DefineProperty5( + *cx, + obj, + b"length\0".as_ptr() as *const libc::c_char, + length, + JSPROP_READONLY as u32 + )); } } -unsafe fn define_name(cx: *mut JSContext, obj: HandleObject, name: &[u8]) { - assert!(*name.last().unwrap() == b'\0'); - rooted!(in(cx) let name = JS_AtomizeAndPinString(cx, name.as_ptr() as *const libc::c_char)); - assert!(!name.is_null()); - assert!(JS_DefineProperty2(cx, - obj, - b"name\0".as_ptr() as *const libc::c_char, - name.handle(), - JSPROP_READONLY, - None, None)); -} - -unsafe fn define_length(cx: *mut JSContext, obj: HandleObject, length: u32) { - assert!(JS_DefineProperty4(cx, - obj, - b"length\0".as_ptr() as *const libc::c_char, - length, - JSPROP_READONLY, - None, None)); -} - unsafe extern "C" fn invalid_constructor( - cx: *mut JSContext, - _argc: libc::c_uint, - _vp: *mut JSVal) - -> bool { + cx: *mut JSContext, + _argc: libc::c_uint, + _vp: *mut JSVal, +) -> bool { throw_type_error(cx, "Illegal constructor."); false } unsafe extern "C" fn non_new_constructor( - cx: *mut JSContext, - _argc: libc::c_uint, - _vp: *mut JSVal) - -> bool { + cx: *mut JSContext, + _argc: libc::c_uint, + _vp: *mut JSVal, +) -> bool { throw_type_error(cx, "This constructor needs to be called with `new`."); false } diff --git a/components/script/dom/bindings/iterable.rs b/components/script/dom/bindings/iterable.rs index 845ecbaa8cd..2eac830a4bb 100644 --- a/components/script/dom/bindings/iterable.rs +++ b/components/script/dom/bindings/iterable.rs @@ -1,28 +1,32 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #![allow(unsafe_code)] //! Implementation of `iterable<...>` and `iterable<..., ...>` WebIDL declarations. -use core::nonzero::NonZero; -use dom::bindings::codegen::Bindings::IterableIteratorBinding::IterableKeyAndValueResult; -use dom::bindings::codegen::Bindings::IterableIteratorBinding::IterableKeyOrValueResult; -use dom::bindings::error::Fallible; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::trace::JSTraceable; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::IterableIteratorBinding::IterableKeyAndValueResult; +use crate::dom::bindings::codegen::Bindings::IterableIteratorBinding::IterableKeyOrValueResult; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{ + reflect_dom_object, DomObjectIteratorWrap, DomObjectWrap, Reflector, +}; +use crate::dom::bindings::root::{Dom, DomRoot, Root}; +use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox}; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; use js::conversions::ToJSValConvertible; -use js::jsapi::{HandleValue, Heap, JSContext, JSObject, MutableHandleObject}; +use js::jsapi::{Heap, JSObject}; use js::jsval::UndefinedValue; +use js::rust::{HandleValue, MutableHandleObject}; use std::cell::Cell; use std::ptr; +use std::ptr::NonNull; /// The values that an iterator will iterate over. -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] pub enum IteratorType { /// The keys of the iterable object. Keys, @@ -49,93 +53,108 @@ pub trait Iterable { /// An iterator over the iterable entries of a given DOM interface. //FIXME: #12811 prevents dom_struct with type parameters #[dom_struct] -pub struct IterableIterator<T: DomObject + JSTraceable + Iterable> { +pub struct IterableIterator<T: DomObjectIteratorWrap + JSTraceable + Iterable> { reflector: Reflector, - iterable: JS<T>, + iterable: Dom<T>, type_: IteratorType, index: Cell<u32>, } -impl<T: DomObject + JSTraceable + Iterable> IterableIterator<T> { +impl<T: DomObjectIteratorWrap + JSTraceable + Iterable> IterableIterator<T> { /// Create a new iterator instance for the provided iterable DOM interface. - pub fn new(iterable: &T, - type_: IteratorType, - wrap: unsafe fn(*mut JSContext, &GlobalScope, Box<IterableIterator<T>>) - -> Root<Self>) -> Root<Self> { - let iterator = box IterableIterator { + pub fn new(iterable: &T, type_: IteratorType) -> DomRoot<Self> { + let iterator = Box::new(IterableIterator { reflector: Reflector::new(), type_: type_, - iterable: JS::from_ref(iterable), + iterable: Dom::from_ref(iterable), index: Cell::new(0), - }; - reflect_dom_object(iterator, &*iterable.global(), wrap) + }); + reflect_dom_object(iterator, &*iterable.global()) } /// Return the next value from the iterable object. #[allow(non_snake_case)] - pub fn Next(&self, cx: *mut JSContext) -> Fallible<NonZero<*mut JSObject>> { + pub fn Next(&self, cx: JSContext) -> Fallible<NonNull<JSObject>> { let index = self.index.get(); - rooted!(in(cx) let mut value = UndefinedValue()); - rooted!(in(cx) let mut rval = ptr::null_mut()); + rooted!(in(*cx) let mut value = UndefinedValue()); + rooted!(in(*cx) let mut rval = ptr::null_mut::<JSObject>()); let result = if index >= self.iterable.get_iterable_length() { dict_return(cx, rval.handle_mut(), true, value.handle()) } else { match self.type_ { IteratorType::Keys => { unsafe { - self.iterable.get_key_at_index(index).to_jsval(cx, value.handle_mut()); + self.iterable + .get_key_at_index(index) + .to_jsval(*cx, value.handle_mut()); } dict_return(cx, rval.handle_mut(), false, value.handle()) - } + }, IteratorType::Values => { unsafe { - self.iterable.get_value_at_index(index).to_jsval(cx, value.handle_mut()); + self.iterable + .get_value_at_index(index) + .to_jsval(*cx, value.handle_mut()); } dict_return(cx, rval.handle_mut(), false, value.handle()) - } + }, IteratorType::Entries => { - rooted!(in(cx) let mut key = UndefinedValue()); + rooted!(in(*cx) let mut key = UndefinedValue()); unsafe { - self.iterable.get_key_at_index(index).to_jsval(cx, key.handle_mut()); - self.iterable.get_value_at_index(index).to_jsval(cx, value.handle_mut()); + self.iterable + .get_key_at_index(index) + .to_jsval(*cx, key.handle_mut()); + self.iterable + .get_value_at_index(index) + .to_jsval(*cx, value.handle_mut()); } key_and_value_return(cx, rval.handle_mut(), key.handle(), value.handle()) - } + }, } }; self.index.set(index + 1); - result.map(|_| { - assert!(!rval.is_null()); - unsafe { NonZero::new(rval.get()) } - }) + result.map(|_| NonNull::new(rval.get()).expect("got a null pointer")) } } -fn dict_return(cx: *mut JSContext, - result: MutableHandleObject, - done: bool, - value: HandleValue) -> Fallible<()> { - let mut dict = unsafe { IterableKeyOrValueResult::empty(cx) }; +impl<T: DomObjectIteratorWrap + JSTraceable + Iterable> DomObjectWrap for IterableIterator<T> { + const WRAP: unsafe fn(JSContext, &GlobalScope, Box<Self>) -> Root<Dom<Self>> = T::ITER_WRAP; +} + +fn dict_return( + cx: JSContext, + mut result: MutableHandleObject, + done: bool, + value: HandleValue, +) -> Fallible<()> { + let mut dict = IterableKeyOrValueResult::empty(); dict.done = done; dict.value.set(value.get()); - rooted!(in(cx) let mut dict_value = UndefinedValue()); + rooted!(in(*cx) let mut dict_value = UndefinedValue()); unsafe { - dict.to_jsval(cx, dict_value.handle_mut()); + dict.to_jsval(*cx, dict_value.handle_mut()); } result.set(dict_value.to_object()); Ok(()) } -fn key_and_value_return(cx: *mut JSContext, - result: MutableHandleObject, - key: HandleValue, - value: HandleValue) -> Fallible<()> { - let mut dict = unsafe { IterableKeyAndValueResult::empty(cx) }; +fn key_and_value_return( + cx: JSContext, + mut result: MutableHandleObject, + key: HandleValue, + value: HandleValue, +) -> Fallible<()> { + let mut dict = IterableKeyAndValueResult::empty(); dict.done = false; - dict.value = Some(vec![Heap::new(key.get()), Heap::new(value.get())]); - rooted!(in(cx) let mut dict_value = UndefinedValue()); + dict.value = Some( + vec![key, value] + .into_iter() + .map(|handle| RootedTraceableBox::from_box(Heap::boxed(handle.get()))) + .collect(), + ); + rooted!(in(*cx) let mut dict_value = UndefinedValue()); unsafe { - dict.to_jsval(cx, dict_value.handle_mut()); + dict.to_jsval(*cx, dict_value.handle_mut()); } result.set(dict_value.to_object()); Ok(()) diff --git a/components/script/dom/bindings/js.rs b/components/script/dom/bindings/js.rs deleted file mode 100644 index b4c1322ed0c..00000000000 --- a/components/script/dom/bindings/js.rs +++ /dev/null @@ -1,606 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -//! Smart pointers for the JS-managed DOM objects. -//! -//! The DOM is made up of DOM objects whose lifetime is entirely controlled by -//! the whims of the SpiderMonkey garbage collector. The types in this module -//! are designed to ensure that any interactions with said Rust types only -//! occur on values that will remain alive the entire time. -//! -//! Here is a brief overview of the important types: -//! -//! - `Root<T>`: a stack-based reference to a rooted DOM object. -//! - `JS<T>`: a reference to a DOM object that can automatically be traced by -//! the GC when encountered as a field of a Rust structure. -//! -//! `JS<T>` does not allow access to their inner value without explicitly -//! creating a stack-based root via the `root` method. This returns a `Root<T>`, -//! which causes the JS-owned value to be uncollectable for the duration of the -//! `Root` object's lifetime. A reference to the object can then be obtained -//! from the `Root` object. These references are not allowed to outlive their -//! originating `Root<T>`. -//! - -use core::nonzero::NonZero; -use dom::bindings::conversions::DerivedFrom; -use dom::bindings::inheritance::Castable; -use dom::bindings::reflector::{DomObject, Reflector}; -use dom::bindings::trace::JSTraceable; -use dom::bindings::trace::trace_reflector; -use dom::node::Node; -use heapsize::HeapSizeOf; -use js::jsapi::{JSObject, JSTracer}; -use script_layout_interface::TrustedNodeAddress; -use script_thread::STACK_ROOTS; -use std::cell::UnsafeCell; -use std::default::Default; -use std::hash::{Hash, Hasher}; -#[cfg(debug_assertions)] -use std::intrinsics::type_name; -use std::mem; -use std::ops::Deref; -use std::ptr; -use std::rc::Rc; -use style::thread_state; - -/// A traced reference to a DOM object -/// -/// This type is critical to making garbage collection work with the DOM, -/// but it is very dangerous; if garbage collection happens with a `JS<T>` -/// on the stack, the `JS<T>` can point to freed memory. -/// -/// This should only be used as a field in other DOM objects. -#[must_root] -pub struct JS<T> { - ptr: NonZero<*const T>, -} - -// JS<T> is similar to Rc<T>, in that it's not always clear how to avoid double-counting. -// For now, we choose not to follow any such pointers. -impl<T> HeapSizeOf for JS<T> { - fn heap_size_of_children(&self) -> usize { - 0 - } -} - -impl<T> JS<T> { - /// Returns `LayoutJS<T>` containing the same pointer. - pub unsafe fn to_layout(&self) -> LayoutJS<T> { - debug_assert!(thread_state::get().is_layout()); - LayoutJS { - ptr: self.ptr.clone(), - } - } -} - -impl<T: DomObject> JS<T> { - /// Create a JS<T> from a &T - #[allow(unrooted_must_root)] - pub fn from_ref(obj: &T) -> JS<T> { - debug_assert!(thread_state::get().is_script()); - JS { - ptr: unsafe { NonZero::new(&*obj) }, - } - } -} - -impl<'root, T: DomObject + 'root> RootedReference<'root> for JS<T> { - type Ref = &'root T; - fn r(&'root self) -> &'root T { - &self - } -} - -impl<T: DomObject> Deref for JS<T> { - type Target = T; - - fn deref(&self) -> &T { - debug_assert!(thread_state::get().is_script()); - // We can only have &JS<T> from a rooted thing, so it's safe to deref - // it to &T. - unsafe { &**self.ptr } - } -} - -unsafe impl<T: DomObject> JSTraceable for JS<T> { - unsafe fn trace(&self, trc: *mut JSTracer) { - #[cfg(debug_assertions)] - let trace_str = format!("for {} on heap", type_name::<T>()); - #[cfg(debug_assertions)] - let trace_info = &trace_str[..]; - #[cfg(not(debug_assertions))] - let trace_info = "for DOM object on heap"; - - trace_reflector(trc, - trace_info, - (**self.ptr).reflector()); - } -} - -/// An unrooted reference to a DOM object for use in layout. `Layout*Helpers` -/// traits must be implemented on this. -#[allow_unrooted_interior] -pub struct LayoutJS<T> { - ptr: NonZero<*const T>, -} - -impl<T: Castable> LayoutJS<T> { - /// Cast a DOM object root upwards to one of the interfaces it derives from. - pub fn upcast<U>(&self) -> LayoutJS<U> - where U: Castable, - T: DerivedFrom<U> - { - debug_assert!(thread_state::get().is_layout()); - let ptr: *const T = *self.ptr; - LayoutJS { - ptr: unsafe { NonZero::new(ptr as *const U) }, - } - } - - /// Cast a DOM object downwards to one of the interfaces it might implement. - pub fn downcast<U>(&self) -> Option<LayoutJS<U>> - where U: DerivedFrom<T> - { - debug_assert!(thread_state::get().is_layout()); - unsafe { - if (*self.unsafe_get()).is::<U>() { - let ptr: *const T = *self.ptr; - Some(LayoutJS { - ptr: NonZero::new(ptr as *const U), - }) - } else { - None - } - } - } -} - -impl<T: DomObject> LayoutJS<T> { - /// Get the reflector. - pub unsafe fn get_jsobject(&self) -> *mut JSObject { - debug_assert!(thread_state::get().is_layout()); - (**self.ptr).reflector().get_jsobject().get() - } -} - -impl<T> Copy for LayoutJS<T> {} - -impl<T> PartialEq for JS<T> { - fn eq(&self, other: &JS<T>) -> bool { - self.ptr == other.ptr - } -} - -impl<T> Eq for JS<T> {} - -impl<T> PartialEq for LayoutJS<T> { - fn eq(&self, other: &LayoutJS<T>) -> bool { - self.ptr == other.ptr - } -} - -impl<T> Eq for LayoutJS<T> {} - -impl<T> Hash for JS<T> { - fn hash<H: Hasher>(&self, state: &mut H) { - self.ptr.hash(state) - } -} - -impl<T> Hash for LayoutJS<T> { - fn hash<H: Hasher>(&self, state: &mut H) { - self.ptr.hash(state) - } -} - -impl <T> Clone for JS<T> { - #[inline] - #[allow(unrooted_must_root)] - fn clone(&self) -> JS<T> { - debug_assert!(thread_state::get().is_script()); - JS { - ptr: self.ptr.clone(), - } - } -} - -impl <T> Clone for LayoutJS<T> { - #[inline] - fn clone(&self) -> LayoutJS<T> { - debug_assert!(thread_state::get().is_layout()); - LayoutJS { - ptr: self.ptr.clone(), - } - } -} - -impl LayoutJS<Node> { - /// Create a new JS-owned value wrapped from an address known to be a - /// `Node` pointer. - pub unsafe fn from_trusted_node_address(inner: TrustedNodeAddress) -> LayoutJS<Node> { - debug_assert!(thread_state::get().is_layout()); - let TrustedNodeAddress(addr) = inner; - LayoutJS { - ptr: NonZero::new(addr as *const Node), - } - } -} - -/// A holder that provides interior mutability for GC-managed values such as -/// `JS<T>`. Essentially a `Cell<JS<T>>`, but safer. -/// -/// This should only be used as a field in other DOM objects; see warning -/// on `JS<T>`. -#[must_root] -#[derive(JSTraceable)] -pub struct MutJS<T: DomObject> { - val: UnsafeCell<JS<T>>, -} - -impl<T: DomObject> MutJS<T> { - /// Create a new `MutJS`. - pub fn new(initial: &T) -> MutJS<T> { - debug_assert!(thread_state::get().is_script()); - MutJS { - val: UnsafeCell::new(JS::from_ref(initial)), - } - } - - /// Set this `MutJS` to the given value. - pub fn set(&self, val: &T) { - debug_assert!(thread_state::get().is_script()); - unsafe { - *self.val.get() = JS::from_ref(val); - } - } - - /// Get the value in this `MutJS`. - pub fn get(&self) -> Root<T> { - debug_assert!(thread_state::get().is_script()); - unsafe { - Root::from_ref(&*ptr::read(self.val.get())) - } - } -} - -impl<T: DomObject> HeapSizeOf for MutJS<T> { - fn heap_size_of_children(&self) -> usize { - // See comment on HeapSizeOf for JS<T>. - 0 - } -} - -impl<T: DomObject> PartialEq for MutJS<T> { - fn eq(&self, other: &Self) -> bool { - unsafe { - *self.val.get() == *other.val.get() - } - } -} - -impl<T: DomObject + PartialEq> PartialEq<T> for MutJS<T> { - fn eq(&self, other: &T) -> bool { - unsafe { - **self.val.get() == *other - } - } -} - -/// A holder that provides interior mutability for GC-managed values such as -/// `JS<T>`, with nullability represented by an enclosing Option wrapper. -/// Essentially a `Cell<Option<JS<T>>>`, but safer. -/// -/// This should only be used as a field in other DOM objects; see warning -/// on `JS<T>`. -#[must_root] -#[derive(JSTraceable)] -pub struct MutNullableJS<T: DomObject> { - ptr: UnsafeCell<Option<JS<T>>>, -} - -impl<T: DomObject> MutNullableJS<T> { - /// Create a new `MutNullableJS`. - pub fn new(initial: Option<&T>) -> MutNullableJS<T> { - debug_assert!(thread_state::get().is_script()); - MutNullableJS { - ptr: UnsafeCell::new(initial.map(JS::from_ref)), - } - } - - /// Retrieve a copy of the current inner value. If it is `None`, it is - /// initialized with the result of `cb` first. - pub fn or_init<F>(&self, cb: F) -> Root<T> - where F: FnOnce() -> Root<T> - { - debug_assert!(thread_state::get().is_script()); - match self.get() { - Some(inner) => inner, - None => { - let inner = cb(); - self.set(Some(&inner)); - inner - }, - } - } - - /// Retrieve a copy of the inner optional `JS<T>` as `LayoutJS<T>`. - /// For use by layout, which can't use safe types like Temporary. - #[allow(unrooted_must_root)] - pub unsafe fn get_inner_as_layout(&self) -> Option<LayoutJS<T>> { - debug_assert!(thread_state::get().is_layout()); - ptr::read(self.ptr.get()).map(|js| js.to_layout()) - } - - /// Get a rooted value out of this object - #[allow(unrooted_must_root)] - pub fn get(&self) -> Option<Root<T>> { - debug_assert!(thread_state::get().is_script()); - unsafe { - ptr::read(self.ptr.get()).map(|o| Root::from_ref(&*o)) - } - } - - /// Set this `MutNullableJS` to the given value. - pub fn set(&self, val: Option<&T>) { - debug_assert!(thread_state::get().is_script()); - unsafe { - *self.ptr.get() = val.map(|p| JS::from_ref(p)); - } - } - - /// Gets the current value out of this object and sets it to `None`. - pub fn take(&self) -> Option<Root<T>> { - let value = self.get(); - self.set(None); - value - } -} - -impl<T: DomObject> PartialEq for MutNullableJS<T> { - fn eq(&self, other: &Self) -> bool { - unsafe { - *self.ptr.get() == *other.ptr.get() - } - } -} - -impl<'a, T: DomObject> PartialEq<Option<&'a T>> for MutNullableJS<T> { - fn eq(&self, other: &Option<&T>) -> bool { - unsafe { - *self.ptr.get() == other.map(JS::from_ref) - } - } -} - -impl<T: DomObject> Default for MutNullableJS<T> { - #[allow(unrooted_must_root)] - fn default() -> MutNullableJS<T> { - debug_assert!(thread_state::get().is_script()); - MutNullableJS { - ptr: UnsafeCell::new(None), - } - } -} - -impl<T: DomObject> HeapSizeOf for MutNullableJS<T> { - fn heap_size_of_children(&self) -> usize { - // See comment on HeapSizeOf for JS<T>. - 0 - } -} - -impl<T: DomObject> LayoutJS<T> { - /// Returns an unsafe pointer to the interior of this JS object. This is - /// the only method that be safely accessed from layout. (The fact that - /// this is unsafe is what necessitates the layout wrappers.) - pub unsafe fn unsafe_get(&self) -> *const T { - debug_assert!(thread_state::get().is_layout()); - *self.ptr - } - - /// Returns a reference to the interior of this JS object. This method is - /// safe to call because it originates from the layout thread, and it cannot - /// mutate DOM nodes. - pub fn get_for_script(&self) -> &T { - debug_assert!(thread_state::get().is_script()); - unsafe { &**self.ptr } - } -} - -/// Get a reference out of a rooted value. -pub trait RootedReference<'root> { - /// The type of the reference. - type Ref: 'root; - /// Obtain a reference out of the rooted value. - fn r(&'root self) -> Self::Ref; -} - -impl<'root, T: JSTraceable + DomObject + 'root> RootedReference<'root> for [JS<T>] { - type Ref = &'root [&'root T]; - fn r(&'root self) -> &'root [&'root T] { - unsafe { mem::transmute(self) } - } -} - -impl<'root, T: DomObject + 'root> RootedReference<'root> for Rc<T> { - type Ref = &'root T; - fn r(&'root self) -> &'root T { - self - } -} - -impl<'root, T: RootedReference<'root> + 'root> RootedReference<'root> for Option<T> { - type Ref = Option<T::Ref>; - fn r(&'root self) -> Option<T::Ref> { - self.as_ref().map(RootedReference::r) - } -} - -/// A rooting mechanism for reflectors on the stack. -/// LIFO is not required. -/// -/// See also [*Exact Stack Rooting - Storing a GCPointer on the CStack*] -/// (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/GC/Exact_Stack_Rooting). -pub struct RootCollection { - roots: UnsafeCell<Vec<*const Reflector>>, -} - -/// A pointer to a RootCollection, for use in global variables. -pub struct RootCollectionPtr(pub *const RootCollection); - -impl Copy for RootCollectionPtr {} -impl Clone for RootCollectionPtr { - fn clone(&self) -> RootCollectionPtr { - *self - } -} - -impl RootCollection { - /// Create an empty collection of roots - pub fn new() -> RootCollection { - debug_assert!(thread_state::get().is_script()); - RootCollection { - roots: UnsafeCell::new(vec![]), - } - } - - /// Start tracking a stack-based root - unsafe fn root(&self, untracked_reflector: *const Reflector) { - debug_assert!(thread_state::get().is_script()); - let mut roots = &mut *self.roots.get(); - roots.push(untracked_reflector); - assert!(!(*untracked_reflector).get_jsobject().is_null()) - } - - /// Stop tracking a stack-based reflector, asserting if it isn't found. - unsafe fn unroot(&self, tracked_reflector: *const Reflector) { - assert!(!tracked_reflector.is_null()); - assert!(!(*tracked_reflector).get_jsobject().is_null()); - debug_assert!(thread_state::get().is_script()); - let mut roots = &mut *self.roots.get(); - match roots.iter().rposition(|r| *r == tracked_reflector) { - Some(idx) => { - roots.remove(idx); - }, - None => panic!("Can't remove a root that was never rooted!"), - } - } -} - -/// SM Callback that traces the rooted reflectors -pub unsafe fn trace_roots(tracer: *mut JSTracer) { - debug!("tracing stack roots"); - STACK_ROOTS.with(|ref collection| { - let RootCollectionPtr(collection) = collection.get().unwrap(); - let collection = &*(*collection).roots.get(); - for root in collection { - trace_reflector(tracer, "on stack", &**root); - } - }); -} - -/// A rooted reference to a DOM object. -/// -/// The JS value is pinned for the duration of this object's lifetime; roots -/// are additive, so this object's destruction will not invalidate other roots -/// for the same JS value. `Root`s cannot outlive the associated -/// `RootCollection` object. -#[allow_unrooted_interior] -pub struct Root<T: DomObject> { - /// Reference to rooted value that must not outlive this container - ptr: NonZero<*const T>, - /// List that ensures correct dynamic root ordering - root_list: *const RootCollection, -} - -impl<T: Castable> Root<T> { - /// Cast a DOM object root upwards to one of the interfaces it derives from. - pub fn upcast<U>(root: Root<T>) -> Root<U> - where U: Castable, - T: DerivedFrom<U> - { - unsafe { mem::transmute(root) } - } - - /// Cast a DOM object root downwards to one of the interfaces it might implement. - pub fn downcast<U>(root: Root<T>) -> Option<Root<U>> - where U: DerivedFrom<T> - { - if root.is::<U>() { - Some(unsafe { mem::transmute(root) }) - } else { - None - } - } -} - -impl<T: DomObject> Root<T> { - /// Create a new stack-bounded root for the provided JS-owned value. - /// It cannot outlive its associated `RootCollection`, and it gives - /// out references which cannot outlive this new `Root`. - pub fn new(unrooted: NonZero<*const T>) -> Root<T> { - debug_assert!(thread_state::get().is_script()); - STACK_ROOTS.with(|ref collection| { - let RootCollectionPtr(collection) = collection.get().unwrap(); - unsafe { (*collection).root(&*(**unrooted).reflector()) } - Root { - ptr: unrooted, - root_list: collection, - } - }) - } - - /// Generate a new root from a reference - pub fn from_ref(unrooted: &T) -> Root<T> { - Root::new(unsafe { NonZero::new(&*unrooted) }) - } -} - -impl<'root, T: DomObject + 'root> RootedReference<'root> for Root<T> { - type Ref = &'root T; - fn r(&'root self) -> &'root T { - self - } -} - -impl<T: DomObject> Deref for Root<T> { - type Target = T; - fn deref(&self) -> &T { - debug_assert!(thread_state::get().is_script()); - unsafe { &**self.ptr.deref() } - } -} - -impl<T: DomObject + HeapSizeOf> HeapSizeOf for Root<T> { - fn heap_size_of_children(&self) -> usize { - (**self).heap_size_of_children() - } -} - -impl<T: DomObject> PartialEq for Root<T> { - fn eq(&self, other: &Self) -> bool { - self.ptr == other.ptr - } -} - -impl<T: DomObject> Clone for Root<T> { - fn clone(&self) -> Root<T> { - Root::from_ref(&*self) - } -} - -impl<T: DomObject> Drop for Root<T> { - fn drop(&mut self) { - unsafe { - (*self.root_list).unroot(self.reflector()); - } - } -} - -unsafe impl<T: DomObject> JSTraceable for Root<T> { - unsafe fn trace(&self, _: *mut JSTracer) { - // Already traced. - } -} diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index 5d0cd864dad..8b3f9682544 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The code to expose the DOM to JavaScript through IDL bindings. //! @@ -139,20 +139,23 @@ pub mod constant; pub mod conversions; pub mod error; pub mod guard; +pub mod htmlconstructor; pub mod inheritance; pub mod interface; pub mod iterable; -pub mod js; -pub mod mozmap; pub mod namespace; pub mod num; pub mod proxyhandler; +pub mod record; pub mod refcounted; pub mod reflector; +pub mod root; +pub mod serializable; pub mod settings_stack; pub mod str; pub mod structuredclone; pub mod trace; +pub mod transferable; pub mod utils; pub mod weakref; pub mod xmlname; @@ -167,9 +170,6 @@ pub mod codegen { pub mod InterfaceObjectMap { include!(concat!(env!("OUT_DIR"), "/InterfaceObjectMap.rs")); } - pub mod InterfaceTypes { - include!(concat!(env!("OUT_DIR"), "/InterfaceTypes.rs")); - } #[allow(dead_code, unused_imports)] pub mod InheritTypes { include!(concat!(env!("OUT_DIR"), "/InheritTypes.rs")); diff --git a/components/script/dom/bindings/mozmap.rs b/components/script/dom/bindings/mozmap.rs deleted file mode 100644 index abfedf02caf..00000000000 --- a/components/script/dom/bindings/mozmap.rs +++ /dev/null @@ -1,110 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -//! The `MozMap` (open-ended dictionary) type. - -use dom::bindings::conversions::jsid_to_string; -use dom::bindings::str::DOMString; -use js::conversions::{FromJSValConvertible, ToJSValConvertible, ConversionResult}; -use js::jsapi::GetPropertyKeys; -use js::jsapi::HandleValue; -use js::jsapi::JSContext; -use js::jsapi::JSITER_OWNONLY; -use js::jsapi::JSPROP_ENUMERATE; -use js::jsapi::JS_DefineUCProperty2; -use js::jsapi::JS_GetPropertyById; -use js::jsapi::JS_NewPlainObject; -use js::jsapi::MutableHandleValue; -use js::jsval::ObjectValue; -use js::jsval::UndefinedValue; -use js::rust::IdVector; -use std::collections::HashMap; -use std::ops::Deref; - -/// The `MozMap` (open-ended dictionary) type. -#[derive(Clone, JSTraceable)] -pub struct MozMap<T> { - map: HashMap<DOMString, T>, -} - -impl<T> MozMap<T> { - /// Create an empty `MozMap`. - pub fn new() -> Self { - MozMap { - map: HashMap::new(), - } - } -} - -impl<T> Deref for MozMap<T> { - type Target = HashMap<DOMString, T>; - - fn deref(&self) -> &HashMap<DOMString, T> { - &self.map - } -} - -impl<T, C> FromJSValConvertible for MozMap<T> - where T: FromJSValConvertible<Config=C>, - C: Clone, -{ - type Config = C; - unsafe fn from_jsval(cx: *mut JSContext, value: HandleValue, config: C) - -> Result<ConversionResult<Self>, ()> { - if !value.is_object() { - return Ok(ConversionResult::Failure("MozMap value was not an object".into())); - } - - rooted!(in(cx) let object = value.to_object()); - let ids = IdVector::new(cx); - assert!(GetPropertyKeys(cx, object.handle(), JSITER_OWNONLY, ids.get())); - - let mut map = HashMap::new(); - for id in &*ids { - rooted!(in(cx) let id = *id); - - rooted!(in(cx) let mut property = UndefinedValue()); - if !JS_GetPropertyById(cx, object.handle(), id.handle(), property.handle_mut()) { - return Err(()); - } - - let property = match try!(T::from_jsval(cx, property.handle(), config.clone())) { - ConversionResult::Success(property) => property, - ConversionResult::Failure(message) => return Ok(ConversionResult::Failure(message)), - }; - - let key = jsid_to_string(cx, id.handle()).unwrap(); - map.insert(key, property); - } - - Ok(ConversionResult::Success(MozMap { - map: map, - })) - } -} - -impl<T: ToJSValConvertible> ToJSValConvertible for MozMap<T> { - #[inline] - unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { - rooted!(in(cx) let js_object = JS_NewPlainObject(cx)); - assert!(!js_object.handle().is_null()); - - rooted!(in(cx) let mut js_value = UndefinedValue()); - for (key, value) in &self.map { - let key = key.encode_utf16().collect::<Vec<_>>(); - value.to_jsval(cx, js_value.handle_mut()); - - assert!(JS_DefineUCProperty2(cx, - js_object.handle(), - key.as_ptr(), - key.len(), - js_value.handle(), - JSPROP_ENUMERATE, - None, - None)); - } - - rval.set(ObjectValue(js_object.handle().get())); - } -} diff --git a/components/script/dom/bindings/namespace.rs b/components/script/dom/bindings/namespace.rs index 38055dea180..64b7b0b2626 100644 --- a/components/script/dom/bindings/namespace.rs +++ b/components/script/dom/bindings/namespace.rs @@ -1,17 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Machinery to initialise namespace objects. -use dom::bindings::guard::Guard; -use dom::bindings::interface::{create_object, define_on_global_object}; -use js::jsapi::{HandleObject, JSClass, JSContext, JSFunctionSpec, MutableHandleObject}; -use libc; +use crate::dom::bindings::guard::Guard; +use crate::dom::bindings::interface::{create_object, define_on_global_object}; +use crate::script_runtime::JSContext; +use js::jsapi::{JSClass, JSFunctionSpec}; +use js::rust::{HandleObject, MutableHandleObject}; use std::ptr; /// The class of a namespace object. -#[derive(Copy, Clone)] +#[derive(Clone, Copy)] pub struct NamespaceObjectClass(JSClass); unsafe impl Sync for NamespaceObjectClass {} @@ -22,21 +23,24 @@ impl NamespaceObjectClass { NamespaceObjectClass(JSClass { name: name as *const _ as *const libc::c_char, flags: 0, - cOps: ptr::null_mut(), - reserved: [ptr::null_mut(); 3], + cOps: 0 as *mut _, + spec: ptr::null(), + ext: ptr::null(), + oOps: ptr::null(), }) } } /// Create a new namespace object. -pub unsafe fn create_namespace_object( - cx: *mut JSContext, - global: HandleObject, - proto: HandleObject, - class: &'static NamespaceObjectClass, - methods: &[Guard<&'static [JSFunctionSpec]>], - name: &[u8], - rval: MutableHandleObject) { - create_object(cx, proto, &class.0, methods, &[], &[], rval); +pub fn create_namespace_object( + cx: JSContext, + global: HandleObject, + proto: HandleObject, + class: &'static NamespaceObjectClass, + methods: &[Guard<&'static [JSFunctionSpec]>], + name: &[u8], + rval: MutableHandleObject, +) { + create_object(cx, global, proto, &class.0, methods, &[], &[], rval); define_on_global_object(cx, global, name, rval.handle()); } diff --git a/components/script/dom/bindings/num.rs b/components/script/dom/bindings/num.rs index 03b0c743f9f..c7f987e98ef 100644 --- a/components/script/dom/bindings/num.rs +++ b/components/script/dom/bindings/num.rs @@ -1,15 +1,16 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The `Finite<T>` struct. -use heapsize::HeapSizeOf; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use num_traits::Float; +use std::default::Default; use std::ops::Deref; /// Encapsulates the IDL restricted float type. -#[derive(JSTraceable, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, JSTraceable, PartialEq)] pub struct Finite<T: Float>(T); impl<T: Float> Finite<T> { @@ -25,8 +26,10 @@ impl<T: Float> Finite<T> { /// Create a new `Finite<T: Float>`. #[inline] pub fn wrap(value: T) -> Finite<T> { - assert!(value.is_finite(), - "Finite<T> doesn't encapsulate unrestricted value."); + assert!( + value.is_finite(), + "Finite<T> doesn't encapsulate unrestricted value." + ); Finite(value) } } @@ -40,8 +43,14 @@ impl<T: Float> Deref for Finite<T> { } } -impl<T: Float + HeapSizeOf> HeapSizeOf for Finite<T> { - fn heap_size_of_children(&self) -> usize { - (**self).heap_size_of_children() +impl<T: Float + MallocSizeOf> MallocSizeOf for Finite<T> { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + (**self).size_of(ops) + } +} + +impl<T: Float + Default> Default for Finite<T> { + fn default() -> Finite<T> { + Finite::wrap(T::default()) } } diff --git a/components/script/dom/bindings/proxyhandler.rs b/components/script/dom/bindings/proxyhandler.rs index 67c68927c9f..11913c03642 100644 --- a/components/script/dom/bindings/proxyhandler.rs +++ b/components/script/dom/bindings/proxyhandler.rs @@ -1,43 +1,46 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Utilities for the implementation of JSAPI proxy handlers. #![deny(missing_docs)] -use dom::bindings::conversions::is_dom_proxy; -use dom::bindings::utils::delete_property_by_id; -use js::glue::{GetProxyHandler, GetProxyHandlerFamily, SetProxyExtra}; -use js::glue::GetProxyExtra; -use js::glue::InvokeGetOwnPropertyDescriptor; -use js::jsapi::{DOMProxyShadowsResult, JSContext, JSObject, JSPROP_GETTER, PropertyDescriptor}; -use js::jsapi::{Handle, HandleId, HandleObject, MutableHandle, ObjectOpResult}; -use js::jsapi::{JSErrNum, JS_AlreadyHasOwnPropertyById, JS_StrictPropertyStub}; -use js::jsapi::{JS_DefinePropertyById, JS_NewObjectWithGivenProto, SetDOMProxyInformation}; -use js::jsapi::GetObjectProto; +use crate::dom::bindings::conversions::is_dom_proxy; +use crate::dom::bindings::utils::delete_property_by_id; +use js::glue::GetProxyHandlerFamily; +use js::glue::{GetProxyPrivate, SetProxyPrivate}; use js::jsapi::GetStaticPrototype; -use js::jsapi::JS_GetPropertyDescriptorById; -use js::jsapi::MutableHandleObject; +use js::jsapi::Handle as RawHandle; +use js::jsapi::HandleId as RawHandleId; +use js::jsapi::HandleObject as RawHandleObject; +use js::jsapi::JS_DefinePropertyById; +use js::jsapi::MutableHandleObject as RawMutableHandleObject; +use js::jsapi::ObjectOpResult; +use js::jsapi::{DOMProxyShadowsResult, JSContext, JSObject, PropertyDescriptor}; +use js::jsapi::{JSErrNum, SetDOMProxyInformation}; use js::jsval::ObjectValue; -use libc; -use std::{mem, ptr}; - - -static JSPROXYSLOT_EXPANDO: u32 = 0; +use js::jsval::UndefinedValue; +use js::rust::wrappers::JS_AlreadyHasOwnPropertyById; +use js::rust::wrappers::JS_NewObjectWithGivenProto; +use js::rust::{Handle, HandleObject, MutableHandle, MutableHandleObject}; +use std::ptr; /// Determine if this id shadows any existing properties for this proxy. -pub unsafe extern "C" fn shadow_check_callback(cx: *mut JSContext, - object: HandleObject, - id: HandleId) - -> DOMProxyShadowsResult { +pub unsafe extern "C" fn shadow_check_callback( + cx: *mut JSContext, + object: RawHandleObject, + id: RawHandleId, +) -> DOMProxyShadowsResult { // TODO: support OverrideBuiltins when #12978 is fixed. - rooted!(in(cx) let mut expando = ptr::null_mut()); + rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>()); get_expando_object(object, expando.handle_mut()); if !expando.get().is_null() { let mut has_own = false; - if !JS_AlreadyHasOwnPropertyById(cx, expando.handle(), id, &mut has_own) { + let raw_id = Handle::from_raw(id); + + if !JS_AlreadyHasOwnPropertyById(cx, expando.handle(), raw_id, &mut has_own) { return DOMProxyShadowsResult::ShadowCheckFailed; } @@ -52,89 +55,59 @@ pub unsafe extern "C" fn shadow_check_callback(cx: *mut JSContext, /// Initialize the infrastructure for DOM proxy objects. pub unsafe fn init() { - SetDOMProxyInformation(GetProxyHandlerFamily(), - JSPROXYSLOT_EXPANDO, - Some(shadow_check_callback)); -} - -/// Invoke the [[GetOwnProperty]] trap (`getOwnPropertyDescriptor`) on `proxy`, -/// with argument `id` and return the result, if it is not `undefined`. -/// Otherwise, walk along the prototype chain to find a property with that -/// name. -pub unsafe extern "C" fn get_property_descriptor(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - desc: MutableHandle<PropertyDescriptor>) - -> bool { - let handler = GetProxyHandler(proxy.get()); - if !InvokeGetOwnPropertyDescriptor(handler, cx, proxy, id, desc) { - return false; - } - if !desc.obj.is_null() { - return true; - } - - rooted!(in(cx) let mut proto = ptr::null_mut()); - if !GetObjectProto(cx, proxy, proto.handle_mut()) { - // FIXME(#11868) Should assign to desc.obj, desc.get() is a copy. - desc.get().obj = ptr::null_mut(); - return true; - } - - JS_GetPropertyDescriptorById(cx, proto.handle(), id, desc) + SetDOMProxyInformation( + GetProxyHandlerFamily(), + Some(shadow_check_callback), + ptr::null(), + ); } /// Defines an expando on the given `proxy`. -pub unsafe extern "C" fn define_property(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - desc: Handle<PropertyDescriptor>, - result: *mut ObjectOpResult) - -> bool { - // FIXME: Workaround for https://github.com/rust-lang/rfcs/issues/718 - let setter: *const libc::c_void = mem::transmute(desc.get().setter); - let setter_stub: unsafe extern fn(_, _, _, _, _) -> _ = JS_StrictPropertyStub; - let setter_stub: *const libc::c_void = mem::transmute(setter_stub); - if (desc.get().attrs & JSPROP_GETTER) != 0 && setter == setter_stub { - (*result).code_ = JSErrNum::JSMSG_GETTER_ONLY as ::libc::uintptr_t; - return true; - } - - rooted!(in(cx) let mut expando = ptr::null_mut()); +pub unsafe extern "C" fn define_property( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + desc: RawHandle<PropertyDescriptor>, + result: *mut ObjectOpResult, +) -> bool { + rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>()); ensure_expando_object(cx, proxy, expando.handle_mut()); - JS_DefinePropertyById(cx, expando.handle(), id, desc, result) + JS_DefinePropertyById(cx, expando.handle().into(), id, desc, result) } /// Deletes an expando off the given `proxy`. -pub unsafe extern "C" fn delete(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - bp: *mut ObjectOpResult) - -> bool { - rooted!(in(cx) let mut expando = ptr::null_mut()); +pub unsafe extern "C" fn delete( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + bp: *mut ObjectOpResult, +) -> bool { + rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>()); get_expando_object(proxy, expando.handle_mut()); if expando.is_null() { (*bp).code_ = 0 /* OkCode */; return true; } - delete_property_by_id(cx, expando.handle(), id, bp) + delete_property_by_id(cx, expando.handle(), Handle::from_raw(id), bp) } /// Controls whether the Extensible bit can be changed -pub unsafe extern "C" fn prevent_extensions(_cx: *mut JSContext, - _proxy: HandleObject, - result: *mut ObjectOpResult) - -> bool { +pub unsafe extern "C" fn prevent_extensions( + _cx: *mut JSContext, + _proxy: RawHandleObject, + result: *mut ObjectOpResult, +) -> bool { (*result).code_ = JSErrNum::JSMSG_CANT_PREVENT_EXTENSIONS as ::libc::uintptr_t; true } /// Reports whether the object is Extensible -pub unsafe extern "C" fn is_extensible(_cx: *mut JSContext, - _proxy: HandleObject, - succeeded: *mut bool) - -> bool { +pub unsafe extern "C" fn is_extensible( + _cx: *mut JSContext, + _proxy: RawHandleObject, + succeeded: *mut bool, +) -> bool { *succeeded = true; true } @@ -148,20 +121,22 @@ pub unsafe extern "C" fn is_extensible(_cx: *mut JSContext, /// This implementation always handles the case of the ordinary /// `[[GetPrototypeOf]]` behavior. An alternative implementation will be /// necessary for the Location object. -pub unsafe extern "C" fn get_prototype_if_ordinary(_: *mut JSContext, - proxy: HandleObject, - is_ordinary: *mut bool, - proto: MutableHandleObject) - -> bool { +pub unsafe extern "C" fn get_prototype_if_ordinary( + _: *mut JSContext, + proxy: RawHandleObject, + is_ordinary: *mut bool, + proto: RawMutableHandleObject, +) -> bool { *is_ordinary = true; proto.set(GetStaticPrototype(proxy.get())); true } /// Get the expando object, or null if there is none. -pub unsafe fn get_expando_object(obj: HandleObject, expando: MutableHandleObject) { +pub unsafe fn get_expando_object(obj: RawHandleObject, mut expando: MutableHandleObject) { assert!(is_dom_proxy(obj.get())); - let val = GetProxyExtra(obj.get(), JSPROXYSLOT_EXPANDO); + let ref mut val = UndefinedValue(); + GetProxyPrivate(obj.get(), val); expando.set(if val.is_undefined() { ptr::null_mut() } else { @@ -171,22 +146,32 @@ pub unsafe fn get_expando_object(obj: HandleObject, expando: MutableHandleObject /// Get the expando object, or create it if it doesn't exist yet. /// Fails on JSAPI failure. -pub unsafe fn ensure_expando_object(cx: *mut JSContext, obj: HandleObject, expando: MutableHandleObject) { +pub unsafe fn ensure_expando_object( + cx: *mut JSContext, + obj: RawHandleObject, + mut expando: MutableHandleObject, +) { assert!(is_dom_proxy(obj.get())); get_expando_object(obj, expando); if expando.is_null() { - expando.set(JS_NewObjectWithGivenProto(cx, ptr::null_mut(), HandleObject::null())); + expando.set(JS_NewObjectWithGivenProto( + cx, + ptr::null_mut(), + HandleObject::null(), + )); assert!(!expando.is_null()); - SetProxyExtra(obj.get(), JSPROXYSLOT_EXPANDO, &ObjectValue(expando.get())); + SetProxyPrivate(obj.get(), &ObjectValue(expando.get())); } } /// Set the property descriptor's object to `obj` and set it to enumerable, /// and writable if `readonly` is true. -pub fn fill_property_descriptor(mut desc: MutableHandle<PropertyDescriptor>, - obj: *mut JSObject, - attrs: u32) { +pub fn fill_property_descriptor( + mut desc: MutableHandle<PropertyDescriptor>, + obj: *mut JSObject, + attrs: u32, +) { desc.obj = obj; desc.attrs = attrs; desc.getter = None; diff --git a/components/script/dom/bindings/record.rs b/components/script/dom/bindings/record.rs new file mode 100644 index 00000000000..c54a2403d70 --- /dev/null +++ b/components/script/dom/bindings/record.rs @@ -0,0 +1,199 @@ +/* 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/. */ + +//! The `Record` (open-ended dictionary) type. + +use crate::dom::bindings::conversions::jsid_to_string; +use crate::dom::bindings::str::{ByteString, DOMString, USVString}; +use indexmap::IndexMap; +use js::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible}; +use js::jsapi::HandleId as RawHandleId; +use js::jsapi::JSContext; +use js::jsapi::JS_NewPlainObject; +use js::jsapi::PropertyDescriptor; +use js::jsapi::JSITER_HIDDEN; +use js::jsapi::JSITER_OWNONLY; +use js::jsapi::JSITER_SYMBOLS; +use js::jsapi::JSPROP_ENUMERATE; +use js::jsval::ObjectValue; +use js::jsval::UndefinedValue; +use js::rust::wrappers::GetPropertyKeys; +use js::rust::wrappers::JS_DefineUCProperty2; +use js::rust::wrappers::JS_GetOwnPropertyDescriptorById; +use js::rust::wrappers::JS_GetPropertyById; +use js::rust::wrappers::JS_IdToValue; +use js::rust::HandleId; +use js::rust::HandleValue; +use js::rust::IdVector; +use js::rust::MutableHandleValue; +use std::cmp::Eq; +use std::hash::Hash; +use std::marker::Sized; +use std::ops::Deref; + +pub trait RecordKey: Eq + Hash + Sized { + fn to_utf16_vec(&self) -> Vec<u16>; + unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()>; +} + +impl RecordKey for DOMString { + fn to_utf16_vec(&self) -> Vec<u16> { + self.encode_utf16().collect::<Vec<_>>() + } + + unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> { + match jsid_to_string(cx, id) { + Some(s) => Ok(ConversionResult::Success(s)), + None => Ok(ConversionResult::Failure("Failed to get DOMString".into())), + } + } +} + +impl RecordKey for USVString { + fn to_utf16_vec(&self) -> Vec<u16> { + self.0.encode_utf16().collect::<Vec<_>>() + } + + unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> { + rooted!(in(cx) let mut jsid_value = UndefinedValue()); + let raw_id: RawHandleId = id.into(); + JS_IdToValue(cx, *raw_id.ptr, jsid_value.handle_mut()); + + USVString::from_jsval(cx, jsid_value.handle(), ()) + } +} + +impl RecordKey for ByteString { + fn to_utf16_vec(&self) -> Vec<u16> { + self.iter().map(|&x| x as u16).collect::<Vec<u16>>() + } + + unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> { + rooted!(in(cx) let mut jsid_value = UndefinedValue()); + let raw_id: RawHandleId = id.into(); + JS_IdToValue(cx, *raw_id.ptr, jsid_value.handle_mut()); + + ByteString::from_jsval(cx, jsid_value.handle(), ()) + } +} + +/// The `Record` (open-ended dictionary) type. +#[derive(Clone, JSTraceable)] +pub struct Record<K: RecordKey, V> { + map: IndexMap<K, V>, +} + +impl<K: RecordKey, V> Record<K, V> { + /// Create an empty `Record`. + pub fn new() -> Self { + Record { + map: IndexMap::new(), + } + } +} + +impl<K: RecordKey, V> Deref for Record<K, V> { + type Target = IndexMap<K, V>; + + fn deref(&self) -> &IndexMap<K, V> { + &self.map + } +} + +impl<K, V, C> FromJSValConvertible for Record<K, V> +where + K: RecordKey, + V: FromJSValConvertible<Config = C>, + C: Clone, +{ + type Config = C; + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + config: C, + ) -> Result<ConversionResult<Self>, ()> { + if !value.is_object() { + return Ok(ConversionResult::Failure( + "Record value was not an object".into(), + )); + } + + rooted!(in(cx) let object = value.to_object()); + let mut ids = IdVector::new(cx); + if !GetPropertyKeys( + cx, + object.handle(), + JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, + ids.handle_mut(), + ) { + return Err(()); + } + + let mut map = IndexMap::new(); + for id in &*ids { + rooted!(in(cx) let id = *id); + rooted!(in(cx) let mut desc = PropertyDescriptor::default()); + + if !JS_GetOwnPropertyDescriptorById(cx, object.handle(), id.handle(), desc.handle_mut()) + { + return Err(()); + } + + if (JSPROP_ENUMERATE as u32) & desc.attrs == 0 { + continue; + } + + let key = match K::from_id(cx, id.handle())? { + ConversionResult::Success(key) => key, + ConversionResult::Failure(message) => { + return Ok(ConversionResult::Failure(message)) + }, + }; + + rooted!(in(cx) let mut property = UndefinedValue()); + if !JS_GetPropertyById(cx, object.handle(), id.handle(), property.handle_mut()) { + return Err(()); + } + + let property = match V::from_jsval(cx, property.handle(), config.clone())? { + ConversionResult::Success(property) => property, + ConversionResult::Failure(message) => { + return Ok(ConversionResult::Failure(message)) + }, + }; + map.insert(key, property); + } + + Ok(ConversionResult::Success(Record { map: map })) + } +} + +impl<K, V> ToJSValConvertible for Record<K, V> +where + K: RecordKey, + V: ToJSValConvertible, +{ + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) { + rooted!(in(cx) let js_object = JS_NewPlainObject(cx)); + assert!(!js_object.handle().is_null()); + + rooted!(in(cx) let mut js_value = UndefinedValue()); + for (key, value) in &self.map { + let key = key.to_utf16_vec(); + value.to_jsval(cx, js_value.handle_mut()); + + assert!(JS_DefineUCProperty2( + cx, + js_object.handle(), + key.as_ptr(), + key.len(), + js_value.handle(), + JSPROP_ENUMERATE as u32 + )); + } + + rval.set(ObjectValue(js_object.handle().get())); + } +} diff --git a/components/script/dom/bindings/refcounted.rs b/components/script/dom/bindings/refcounted.rs index dcf11626543..ab7aea3e922 100644 --- a/components/script/dom/bindings/refcounted.rs +++ b/components/script/dom/bindings/refcounted.rs @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! A generic, safe mechanism by which DOM objects can be pinned and transferred //! between threads (or intra-thread for asynchronous events). Akin to Gecko's @@ -11,7 +11,7 @@ //! To guarantee the lifetime of a DOM object when performing asynchronous operations, //! obtain a `Trusted<T>` from that object and pass it along with each operation. //! A usable pointer to the original DOM object can be obtained on the script thread -//! from a `Trusted<T>` via the `to_temporary` method. +//! from a `Trusted<T>` via the `root` method. //! //! The implementation of `Trusted<T>` is as follows: //! The `Trusted<T>` object contains an atomic reference counted pointer to the Rust DOM object. @@ -22,41 +22,43 @@ //! its hash table during the next GC. During GC, the entries of the hash table are counted //! as JS roots. -use core::nonzero::NonZero; -use dom::bindings::js::Root; -use dom::bindings::reflector::{DomObject, Reflector}; -use dom::bindings::trace::trace_reflector; -use dom::promise::Promise; +use crate::dom::bindings::conversions::ToJSValConvertible; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::reflector::{DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::trace::trace_reflector; +use crate::dom::promise::Promise; +use crate::task::TaskOnce; use js::jsapi::JSTracer; -use libc; use std::cell::RefCell; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::HashMap; use std::hash::Hash; use std::marker::PhantomData; -use std::os; use std::rc::Rc; use std::sync::{Arc, Weak}; - -#[allow(missing_docs)] // FIXME -mod dummy { // Attributes don’t apply through the macro. +#[allow(missing_docs)] // FIXME +mod dummy { + // Attributes don’t apply through the macro. + use super::LiveDOMReferences; use std::cell::RefCell; use std::rc::Rc; - use super::LiveDOMReferences; thread_local!(pub static LIVE_REFERENCES: Rc<RefCell<Option<LiveDOMReferences>>> = Rc::new(RefCell::new(None))); } pub use self::dummy::LIVE_REFERENCES; - /// A pointer to a Rust DOM object that needs to be destroyed. -pub struct TrustedReference(*const libc::c_void); +struct TrustedReference(*const libc::c_void); unsafe impl Send for TrustedReference {} impl TrustedReference { - fn new<T: DomObject>(ptr: *const T) -> TrustedReference { - TrustedReference(ptr as *const libc::c_void) + /// Creates a new TrustedReference from a pointer to a value that impements DOMObject. + /// This is not enforced by the type system to reduce duplicated generic code, + /// which is acceptable since this method is internal to this module. + unsafe fn new(ptr: *const libc::c_void) -> TrustedReference { + TrustedReference(ptr) } } @@ -92,41 +94,72 @@ impl TrustedPromise { /// Obtain a usable DOM Promise from a pinned `TrustedPromise` value. Fails if used on /// a different thread than the original value from which this `TrustedPromise` was /// obtained. - #[allow(unrooted_must_root)] pub fn root(self) -> Rc<Promise> { LIVE_REFERENCES.with(|ref r| { let r = r.borrow(); let live_references = r.as_ref().unwrap(); - assert!(self.owner_thread == (&*live_references) as *const _ as *const libc::c_void); + assert_eq!( + self.owner_thread, + (&*live_references) as *const _ as *const libc::c_void + ); // Borrow-check error requires the redundant `let promise = ...; promise` here. - let promise = match live_references.promise_table.borrow_mut().entry(self.dom_object) { + let promise = match live_references + .promise_table + .borrow_mut() + .entry(self.dom_object) + { Occupied(mut entry) => { let promise = { let promises = entry.get_mut(); - promises.pop().expect("rooted promise list unexpectedly empty") + promises + .pop() + .expect("rooted promise list unexpectedly empty") }; if entry.get().is_empty() { entry.remove(); } promise - } + }, Vacant(_) => unreachable!(), }; promise }) } + + /// A task which will reject the promise. + #[allow(unrooted_must_root)] + pub fn reject_task(self, error: Error) -> impl TaskOnce { + let this = self; + task!(reject_promise: move || { + debug!("Rejecting promise."); + this.root().reject_error(error); + }) + } + + /// A task which will resolve the promise. + #[allow(unrooted_must_root)] + pub fn resolve_task<T>(self, value: T) -> impl TaskOnce + where + T: ToJSValConvertible + Send, + { + let this = self; + task!(resolve_promise: move || { + debug!("Resolving promise."); + this.root().resolve_native(&value); + }) + } } /// A safe wrapper around a raw pointer to a DOM object that can be /// shared among threads for use in asynchronous operations. The underlying /// DOM object is guaranteed to live at least as long as the last outstanding /// `Trusted<T>` instance. -#[allow_unrooted_interior] +#[unrooted_must_root_lint::allow_unrooted_interior] pub struct Trusted<T: DomObject> { /// A pointer to the Rust DOM object of type T, but void to allow /// sending `Trusted<T>` between threads, regardless of T's sendability. refcount: Arc<TrustedReference>, - owner_thread: *const libc::c_void, + owner_thread: *const LiveDOMReferences, phantom: PhantomData<T>, } @@ -137,30 +170,38 @@ impl<T: DomObject> Trusted<T> { /// be prevented from being GCed for the duration of the resulting `Trusted<T>` object's /// lifetime. pub fn new(ptr: &T) -> Trusted<T> { - LIVE_REFERENCES.with(|ref r| { - let r = r.borrow(); - let live_references = r.as_ref().unwrap(); - let refcount = live_references.addref(&*ptr as *const T); - Trusted { - refcount: refcount, - owner_thread: (&*live_references) as *const _ as *const libc::c_void, - phantom: PhantomData, - } - }) + fn add_live_reference( + ptr: *const libc::c_void, + ) -> (Arc<TrustedReference>, *const LiveDOMReferences) { + LIVE_REFERENCES.with(|ref r| { + let r = r.borrow(); + let live_references = r.as_ref().unwrap(); + let refcount = unsafe { live_references.addref(ptr) }; + (refcount, live_references as *const _) + }) + } + + let (refcount, owner_thread) = add_live_reference(ptr as *const T as *const _); + Trusted { + refcount, + owner_thread, + phantom: PhantomData, + } } /// Obtain a usable DOM pointer from a pinned `Trusted<T>` value. Fails if used on /// a different thread than the original value from which this `Trusted<T>` was /// obtained. - pub fn root(&self) -> Root<T> { - assert!(LIVE_REFERENCES.with(|ref r| { - let r = r.borrow(); - let live_references = r.as_ref().unwrap(); - self.owner_thread == (&*live_references) as *const _ as *const libc::c_void - })); - unsafe { - Root::new(NonZero::new(self.refcount.0 as *const T)) + pub fn root(&self) -> DomRoot<T> { + fn validate(owner_thread: *const LiveDOMReferences) { + assert!(LIVE_REFERENCES.with(|ref r| { + let r = r.borrow(); + let live_references = r.as_ref().unwrap(); + owner_thread == live_references + })); } + validate(self.owner_thread); + unsafe { DomRoot::from_ref(&*(self.refcount.0 as *const T)) } } } @@ -194,13 +235,22 @@ impl LiveDOMReferences { }); } + pub fn destruct() { + LIVE_REFERENCES.with(|ref r| { + *r.borrow_mut() = None; + }); + } + #[allow(unrooted_must_root)] fn addref_promise(&self, promise: Rc<Promise>) { let mut table = self.promise_table.borrow_mut(); table.entry(&*promise).or_insert(vec![]).push(promise) } - fn addref<T: DomObject>(&self, ptr: *const T) -> Arc<TrustedReference> { + /// ptr must be a pointer to a type that implements DOMObject. + /// This is not enforced by the type system to reduce duplicated generic code, + /// which is acceptable since this method is internal to this module. + unsafe fn addref(&self, ptr: *const libc::c_void) -> Arc<TrustedReference> { let mut table = self.reflectable_table.borrow_mut(); let capacity = table.capacity(); let len = table.len(); @@ -209,7 +259,7 @@ impl LiveDOMReferences { remove_nulls(&mut table); table.reserve(len); } - match table.entry(ptr as *const libc::c_void) { + match table.entry(ptr) { Occupied(mut entry) => match entry.get().upgrade() { Some(refcount) => refcount, None => { @@ -222,15 +272,15 @@ impl LiveDOMReferences { let refcount = Arc::new(TrustedReference::new(ptr)); entry.insert(Arc::downgrade(&refcount)); refcount - } + }, } } } /// Remove null entries from the live references table -fn remove_nulls<K: Eq + Hash + Clone, V> (table: &mut HashMap<K, Weak<V>>) { - let to_remove: Vec<K> = - table.iter() +fn remove_nulls<K: Eq + Hash + Clone, V>(table: &mut HashMap<K, Weak<V>>) { + let to_remove: Vec<K> = table + .iter() .filter(|&(_, value)| Weak::upgrade(value).is_none()) .map(|(key, _)| key.clone()) .collect(); @@ -242,8 +292,7 @@ fn remove_nulls<K: Eq + Hash + Clone, V> (table: &mut HashMap<K, Weak<V>>) { /// A JSTraceDataOp for tracing reflectors held in LIVE_REFERENCES #[allow(unrooted_must_root)] -pub unsafe extern "C" fn trace_refcounted_objects(tracer: *mut JSTracer, - _data: *mut os::raw::c_void) { +pub unsafe fn trace_refcounted_objects(tracer: *mut JSTracer) { info!("tracing live refcounted references"); LIVE_REFERENCES.with(|ref r| { let r = r.borrow(); diff --git a/components/script/dom/bindings/reflector.rs b/components/script/dom/bindings/reflector.rs index 896cf74a34f..04bf9fdbbed 100644 --- a/components/script/dom/bindings/reflector.rs +++ b/components/script/dom/bindings/reflector.rs @@ -1,37 +1,37 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The `Reflector` struct. -use dom::bindings::conversions::DerivedFrom; -use dom::bindings::js::Root; -use dom::globalscope::GlobalScope; -use js::jsapi::{HandleObject, JSContext, JSObject, Heap}; +use crate::dom::bindings::conversions::DerivedFrom; +use crate::dom::bindings::iterable::{Iterable, IterableIterator}; +use crate::dom::bindings::root::{Dom, DomRoot, Root}; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; +use js::jsapi::{Heap, JSObject}; +use js::rust::HandleObject; use std::default::Default; /// Create the reflector for a new DOM object and yield ownership to the /// reflector. -pub fn reflect_dom_object<T, U>( - obj: Box<T>, - global: &U, - wrap_fn: unsafe fn(*mut JSContext, &GlobalScope, Box<T>) -> Root<T>) - -> Root<T> - where T: DomObject, U: DerivedFrom<GlobalScope> +pub fn reflect_dom_object<T, U>(obj: Box<T>, global: &U) -> DomRoot<T> +where + T: DomObject + DomObjectWrap, + U: DerivedFrom<GlobalScope>, { let global_scope = global.upcast(); - unsafe { - wrap_fn(global_scope.get_cx(), global_scope, obj) - } + unsafe { T::WRAP(global_scope.get_cx(), global_scope, obj) } } /// A struct to store a reference to the reflector of a DOM object. #[allow(unrooted_must_root)] -#[derive(HeapSizeOf)] -#[must_root] +#[derive(MallocSizeOf)] +#[unrooted_must_root_lint::must_root] // If you're renaming or moving this field, update the path in plugins::reflector as well pub struct Reflector { - #[ignore_heap_size_of = "defined and measured in rust-mozjs"] + #[ignore_malloc_size_of = "defined and measured in rust-mozjs"] object: Heap<*mut JSObject>, } @@ -46,11 +46,12 @@ impl Reflector { /// Get the reflector. #[inline] pub fn get_jsobject(&self) -> HandleObject { - self.object.handle() + // We're rooted, so it's safe to hand out a handle to object in Heap + unsafe { HandleObject::from_raw(self.object.handle()) } } /// Initialize the reflector. (May be called only once.) - pub fn set_jsobject(&mut self, object: *mut JSObject) { + pub unsafe fn set_jsobject(&self, object: *mut JSObject) { assert!(self.object.get().is_null()); assert!(!object.is_null()); self.object.set(object); @@ -72,12 +73,15 @@ impl Reflector { } /// A trait to provide access to the `Reflector` for a DOM object. -pub trait DomObject { +pub trait DomObject: JSTraceable + 'static { /// Returns the receiver's reflector. fn reflector(&self) -> &Reflector; /// Returns the global scope of the realm that the DomObject was created in. - fn global(&self) -> Root<GlobalScope> where Self: Sized { + fn global(&self) -> DomRoot<GlobalScope> + where + Self: Sized, + { GlobalScope::from_reflector(self) } } @@ -91,11 +95,28 @@ impl DomObject for Reflector { /// A trait to initialize the `Reflector` for a DOM object. pub trait MutDomObject: DomObject { /// Initializes the Reflector - fn init_reflector(&mut self, obj: *mut JSObject); + unsafe fn init_reflector(&self, obj: *mut JSObject); } impl MutDomObject for Reflector { - fn init_reflector(&mut self, obj: *mut JSObject) { + unsafe fn init_reflector(&self, obj: *mut JSObject) { self.set_jsobject(obj) } } + +/// A trait to provide a function pointer to wrap function for DOM objects. +pub trait DomObjectWrap: Sized + DomObject { + /// Function pointer to the general wrap function type + const WRAP: unsafe fn(JSContext, &GlobalScope, Box<Self>) -> Root<Dom<Self>>; +} + +/// A trait to provide a function pointer to wrap function for +/// DOM iterator interfaces. +pub trait DomObjectIteratorWrap: DomObjectWrap + JSTraceable + Iterable { + /// Function pointer to the wrap function for IterableIterator<T> + const ITER_WRAP: unsafe fn( + JSContext, + &GlobalScope, + Box<IterableIterator<Self>>, + ) -> Root<Dom<IterableIterator<Self>>>; +} diff --git a/components/script/dom/bindings/root.rs b/components/script/dom/bindings/root.rs new file mode 100644 index 00000000000..b0b7eeb146d --- /dev/null +++ b/components/script/dom/bindings/root.rs @@ -0,0 +1,799 @@ +/* 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/. */ + +//! Smart pointers for the JS-managed DOM objects. +//! +//! The DOM is made up of DOM objects whose lifetime is entirely controlled by +//! the whims of the SpiderMonkey garbage collector. The types in this module +//! are designed to ensure that any interactions with said Rust types only +//! occur on values that will remain alive the entire time. +//! +//! Here is a brief overview of the important types: +//! +//! - `Root<T>`: a stack-based rooted value. +//! - `DomRoot<T>`: a stack-based reference to a rooted DOM object. +//! - `Dom<T>`: a reference to a DOM object that can automatically be traced by +//! the GC when encountered as a field of a Rust structure. +//! +//! `Dom<T>` does not allow access to their inner value without explicitly +//! creating a stack-based root via the `root` method. This returns a `DomRoot<T>`, +//! which causes the JS-owned value to be uncollectable for the duration of the +//! `Root` object's lifetime. A reference to the object can then be obtained +//! from the `Root` object. These references are not allowed to outlive their +//! originating `DomRoot<T>`. +//! + +use crate::dom::bindings::conversions::DerivedFrom; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{DomObject, MutDomObject, Reflector}; +use crate::dom::bindings::trace::trace_reflector; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::node::Node; +use js::jsapi::{Heap, JSObject, JSTracer}; +use js::rust::GCMethods; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use mitochondria::OnceCell; +use script_layout_interface::TrustedNodeAddress; +use std::cell::{Cell, UnsafeCell}; +use std::default::Default; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::mem; +use std::ops::Deref; +use std::ptr; +use style::thread_state; + +/// A rooted value. +#[allow(unrooted_must_root)] +#[unrooted_must_root_lint::allow_unrooted_interior] +pub struct Root<T: StableTraceObject> { + /// The value to root. + value: T, + /// List that ensures correct dynamic root ordering + root_list: *const RootCollection, +} + +impl<T> Root<T> +where + T: StableTraceObject + 'static, +{ + /// Create a new stack-bounded root for the provided value. + /// It cannot outlive its associated `RootCollection`, and it gives + /// out references which cannot outlive this new `Root`. + #[allow(unrooted_must_root)] + pub unsafe fn new(value: T) -> Self { + unsafe fn add_to_root_list(object: *const dyn JSTraceable) -> *const RootCollection { + assert_in_script(); + STACK_ROOTS.with(|ref root_list| { + let root_list = &*root_list.get().unwrap(); + root_list.root(object); + root_list + }) + } + + let root_list = add_to_root_list(value.stable_trace_object()); + Root { value, root_list } + } +} + +/// Represents values that can be rooted through a stable address that will +/// not change for their whole lifetime. +pub unsafe trait StableTraceObject { + /// Returns a stable trace object which address won't change for the whole + /// lifetime of the value. + fn stable_trace_object(&self) -> *const dyn JSTraceable; +} + +unsafe impl<T> StableTraceObject for Dom<T> +where + T: DomObject, +{ + fn stable_trace_object<'a>(&'a self) -> *const dyn JSTraceable { + // The JSTraceable impl for Reflector doesn't actually do anything, + // so we need this shenanigan to actually trace the reflector of the + // T pointer in Dom<T>. + #[allow(unrooted_must_root)] + struct ReflectorStackRoot(Reflector); + unsafe impl JSTraceable for ReflectorStackRoot { + unsafe fn trace(&self, tracer: *mut JSTracer) { + trace_reflector(tracer, "on stack", &self.0); + } + } + unsafe { &*(self.reflector() as *const Reflector as *const ReflectorStackRoot) } + } +} + +unsafe impl<T> StableTraceObject for MaybeUnreflectedDom<T> +where + T: DomObject, +{ + fn stable_trace_object<'a>(&'a self) -> *const dyn JSTraceable { + // The JSTraceable impl for Reflector doesn't actually do anything, + // so we need this shenanigan to actually trace the reflector of the + // T pointer in Dom<T>. + #[allow(unrooted_must_root)] + struct MaybeUnreflectedStackRoot<T>(T); + unsafe impl<T> JSTraceable for MaybeUnreflectedStackRoot<T> + where + T: DomObject, + { + unsafe fn trace(&self, tracer: *mut JSTracer) { + if self.0.reflector().get_jsobject().is_null() { + self.0.trace(tracer); + } else { + trace_reflector(tracer, "on stack", &self.0.reflector()); + } + } + } + unsafe { &*(self.ptr.as_ptr() as *const T as *const MaybeUnreflectedStackRoot<T>) } + } +} + +impl<T> Deref for Root<T> +where + T: Deref + StableTraceObject, +{ + type Target = <T as Deref>::Target; + + fn deref(&self) -> &Self::Target { + assert_in_script(); + &self.value + } +} + +impl<T> Drop for Root<T> +where + T: StableTraceObject, +{ + fn drop(&mut self) { + unsafe { + (*self.root_list).unroot(self.value.stable_trace_object()); + } + } +} + +/// A rooted reference to a DOM object. +pub type DomRoot<T> = Root<Dom<T>>; + +impl<T: Castable> DomRoot<T> { + /// Cast a DOM object root upwards to one of the interfaces it derives from. + pub fn upcast<U>(root: DomRoot<T>) -> DomRoot<U> + where + U: Castable, + T: DerivedFrom<U>, + { + unsafe { mem::transmute(root) } + } + + /// Cast a DOM object root downwards to one of the interfaces it might implement. + pub fn downcast<U>(root: DomRoot<T>) -> Option<DomRoot<U>> + where + U: DerivedFrom<T>, + { + if root.is::<U>() { + Some(unsafe { mem::transmute(root) }) + } else { + None + } + } +} + +impl<T: DomObject> DomRoot<T> { + /// Generate a new root from a reference + pub fn from_ref(unrooted: &T) -> DomRoot<T> { + unsafe { DomRoot::new(Dom::from_ref(unrooted)) } + } +} + +impl<T> MallocSizeOf for DomRoot<T> +where + T: DomObject + MallocSizeOf, +{ + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + (**self).size_of(ops) + } +} + +impl<T> PartialEq for DomRoot<T> +where + T: DomObject, +{ + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl<T> Clone for DomRoot<T> +where + T: DomObject, +{ + fn clone(&self) -> DomRoot<T> { + DomRoot::from_ref(&*self) + } +} + +unsafe impl<T> JSTraceable for DomRoot<T> +where + T: DomObject, +{ + unsafe fn trace(&self, _: *mut JSTracer) { + // Already traced. + } +} + +/// A rooting mechanism for reflectors on the stack. +/// LIFO is not required. +/// +/// See also [*Exact Stack Rooting - Storing a GCPointer on the CStack*] +/// (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/GC/Exact_Stack_Rooting). +pub struct RootCollection { + roots: UnsafeCell<Vec<*const dyn JSTraceable>>, +} + +thread_local!(static STACK_ROOTS: Cell<Option<*const RootCollection>> = Cell::new(None)); + +pub struct ThreadLocalStackRoots<'a>(PhantomData<&'a u32>); + +impl<'a> ThreadLocalStackRoots<'a> { + pub fn new(roots: &'a RootCollection) -> Self { + STACK_ROOTS.with(|ref r| r.set(Some(roots))); + ThreadLocalStackRoots(PhantomData) + } +} + +impl<'a> Drop for ThreadLocalStackRoots<'a> { + fn drop(&mut self) { + STACK_ROOTS.with(|ref r| r.set(None)); + } +} + +impl RootCollection { + /// Create an empty collection of roots + pub fn new() -> RootCollection { + assert_in_script(); + RootCollection { + roots: UnsafeCell::new(vec![]), + } + } + + /// Starts tracking a trace object. + unsafe fn root(&self, object: *const dyn JSTraceable) { + assert_in_script(); + (*self.roots.get()).push(object); + } + + /// Stops tracking a trace object, asserting if it isn't found. + unsafe fn unroot(&self, object: *const dyn JSTraceable) { + assert_in_script(); + let roots = &mut *self.roots.get(); + match roots + .iter() + .rposition(|r| *r as *const () == object as *const ()) + { + Some(idx) => { + roots.remove(idx); + }, + None => panic!("Can't remove a root that was never rooted!"), + } + } +} + +/// SM Callback that traces the rooted reflectors +pub unsafe fn trace_roots(tracer: *mut JSTracer) { + debug!("tracing stack roots"); + STACK_ROOTS.with(|ref collection| { + let collection = &*(*collection.get().unwrap()).roots.get(); + for root in collection { + (**root).trace(tracer); + } + }); +} + +/// Get a slice of references to DOM objects. +pub trait DomSlice<T> +where + T: JSTraceable + DomObject, +{ + /// Returns the slice of `T` references. + fn r(&self) -> &[&T]; +} + +impl<T> DomSlice<T> for [Dom<T>] +where + T: JSTraceable + DomObject, +{ + #[inline] + fn r(&self) -> &[&T] { + let _ = mem::transmute::<Dom<T>, &T>; + unsafe { &*(self as *const [Dom<T>] as *const [&T]) } + } +} + +/// A traced reference to a DOM object +/// +/// This type is critical to making garbage collection work with the DOM, +/// but it is very dangerous; if garbage collection happens with a `Dom<T>` +/// on the stack, the `Dom<T>` can point to freed memory. +/// +/// This should only be used as a field in other DOM objects. +#[unrooted_must_root_lint::must_root] +pub struct Dom<T> { + ptr: ptr::NonNull<T>, +} + +// Dom<T> is similar to Rc<T>, in that it's not always clear how to avoid double-counting. +// For now, we choose not to follow any such pointers. +impl<T> MallocSizeOf for Dom<T> { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + 0 + } +} + +impl<T> Dom<T> { + /// Returns `LayoutDom<T>` containing the same pointer. + pub unsafe fn to_layout(&self) -> LayoutDom<T> { + assert_in_layout(); + LayoutDom { + value: self.ptr.as_ref(), + } + } +} + +impl<T: DomObject> Dom<T> { + /// Create a Dom<T> from a &T + #[allow(unrooted_must_root)] + pub fn from_ref(obj: &T) -> Dom<T> { + assert_in_script(); + Dom { + ptr: ptr::NonNull::from(obj), + } + } +} + +impl<T: DomObject> Deref for Dom<T> { + type Target = T; + + fn deref(&self) -> &T { + assert_in_script(); + // We can only have &Dom<T> from a rooted thing, so it's safe to deref + // it to &T. + unsafe { &*self.ptr.as_ptr() } + } +} + +unsafe impl<T: DomObject> JSTraceable for Dom<T> { + unsafe fn trace(&self, trc: *mut JSTracer) { + let trace_string; + let trace_info = if cfg!(debug_assertions) { + trace_string = format!("for {} on heap", ::std::any::type_name::<T>()); + &trace_string[..] + } else { + "for DOM object on heap" + }; + trace_reflector(trc, trace_info, (*self.ptr.as_ptr()).reflector()); + } +} + +/// A traced reference to a DOM object that may not be reflected yet. +#[unrooted_must_root_lint::must_root] +pub struct MaybeUnreflectedDom<T> { + ptr: ptr::NonNull<T>, +} + +impl<T> MaybeUnreflectedDom<T> +where + T: DomObject, +{ + #[allow(unrooted_must_root)] + pub unsafe fn from_box(value: Box<T>) -> Self { + Self { + ptr: Box::leak(value).into(), + } + } +} + +impl<T> Root<MaybeUnreflectedDom<T>> +where + T: DomObject, +{ + pub fn as_ptr(&self) -> *const T { + self.value.ptr.as_ptr() + } +} + +impl<T> Root<MaybeUnreflectedDom<T>> +where + T: MutDomObject, +{ + pub unsafe fn reflect_with(self, obj: *mut JSObject) -> DomRoot<T> { + let ptr = self.as_ptr(); + drop(self); + let root = DomRoot::from_ref(&*ptr); + root.init_reflector(obj); + root + } +} + +/// An unrooted reference to a DOM object for use in layout. `Layout*Helpers` +/// traits must be implemented on this. +#[unrooted_must_root_lint::allow_unrooted_interior] +pub struct LayoutDom<'dom, T> { + value: &'dom T, +} + +impl<'dom, T> LayoutDom<'dom, T> +where + T: Castable, +{ + /// Cast a DOM object root upwards to one of the interfaces it derives from. + pub fn upcast<U>(&self) -> LayoutDom<'dom, U> + where + U: Castable, + T: DerivedFrom<U>, + { + assert_in_layout(); + LayoutDom { + value: self.value.upcast::<U>(), + } + } + + /// Cast a DOM object downwards to one of the interfaces it might implement. + pub fn downcast<U>(&self) -> Option<LayoutDom<'dom, U>> + where + U: DerivedFrom<T>, + { + assert_in_layout(); + self.value.downcast::<U>().map(|value| LayoutDom { value }) + } + + /// Returns whether this inner object is a U. + pub fn is<U>(&self) -> bool + where + U: DerivedFrom<T>, + { + assert_in_layout(); + self.value.is::<U>() + } +} + +impl<T> LayoutDom<'_, T> +where + T: DomObject, +{ + /// Get the reflector. + pub unsafe fn get_jsobject(&self) -> *mut JSObject { + assert_in_layout(); + self.value.reflector().get_jsobject().get() + } +} + +impl<T> Copy for LayoutDom<'_, T> {} + +impl<T> PartialEq for Dom<T> { + fn eq(&self, other: &Dom<T>) -> bool { + self.ptr.as_ptr() == other.ptr.as_ptr() + } +} + +impl<'a, T: DomObject> PartialEq<&'a T> for Dom<T> { + fn eq(&self, other: &&'a T) -> bool { + *self == Dom::from_ref(*other) + } +} + +impl<T> Eq for Dom<T> {} + +impl<T> PartialEq for LayoutDom<'_, T> { + fn eq(&self, other: &Self) -> bool { + self.value as *const T == other.value as *const T + } +} + +impl<T> Eq for LayoutDom<'_, T> {} + +impl<T> Hash for Dom<T> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.ptr.as_ptr().hash(state) + } +} + +impl<T> Hash for LayoutDom<'_, T> { + fn hash<H: Hasher>(&self, state: &mut H) { + (self.value as *const T).hash(state) + } +} + +impl<T> Clone for Dom<T> { + #[inline] + #[allow(unrooted_must_root)] + fn clone(&self) -> Self { + assert_in_script(); + Dom { + ptr: self.ptr.clone(), + } + } +} + +impl<T> Clone for LayoutDom<'_, T> { + #[inline] + fn clone(&self) -> Self { + assert_in_layout(); + LayoutDom { value: self.value } + } +} + +impl LayoutDom<'_, Node> { + /// Create a new JS-owned value wrapped from an address known to be a + /// `Node` pointer. + pub unsafe fn from_trusted_node_address(inner: TrustedNodeAddress) -> Self { + assert_in_layout(); + let TrustedNodeAddress(addr) = inner; + LayoutDom { + value: &*(addr as *const Node), + } + } +} + +/// A holder that provides interior mutability for GC-managed values such as +/// `Dom<T>`. Essentially a `Cell<Dom<T>>`, but safer. +/// +/// This should only be used as a field in other DOM objects; see warning +/// on `Dom<T>`. +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable)] +pub struct MutDom<T: DomObject> { + val: UnsafeCell<Dom<T>>, +} + +impl<T: DomObject> MutDom<T> { + /// Create a new `MutDom`. + pub fn new(initial: &T) -> MutDom<T> { + assert_in_script(); + MutDom { + val: UnsafeCell::new(Dom::from_ref(initial)), + } + } + + /// Set this `MutDom` to the given value. + pub fn set(&self, val: &T) { + assert_in_script(); + unsafe { + *self.val.get() = Dom::from_ref(val); + } + } + + /// Get the value in this `MutDom`. + pub fn get(&self) -> DomRoot<T> { + assert_in_script(); + unsafe { DomRoot::from_ref(&*ptr::read(self.val.get())) } + } +} + +impl<T: DomObject> MallocSizeOf for MutDom<T> { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + // See comment on MallocSizeOf for Dom<T>. + 0 + } +} + +impl<T: DomObject> PartialEq for MutDom<T> { + fn eq(&self, other: &Self) -> bool { + unsafe { *self.val.get() == *other.val.get() } + } +} + +impl<T: DomObject + PartialEq> PartialEq<T> for MutDom<T> { + fn eq(&self, other: &T) -> bool { + unsafe { **self.val.get() == *other } + } +} + +pub(crate) fn assert_in_script() { + debug_assert!(thread_state::get().is_script()); +} + +pub(crate) fn assert_in_layout() { + debug_assert!(thread_state::get().is_layout()); +} + +/// A holder that provides interior mutability for GC-managed values such as +/// `Dom<T>`, with nullability represented by an enclosing Option wrapper. +/// Essentially a `Cell<Option<Dom<T>>>`, but safer. +/// +/// This should only be used as a field in other DOM objects; see warning +/// on `Dom<T>`. +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable)] +pub struct MutNullableDom<T: DomObject> { + ptr: UnsafeCell<Option<Dom<T>>>, +} + +impl<T: DomObject> MutNullableDom<T> { + /// Create a new `MutNullableDom`. + pub fn new(initial: Option<&T>) -> MutNullableDom<T> { + assert_in_script(); + MutNullableDom { + ptr: UnsafeCell::new(initial.map(Dom::from_ref)), + } + } + + /// Retrieve a copy of the current inner value. If it is `None`, it is + /// initialized with the result of `cb` first. + pub fn or_init<F>(&self, cb: F) -> DomRoot<T> + where + F: FnOnce() -> DomRoot<T>, + { + assert_in_script(); + match self.get() { + Some(inner) => inner, + None => { + let inner = cb(); + self.set(Some(&inner)); + inner + }, + } + } + + /// Retrieve a copy of the inner optional `Dom<T>` as `LayoutDom<T>`. + /// For use by layout, which can't use safe types like Temporary. + #[allow(unrooted_must_root)] + pub unsafe fn get_inner_as_layout(&self) -> Option<LayoutDom<T>> { + assert_in_layout(); + (*self.ptr.get()).as_ref().map(|js| js.to_layout()) + } + + /// Get a rooted value out of this object + #[allow(unrooted_must_root)] + pub fn get(&self) -> Option<DomRoot<T>> { + assert_in_script(); + unsafe { ptr::read(self.ptr.get()).map(|o| DomRoot::from_ref(&*o)) } + } + + /// Set this `MutNullableDom` to the given value. + pub fn set(&self, val: Option<&T>) { + assert_in_script(); + unsafe { + *self.ptr.get() = val.map(|p| Dom::from_ref(p)); + } + } + + /// Gets the current value out of this object and sets it to `None`. + pub fn take(&self) -> Option<DomRoot<T>> { + let value = self.get(); + self.set(None); + value + } +} + +impl<T: DomObject> PartialEq for MutNullableDom<T> { + fn eq(&self, other: &Self) -> bool { + unsafe { *self.ptr.get() == *other.ptr.get() } + } +} + +impl<'a, T: DomObject> PartialEq<Option<&'a T>> for MutNullableDom<T> { + fn eq(&self, other: &Option<&T>) -> bool { + unsafe { *self.ptr.get() == other.map(Dom::from_ref) } + } +} + +impl<T: DomObject> Default for MutNullableDom<T> { + #[allow(unrooted_must_root)] + fn default() -> MutNullableDom<T> { + assert_in_script(); + MutNullableDom { + ptr: UnsafeCell::new(None), + } + } +} + +impl<T: DomObject> MallocSizeOf for MutNullableDom<T> { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + // See comment on MallocSizeOf for Dom<T>. + 0 + } +} + +/// A holder that allows to lazily initialize the value only once +/// `Dom<T>`, using OnceCell +/// Essentially a `OnceCell<Dom<T>>`. +/// +/// This should only be used as a field in other DOM objects; see warning +/// on `Dom<T>`. +#[unrooted_must_root_lint::must_root] +pub struct DomOnceCell<T: DomObject> { + ptr: OnceCell<Dom<T>>, +} + +impl<T> DomOnceCell<T> +where + T: DomObject, +{ + /// Retrieve a copy of the current inner value. If it is `None`, it is + /// initialized with the result of `cb` first. + #[allow(unrooted_must_root)] + pub fn init_once<F>(&self, cb: F) -> &T + where + F: FnOnce() -> DomRoot<T>, + { + assert_in_script(); + &self.ptr.init_once(|| Dom::from_ref(&cb())) + } +} + +impl<T: DomObject> Default for DomOnceCell<T> { + #[allow(unrooted_must_root)] + fn default() -> DomOnceCell<T> { + assert_in_script(); + DomOnceCell { + ptr: OnceCell::new(), + } + } +} + +impl<T: DomObject> MallocSizeOf for DomOnceCell<T> { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + // See comment on MallocSizeOf for Dom<T>. + 0 + } +} + +#[allow(unrooted_must_root)] +unsafe impl<T: DomObject> JSTraceable for DomOnceCell<T> { + unsafe fn trace(&self, trc: *mut JSTracer) { + if let Some(ptr) = self.ptr.as_ref() { + ptr.trace(trc); + } + } +} + +impl<'dom, T> LayoutDom<'dom, T> +where + T: 'dom + DomObject, +{ + /// Returns a reference to the interior of this JS object. The fact + /// that this is unsafe is what necessitates the layout wrappers. + pub unsafe fn unsafe_get(self) -> &'dom T { + assert_in_layout(); + self.value + } + + /// Transforms a slice of Dom<T> into a slice of LayoutDom<T>. + // FIXME(nox): This should probably be done through a ToLayout trait. + pub unsafe fn to_layout_slice(slice: &'dom [Dom<T>]) -> &'dom [LayoutDom<'dom, T>] { + // This doesn't compile if Dom and LayoutDom don't have the same + // representation. + let _ = mem::transmute::<Dom<T>, LayoutDom<T>>; + &*(slice as *const [Dom<T>] as *const [LayoutDom<T>]) + } +} + +/// Helper trait for safer manipulations of `Option<Heap<T>>` values. +pub trait OptionalHeapSetter { + type Value; + /// Update this optional heap value with a new value. + fn set(&mut self, v: Option<Self::Value>); +} + +impl<T: GCMethods + Copy> OptionalHeapSetter for Option<Heap<T>> +where + Heap<T>: Default, +{ + type Value = T; + fn set(&mut self, v: Option<T>) { + let v = match v { + None => { + *self = None; + return; + }, + Some(v) => v, + }; + + if self.is_none() { + *self = Some(Heap::default()); + } + + self.as_ref().unwrap().set(v); + } +} diff --git a/components/script/dom/bindings/serializable.rs b/components/script/dom/bindings/serializable.rs new file mode 100644 index 00000000000..a2ca8f5a040 --- /dev/null +++ b/components/script/dom/bindings/serializable.rs @@ -0,0 +1,31 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Trait representing the concept of [serializable objects] +//! (https://html.spec.whatwg.org/multipage/#serializable-objects). + +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::structuredclone::StructuredDataHolder; +use crate::dom::globalscope::GlobalScope; + +/// The key corresponding to the storage location +/// of a serialized platform object stored in a StructuredDataHolder. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct StorageKey { + pub index: u32, + pub name_space: u32, +} + +/// Interface for serializable platform objects. +/// <https://html.spec.whatwg.org/multipage/#serializable> +pub trait Serializable: DomObject { + /// <https://html.spec.whatwg.org/multipage/#serialization-steps> + fn serialize(&self, sc_holder: &mut StructuredDataHolder) -> Result<StorageKey, ()>; + /// <https://html.spec.whatwg.org/multipage/#deserialization-steps> + fn deserialize( + owner: &GlobalScope, + sc_holder: &mut StructuredDataHolder, + extra_data: StorageKey, + ) -> Result<(), ()>; +} diff --git a/components/script/dom/bindings/settings_stack.rs b/components/script/dom/bindings/settings_stack.rs index dec63924915..24b6175f159 100644 --- a/components/script/dom/bindings/settings_stack.rs +++ b/components/script/dom/bindings/settings_stack.rs @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::js::{JS, Root}; -use dom::bindings::trace::JSTraceable; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::globalscope::GlobalScope; use js::jsapi::GetScriptedCallerGlobal; use js::jsapi::HideScriptedCaller; use js::jsapi::JSTracer; @@ -15,7 +15,7 @@ use std::thread; thread_local!(static STACK: RefCell<Vec<StackEntry>> = RefCell::new(Vec::new())); -#[derive(PartialEq, Eq, Debug, JSTraceable)] +#[derive(Debug, Eq, JSTraceable, PartialEq)] enum StackEntryKind { Incumbent, Entry, @@ -24,7 +24,7 @@ enum StackEntryKind { #[allow(unrooted_must_root)] #[derive(JSTraceable)] struct StackEntry { - global: JS<GlobalScope>, + global: Dom<GlobalScope>, kind: StackEntryKind, } @@ -35,37 +35,42 @@ pub unsafe fn trace(tracer: *mut JSTracer) { }) } +pub fn is_execution_stack_empty() -> bool { + STACK.with(|stack| stack.borrow().is_empty()) +} + /// RAII struct that pushes and pops entries from the script settings stack. pub struct AutoEntryScript { - global: Root<GlobalScope>, + global: DomRoot<GlobalScope>, } impl AutoEntryScript { - /// https://html.spec.whatwg.org/multipage/#prepare-to-run-script + /// <https://html.spec.whatwg.org/multipage/#prepare-to-run-script> pub fn new(global: &GlobalScope) -> Self { STACK.with(|stack| { trace!("Prepare to run script with {:p}", global); let mut stack = stack.borrow_mut(); stack.push(StackEntry { - global: JS::from_ref(global), + global: Dom::from_ref(global), kind: StackEntryKind::Entry, }); AutoEntryScript { - global: Root::from_ref(global), + global: DomRoot::from_ref(global), } }) } } impl Drop for AutoEntryScript { - /// https://html.spec.whatwg.org/multipage/#clean-up-after-running-script + /// <https://html.spec.whatwg.org/multipage/#clean-up-after-running-script> fn drop(&mut self) { STACK.with(|stack| { let mut stack = stack.borrow_mut(); let entry = stack.pop().unwrap(); - assert_eq!(&*entry.global as *const GlobalScope, - &*self.global as *const GlobalScope, - "Dropped AutoEntryScript out of order."); + assert_eq!( + &*entry.global as *const GlobalScope, &*self.global as *const GlobalScope, + "Dropped AutoEntryScript out of order." + ); assert_eq!(entry.kind, StackEntryKind::Entry); trace!("Clean up after running script with {:p}", &*entry.global); }); @@ -80,14 +85,17 @@ impl Drop for AutoEntryScript { /// Returns the ["entry"] global object. /// /// ["entry"]: https://html.spec.whatwg.org/multipage/#entry -pub fn entry_global() -> Root<GlobalScope> { - STACK.with(|stack| { - stack.borrow() - .iter() - .rev() - .find(|entry| entry.kind == StackEntryKind::Entry) - .map(|entry| Root::from_ref(&*entry.global)) - }).unwrap() +pub fn entry_global() -> DomRoot<GlobalScope> { + STACK + .with(|stack| { + stack + .borrow() + .iter() + .rev() + .find(|entry| entry.kind == StackEntryKind::Entry) + .map(|entry| DomRoot::from_ref(&*entry.global)) + }) + .unwrap() } /// RAII struct that pushes and pops entries from the script settings stack. @@ -96,7 +104,7 @@ pub struct AutoIncumbentScript { } impl AutoIncumbentScript { - /// https://html.spec.whatwg.org/multipage/#prepare-to-run-a-callback + /// <https://html.spec.whatwg.org/multipage/#prepare-to-run-a-callback> pub fn new(global: &GlobalScope) -> Self { // Step 2-3. unsafe { @@ -109,7 +117,7 @@ impl AutoIncumbentScript { // Step 1. let mut stack = stack.borrow_mut(); stack.push(StackEntry { - global: JS::from_ref(global), + global: Dom::from_ref(global), kind: StackEntryKind::Incumbent, }); AutoIncumbentScript { @@ -120,18 +128,22 @@ impl AutoIncumbentScript { } impl Drop for AutoIncumbentScript { - /// https://html.spec.whatwg.org/multipage/#clean-up-after-running-a-callback + /// <https://html.spec.whatwg.org/multipage/#clean-up-after-running-a-callback> fn drop(&mut self) { STACK.with(|stack| { // Step 4. let mut stack = stack.borrow_mut(); let entry = stack.pop().unwrap(); // Step 3. - assert_eq!(&*entry.global as *const GlobalScope as usize, - self.global, - "Dropped AutoIncumbentScript out of order."); + assert_eq!( + &*entry.global as *const GlobalScope as usize, self.global, + "Dropped AutoIncumbentScript out of order." + ); assert_eq!(entry.kind, StackEntryKind::Incumbent); - trace!("Clean up after running a callback with {:p}", &*entry.global); + trace!( + "Clean up after running a callback with {:p}", + &*entry.global + ); }); unsafe { // Step 1-2. @@ -145,7 +157,7 @@ impl Drop for AutoIncumbentScript { /// Returns the ["incumbent"] global object. /// /// ["incumbent"]: https://html.spec.whatwg.org/multipage/#incumbent -pub fn incumbent_global() -> Option<Root<GlobalScope>> { +pub fn incumbent_global() -> Option<DomRoot<GlobalScope>> { // https://html.spec.whatwg.org/multipage/#incumbent-settings-object // Step 1, 3: See what the JS engine has to say. If we've got a scripted @@ -163,8 +175,9 @@ pub fn incumbent_global() -> Option<Root<GlobalScope>> { // Step 2: nothing from the JS engine. Let's use whatever's on the explicit stack. STACK.with(|stack| { - stack.borrow() - .last() - .map(|entry| Root::from_ref(&*entry.global)) + stack + .borrow() + .last() + .map(|entry| DomRoot::from_ref(&*entry.global)) }) } diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs index e75958d974a..0905c157143 100644 --- a/components/script/dom/bindings/str.rs +++ b/components/script/dom/bindings/str.rs @@ -1,22 +1,26 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The `ByteString` struct. - -use html5ever_atoms::{LocalName, Namespace}; +use chrono::prelude::{Utc, Weekday}; +use chrono::{Datelike, TimeZone}; +use cssparser::CowRcStr; +use html5ever::{LocalName, Namespace}; +use regex::Regex; use servo_atoms::Atom; -use std::ascii::AsciiExt; use std::borrow::{Borrow, Cow, ToOwned}; +use std::default::Default; use std::fmt; use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; use std::ops; use std::ops::{Deref, DerefMut}; use std::str; -use std::str::{Bytes, FromStr}; +use std::str::FromStr; /// Encapsulates the IDL `ByteString` type. -#[derive(JSTraceable, Clone, Eq, PartialEq, HeapSizeOf, Debug)] +#[derive(Clone, Debug, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)] pub struct ByteString(Vec<u8>); impl ByteString { @@ -36,11 +40,6 @@ impl ByteString { self.0.len() } - /// Compare `self` to `other`, matching A–Z and a–z as equal. - pub fn eq_ignore_case(&self, other: &ByteString) -> bool { - self.0.eq_ignore_ascii_case(&other.0) - } - /// Returns `self` with A–Z replaced by a–z. pub fn to_lower(&self) -> ByteString { ByteString::new(self.0.to_ascii_lowercase()) @@ -75,9 +74,62 @@ impl ops::Deref for ByteString { /// A string that is constructed from a UCS-2 buffer by replacing invalid code /// points with the replacement character. -#[derive(Clone, HeapSizeOf)] +#[derive(Clone, Default, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)] pub struct USVString(pub String); +impl Borrow<str> for USVString { + #[inline] + fn borrow(&self) -> &str { + &self.0 + } +} + +impl Deref for USVString { + type Target = str; + + #[inline] + fn deref(&self) -> &str { + &self.0 + } +} + +impl DerefMut for USVString { + #[inline] + fn deref_mut(&mut self) -> &mut str { + &mut self.0 + } +} + +impl AsRef<str> for USVString { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl fmt::Display for USVString { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +impl PartialEq<str> for USVString { + fn eq(&self, other: &str) -> bool { + &**self == other + } +} + +impl<'a> PartialEq<&'a str> for USVString { + fn eq(&self, other: &&'a str) -> bool { + &**self == *other + } +} + +impl From<String> for USVString { + fn from(contents: String) -> USVString { + USVString(contents) + } +} /// Returns whether `s` is a `token`, as defined by /// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-17). @@ -88,96 +140,22 @@ pub fn is_token(s: &[u8]) -> bool { s.iter().all(|&x| { // http://tools.ietf.org/html/rfc2616#section-2.2 match x { - 0...31 | 127 => false, // CTLs - 40 | - 41 | - 60 | - 62 | - 64 | - 44 | - 59 | - 58 | - 92 | - 34 | - 47 | - 91 | - 93 | - 63 | - 61 | - 123 | - 125 | - 32 => false, // separators + 0..=31 | 127 => false, // CTLs + 40 | 41 | 60 | 62 | 64 | 44 | 59 | 58 | 92 | 34 | 47 | 91 | 93 | 63 | 61 | 123 | + 125 | 32 => false, // separators x if x > 127 => false, // non-CHARs _ => true, } }) } -/// Returns whether the language is matched, as defined by -/// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2). -pub fn extended_filtering(tag: &str, range: &str) -> bool { - let lang_ranges: Vec<&str> = range.split(',').collect(); - - lang_ranges.iter().any(|&lang_range| { - // step 1 - let range_subtags: Vec<&str> = lang_range.split('\x2d').collect(); - let tag_subtags: Vec<&str> = tag.split('\x2d').collect(); - - let mut range_iter = range_subtags.iter(); - let mut tag_iter = tag_subtags.iter(); - - // step 2 - // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card - if let (Some(range_subtag), Some(tag_subtag)) = (range_iter.next(), tag_iter.next()) { - if !(range_subtag.eq_ignore_ascii_case(tag_subtag) || range_subtag.eq_ignore_ascii_case("*")) { - return false; - } - } - - let mut current_tag_subtag = tag_iter.next(); - - // step 3 - for range_subtag in range_iter { - // step 3a - if range_subtag.eq_ignore_ascii_case("*") { - continue; - } - match current_tag_subtag.clone() { - Some(tag_subtag) => { - // step 3c - if range_subtag.eq_ignore_ascii_case(tag_subtag) { - current_tag_subtag = tag_iter.next(); - continue; - } else { - // step 3d - if tag_subtag.len() == 1 { - return false; - } else { - // else step 3e - continue with loop - current_tag_subtag = tag_iter.next(); - if current_tag_subtag.is_none() { - return false; - } - } - } - }, - // step 3b - None => { return false; } - } - } - // step 4 - true - }) -} - - /// A DOMString. /// /// This type corresponds to the [`DOMString`](idl) type in WebIDL. /// /// [idl]: https://heycam.github.io/webidl/#idl-DOMString /// -/// Cenceptually, a DOMString has the same value space as a JavaScript String, +/// Conceptually, a DOMString has the same value space as a JavaScript String, /// i.e., an array of 16-bit *code units* representing UTF-16, potentially with /// unpaired surrogates present (also sometimes called WTF-16). /// @@ -207,20 +185,18 @@ pub fn extended_filtering(tag: &str, range: &str) -> bool { /// /// This type is currently `!Send`, in order to help with an independent /// experiment to store `JSString`s rather than Rust `String`s. -#[derive(Clone, Debug, Eq, Hash, HeapSizeOf, Ord, PartialEq, PartialOrd)] -pub struct DOMString(String); - -impl !Send for DOMString {} +#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)] +pub struct DOMString(String, PhantomData<*const ()>); impl DOMString { /// Creates a new `DOMString`. pub fn new() -> DOMString { - DOMString(String::new()) + DOMString(String::new(), PhantomData) } /// Creates a new `DOMString` from a `String`. pub fn from_string(s: String) -> DOMString { - DOMString(s) + DOMString(s, PhantomData) } /// Appends a given string slice onto the end of this String. @@ -238,9 +214,332 @@ impl DOMString { self.0.truncate(new_len); } - /// An iterator over the bytes of this `DOMString`. - pub fn bytes(&self) -> Bytes { - self.0.bytes() + /// Removes newline characters according to <https://infra.spec.whatwg.org/#strip-newlines>. + pub fn strip_newlines(&mut self) { + self.0.retain(|c| c != '\r' && c != '\n'); + } + + /// Removes leading and trailing ASCII whitespaces according to + /// <https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace>. + pub fn strip_leading_and_trailing_ascii_whitespace(&mut self) { + if self.0.len() == 0 { + return; + } + + let trailing_whitespace_len = self + .0 + .trim_end_matches(|ref c| char::is_ascii_whitespace(c)) + .len(); + self.0.truncate(trailing_whitespace_len); + if self.0.is_empty() { + return; + } + + let first_non_whitespace = self.0.find(|ref c| !char::is_ascii_whitespace(c)).unwrap(); + let _ = self.0.replace_range(0..first_non_whitespace, ""); + } + + /// Validates this `DOMString` is a time string according to + /// <https://html.spec.whatwg.org/multipage/#valid-time-string>. + pub fn is_valid_time_string(&self) -> bool { + enum State { + HourHigh, + HourLow09, + HourLow03, + MinuteColon, + MinuteHigh, + MinuteLow, + SecondColon, + SecondHigh, + SecondLow, + MilliStop, + MilliHigh, + MilliMiddle, + MilliLow, + Done, + Error, + } + let next_state = |valid: bool, next: State| -> State { + if valid { + next + } else { + State::Error + } + }; + + let state = self.chars().fold(State::HourHigh, |state, c| { + match state { + // Step 1 "HH" + State::HourHigh => match c { + '0' | '1' => State::HourLow09, + '2' => State::HourLow03, + _ => State::Error, + }, + State::HourLow09 => next_state(c.is_digit(10), State::MinuteColon), + State::HourLow03 => next_state(c.is_digit(4), State::MinuteColon), + + // Step 2 ":" + State::MinuteColon => next_state(c == ':', State::MinuteHigh), + + // Step 3 "mm" + State::MinuteHigh => next_state(c.is_digit(6), State::MinuteLow), + State::MinuteLow => next_state(c.is_digit(10), State::SecondColon), + + // Step 4.1 ":" + State::SecondColon => next_state(c == ':', State::SecondHigh), + // Step 4.2 "ss" + State::SecondHigh => next_state(c.is_digit(6), State::SecondLow), + State::SecondLow => next_state(c.is_digit(10), State::MilliStop), + + // Step 4.3.1 "." + State::MilliStop => next_state(c == '.', State::MilliHigh), + // Step 4.3.2 "SSS" + State::MilliHigh => next_state(c.is_digit(10), State::MilliMiddle), + State::MilliMiddle => next_state(c.is_digit(10), State::MilliLow), + State::MilliLow => next_state(c.is_digit(10), State::Done), + + _ => State::Error, + } + }); + + match state { + State::Done | + // Step 4 (optional) + State::SecondColon | + // Step 4.3 (optional) + State::MilliStop | + // Step 4.3.2 (only 1 digit required) + State::MilliMiddle | State::MilliLow => true, + _ => false + } + } + + /// A valid date string should be "YYYY-MM-DD" + /// YYYY must be four or more digits, MM and DD both must be two digits + /// https://html.spec.whatwg.org/multipage/#valid-date-string + pub fn is_valid_date_string(&self) -> bool { + self.parse_date_string().is_ok() + } + + /// https://html.spec.whatwg.org/multipage/#parse-a-date-string + pub fn parse_date_string(&self) -> Result<(i32, u32, u32), ()> { + let value = &self.0; + // Step 1, 2, 3 + let (year_int, month_int, day_int) = parse_date_component(value)?; + + // Step 4 + if value.split('-').nth(3).is_some() { + return Err(()); + } + + // Step 5, 6 + Ok((year_int, month_int, day_int)) + } + + /// https://html.spec.whatwg.org/multipage/#parse-a-time-string + pub fn parse_time_string(&self) -> Result<(u32, u32, f64), ()> { + let value = &self.0; + // Step 1, 2, 3 + let (hour_int, minute_int, second_float) = parse_time_component(value)?; + + // Step 4 + if value.split(':').nth(3).is_some() { + return Err(()); + } + + // Step 5, 6 + Ok((hour_int, minute_int, second_float)) + } + + /// A valid month string should be "YYYY-MM" + /// YYYY must be four or more digits, MM both must be two digits + /// https://html.spec.whatwg.org/multipage/#valid-month-string + pub fn is_valid_month_string(&self) -> bool { + self.parse_month_string().is_ok() + } + + /// https://html.spec.whatwg.org/multipage/#parse-a-month-string + pub fn parse_month_string(&self) -> Result<(i32, u32), ()> { + let value = &self; + // Step 1, 2, 3 + let (year_int, month_int) = parse_month_component(value)?; + + // Step 4 + if value.split("-").nth(2).is_some() { + return Err(()); + } + // Step 5 + Ok((year_int, month_int)) + } + + /// A valid week string should be like {YYYY}-W{WW}, such as "2017-W52" + /// YYYY must be four or more digits, WW both must be two digits + /// https://html.spec.whatwg.org/multipage/#valid-week-string + pub fn is_valid_week_string(&self) -> bool { + self.parse_week_string().is_ok() + } + + /// https://html.spec.whatwg.org/multipage/#parse-a-week-string + pub fn parse_week_string(&self) -> Result<(i32, u32), ()> { + let value = &self.0; + // Step 1, 2, 3 + let mut iterator = value.split('-'); + let year = iterator.next().ok_or(())?; + + // Step 4 + let year_int = year.parse::<i32>().map_err(|_| ())?; + if year.len() < 4 || year_int == 0 { + return Err(()); + } + + // Step 5, 6 + let week = iterator.next().ok_or(())?; + let (week_first, week_last) = week.split_at(1); + if week_first != "W" { + return Err(()); + } + + // Step 7 + let week_int = week_last.parse::<u32>().map_err(|_| ())?; + if week_last.len() != 2 { + return Err(()); + } + + // Step 8 + let max_week = max_week_in_year(year_int); + + // Step 9 + if week_int < 1 || week_int > max_week { + return Err(()); + } + + // Step 10 + if iterator.next().is_some() { + return Err(()); + } + + // Step 11 + Ok((year_int, week_int)) + } + + /// https://html.spec.whatwg.org/multipage/#valid-floating-point-number + pub fn is_valid_floating_point_number_string(&self) -> bool { + lazy_static! { + static ref RE: Regex = + Regex::new(r"^-?(?:\d+\.\d+|\d+|\.\d+)(?:(e|E)(\+|\-)?\d+)?$").unwrap(); + } + RE.is_match(&self.0) && self.parse_floating_point_number().is_ok() + } + + /// https://html.spec.whatwg.org/multipage/#rules-for-parsing-floating-point-number-values + pub fn parse_floating_point_number(&self) -> Result<f64, ()> { + // Steps 15-16 are telling us things about IEEE rounding modes + // for floating-point significands; this code assumes the Rust + // compiler already matches them in any cases where + // that actually matters. They are not + // related to f64::round(), which is for rounding to integers. + let input = &self.0; + match input.trim().parse::<f64>() { + Ok(val) + if !( + // A valid number is the same as what rust considers to be valid, + // except for +1., NaN, and Infinity. + val.is_infinite() || + val.is_nan() || + input.ends_with(".") || + input.starts_with("+") + ) => + { + Ok(val) + }, + _ => Err(()), + } + } + + /// https://html.spec.whatwg.org/multipage/#best-representation-of-the-number-as-a-floating-point-number + pub fn set_best_representation_of_the_floating_point_number(&mut self) { + if let Ok(val) = self.parse_floating_point_number() { + self.0 = val.to_string(); + } + } + + /// A valid normalized local date and time string should be "{date}T{time}" + /// where date and time are both valid, and the time string must be as short as possible + /// https://html.spec.whatwg.org/multipage/#valid-normalised-local-date-and-time-string + pub fn convert_valid_normalized_local_date_and_time_string(&mut self) -> Result<(), ()> { + let ((year, month, day), (hour, minute, second)) = + self.parse_local_date_and_time_string()?; + if second == 0.0 { + self.0 = format!( + "{:04}-{:02}-{:02}T{:02}:{:02}", + year, month, day, hour, minute + ); + } else if second < 10.0 { + // we need exactly one leading zero on the seconds, + // whatever their total string length might be + self.0 = format!( + "{:04}-{:02}-{:02}T{:02}:{:02}:0{}", + year, month, day, hour, minute, second + ); + } else { + // we need no leading zeroes on the seconds + self.0 = format!( + "{:04}-{:02}-{:02}T{:02}:{:02}:{}", + year, month, day, hour, minute, second + ); + } + Ok(()) + } + + /// https://html.spec.whatwg.org/multipage/#parse-a-local-date-and-time-string + pub fn parse_local_date_and_time_string( + &self, + ) -> Result<((i32, u32, u32), (u32, u32, f64)), ()> { + let value = &self; + // Step 1, 2, 4 + let mut iterator = if value.contains('T') { + value.split('T') + } else { + value.split(' ') + }; + + // Step 3 + let date = iterator.next().ok_or(())?; + let date_tuple = parse_date_component(date)?; + + // Step 5 + let time = iterator.next().ok_or(())?; + let time_tuple = parse_time_component(time)?; + + // Step 6 + if iterator.next().is_some() { + return Err(()); + } + + // Step 7, 8, 9 + Ok((date_tuple, time_tuple)) + } + + /// https://html.spec.whatwg.org/multipage/#valid-e-mail-address + pub fn is_valid_email_address_string(&self) -> bool { + lazy_static! { + static ref RE: Regex = Regex::new(concat!( + r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?", + r"(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" + )) + .unwrap(); + } + RE.is_match(&self.0) + } + + /// https://html.spec.whatwg.org/multipage/#valid-simple-colour + pub fn is_valid_simple_color_string(&self) -> bool { + let mut chars = self.0.chars(); + if self.0.len() == 7 && chars.next() == Some('#') { + chars.all(|c| c.is_digit(16)) + } else { + false + } } } @@ -253,7 +552,7 @@ impl Borrow<str> for DOMString { impl Default for DOMString { fn default() -> Self { - DOMString(String::new()) + DOMString(String::new(), PhantomData) } } @@ -300,7 +599,7 @@ impl<'a> PartialEq<&'a str> for DOMString { impl From<String> for DOMString { fn from(contents: String) -> DOMString { - DOMString(contents) + DOMString(contents, PhantomData) } } @@ -355,8 +654,143 @@ impl<'a> Into<Cow<'a, str>> for DOMString { } } +impl<'a> Into<CowRcStr<'a>> for DOMString { + fn into(self) -> CowRcStr<'a> { + self.0.into() + } +} + impl Extend<char> for DOMString { - fn extend<I>(&mut self, iterable: I) where I: IntoIterator<Item=char> { + fn extend<I>(&mut self, iterable: I) + where + I: IntoIterator<Item = char>, + { self.0.extend(iterable) } } + +/// https://html.spec.whatwg.org/multipage/#parse-a-month-component +fn parse_month_component(value: &str) -> Result<(i32, u32), ()> { + // Step 3 + let mut iterator = value.split('-'); + let year = iterator.next().ok_or(())?; + let month = iterator.next().ok_or(())?; + + // Step 1, 2 + let year_int = year.parse::<i32>().map_err(|_| ())?; + if year.len() < 4 || year_int == 0 { + return Err(()); + } + + // Step 4, 5 + let month_int = month.parse::<u32>().map_err(|_| ())?; + if month.len() != 2 || month_int > 12 || month_int < 1 { + return Err(()); + } + + // Step 6 + Ok((year_int, month_int)) +} + +/// https://html.spec.whatwg.org/multipage/#parse-a-date-component +fn parse_date_component(value: &str) -> Result<(i32, u32, u32), ()> { + // Step 1 + let (year_int, month_int) = parse_month_component(value)?; + + // Step 3, 4 + let day = value.split('-').nth(2).ok_or(())?; + let day_int = day.parse::<u32>().map_err(|_| ())?; + if day.len() != 2 { + return Err(()); + } + + // Step 2, 5 + let max_day = max_day_in_month(year_int, month_int)?; + if day_int == 0 || day_int > max_day { + return Err(()); + } + + // Step 6 + Ok((year_int, month_int, day_int)) +} + +/// https://html.spec.whatwg.org/multipage/#parse-a-time-component +fn parse_time_component(value: &str) -> Result<(u32, u32, f64), ()> { + // Step 1 + let mut iterator = value.split(':'); + let hour = iterator.next().ok_or(())?; + if hour.len() != 2 { + return Err(()); + } + let hour_int = hour.parse::<u32>().map_err(|_| ())?; + + // Step 2 + if hour_int > 23 { + return Err(()); + } + + // Step 3, 4 + let minute = iterator.next().ok_or(())?; + if minute.len() != 2 { + return Err(()); + } + let minute_int = minute.parse::<u32>().map_err(|_| ())?; + + // Step 5 + if minute_int > 59 { + return Err(()); + } + + // Step 6, 7 + let second_float = match iterator.next() { + Some(second) => { + let mut second_iterator = second.split('.'); + if second_iterator.next().ok_or(())?.len() != 2 { + return Err(()); + } + match second_iterator.next() { + Some(second_last) => { + if second_last.len() > 3 { + return Err(()); + } + }, + None => {}, + } + + second.parse::<f64>().map_err(|_| ())? + }, + None => 0.0, + }; + + // Step 8 + Ok((hour_int, minute_int, second_float)) +} + +fn max_day_in_month(year_num: i32, month_num: u32) -> Result<u32, ()> { + match month_num { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => Ok(31), + 4 | 6 | 9 | 11 => Ok(30), + 2 => { + if is_leap_year(year_num) { + Ok(29) + } else { + Ok(28) + } + }, + _ => Err(()), + } +} + +/// https://html.spec.whatwg.org/multipage/#week-number-of-the-last-day +fn max_week_in_year(year: i32) -> u32 { + match Utc.ymd(year as i32, 1, 1).weekday() { + Weekday::Thu => 53, + Weekday::Wed if is_leap_year(year) => 53, + _ => 52, + } +} + +#[inline] +fn is_leap_year(year: i32) -> bool { + year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) +} diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index b3a9df32695..132f3f78222 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -1,27 +1,46 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! This module implements structured cloning, as defined by [HTML] //! (https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data). -use dom::bindings::conversions::root_from_handleobject; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::js::Root; -use dom::bindings::reflector::DomObject; -use dom::blob::{Blob, BlobImpl}; -use dom::globalscope::GlobalScope; -use js::jsapi::{Handle, HandleObject, HandleValue, MutableHandleValue, JSAutoCompartment, JSContext}; +use crate::dom::bindings::conversions::{root_from_object, ToJSValConvertible}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::serializable::{Serializable, StorageKey}; +use crate::dom::bindings::transferable::Transferable; +use crate::dom::blob::Blob; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageport::MessagePort; +use crate::realms::{enter_realm, AlreadyInRealm, InRealm}; +use crate::script_runtime::JSContext as SafeJSContext; +use js::glue::CopyJSStructuredCloneData; +use js::glue::DeleteJSAutoStructuredCloneBuffer; +use js::glue::GetLengthOfJSStructuredCloneData; +use js::glue::NewJSAutoStructuredCloneBuffer; +use js::glue::WriteBytesToJSStructuredCloneData; +use js::jsapi::CloneDataPolicy; +use js::jsapi::HandleObject as RawHandleObject; +use js::jsapi::JSContext; +use js::jsapi::MutableHandleObject as RawMutableHandleObject; +use js::jsapi::StructuredCloneScope; +use js::jsapi::TransferableOwnership; +use js::jsapi::JS_STRUCTURED_CLONE_VERSION; +use js::jsapi::{JSObject, JS_ClearPendingException}; use js::jsapi::{JSStructuredCloneCallbacks, JSStructuredCloneReader, JSStructuredCloneWriter}; -use js::jsapi::{JS_ClearPendingException, JSObject, JS_ReadStructuredClone}; -use js::jsapi::{JS_ReadBytes, JS_WriteBytes}; use js::jsapi::{JS_ReadUint32Pair, JS_WriteUint32Pair}; -use js::jsapi::{JS_STRUCTURED_CLONE_VERSION, JS_WriteStructuredClone}; -use js::jsapi::{MutableHandleObject, TransferableOwnership}; -use libc::size_t; +use js::jsval::UndefinedValue; +use js::rust::wrappers::{JS_ReadStructuredClone, JS_WriteStructuredClone}; +use js::rust::{CustomAutoRooterGuard, HandleValue, MutableHandleValue}; +use msg::constellation_msg::{BlobId, MessagePortId}; +use script_traits::serializable::BlobImpl; +use script_traits::transferable::MessagePortImpl; +use script_traits::StructuredSerializedData; +use std::collections::HashMap; use std::os::raw; use std::ptr; -use std::slice; // TODO: Should we add Min and Max const to https://github.com/servo/rust-mozjs/blob/master/src/consts.rs? // TODO: Determine for sure which value Min and Max should have. @@ -32,126 +51,196 @@ enum StructuredCloneTags { /// To support additional types, add new tags with values incremented from the last one before Max. Min = 0xFFFF8000, DomBlob = 0xFFFF8001, + MessagePort = 0xFFFF8002, Max = 0xFFFFFFFF, } -#[cfg(target_pointer_width = "64")] -unsafe fn write_length(w: *mut JSStructuredCloneWriter, - length: usize) { - let high: u32 = (length >> 32) as u32; - let low: u32 = length as u32; - assert!(JS_WriteUint32Pair(w, high, low)); -} - -#[cfg(target_pointer_width = "32")] -unsafe fn write_length(w: *mut JSStructuredCloneWriter, - length: usize) { - assert!(JS_WriteUint32Pair(w, length as u32, 0)); -} - -#[cfg(target_pointer_width = "64")] -unsafe fn read_length(r: *mut JSStructuredCloneReader) - -> usize { - let mut high: u32 = 0; - let mut low: u32 = 0; - assert!(JS_ReadUint32Pair(r, &mut high as *mut u32, &mut low as *mut u32)); - return (low << high) as usize; +unsafe fn read_blob( + owner: &GlobalScope, + r: *mut JSStructuredCloneReader, + mut sc_holder: &mut StructuredDataHolder, +) -> *mut JSObject { + let mut name_space: u32 = 0; + let mut index: u32 = 0; + assert!(JS_ReadUint32Pair( + r, + &mut name_space as *mut u32, + &mut index as *mut u32 + )); + let storage_key = StorageKey { index, name_space }; + if <Blob as Serializable>::deserialize(&owner, &mut sc_holder, storage_key.clone()).is_ok() { + let blobs = match sc_holder { + StructuredDataHolder::Read { blobs, .. } => blobs, + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; + if let Some(blobs) = blobs { + let blob = blobs + .get(&storage_key) + .expect("No blob found at storage key."); + return blob.reflector().get_jsobject().get(); + } + } + warn!( + "Reading structured data for a blob failed in {:?}.", + owner.get_url() + ); + ptr::null_mut() } -#[cfg(target_pointer_width = "32")] -unsafe fn read_length(r: *mut JSStructuredCloneReader) - -> usize { - let mut length: u32 = 0; - let mut zero: u32 = 0; - assert!(JS_ReadUint32Pair(r, &mut length as *mut u32, &mut zero as *mut u32)); - return length as usize; +unsafe fn write_blob( + owner: &GlobalScope, + blob: DomRoot<Blob>, + w: *mut JSStructuredCloneWriter, + sc_holder: &mut StructuredDataHolder, +) -> bool { + if let Ok(storage_key) = blob.serialize(sc_holder) { + assert!(JS_WriteUint32Pair( + w, + StructuredCloneTags::DomBlob as u32, + 0 + )); + assert!(JS_WriteUint32Pair( + w, + storage_key.name_space, + storage_key.index + )); + return true; + } + warn!( + "Writing structured data for a blob failed in {:?}.", + owner.get_url() + ); + return false; } -unsafe fn read_blob(cx: *mut JSContext, - r: *mut JSStructuredCloneReader) - -> *mut JSObject { - let blob_length = read_length(r); - let type_str_length = read_length(r); - let mut blob_buffer = vec![0u8; blob_length]; - assert!(JS_ReadBytes(r, blob_buffer.as_mut_ptr() as *mut raw::c_void, blob_length)); - let mut type_str_buffer = vec![0u8; type_str_length]; - assert!(JS_ReadBytes(r, type_str_buffer.as_mut_ptr() as *mut raw::c_void, type_str_length)); - let type_str = String::from_utf8_unchecked(type_str_buffer); - let target_global = GlobalScope::from_context(cx); - let blob = Blob::new(&target_global, BlobImpl::new_from_bytes(blob_buffer), type_str); - return blob.reflector().get_jsobject().get() +unsafe extern "C" fn read_callback( + cx: *mut JSContext, + r: *mut JSStructuredCloneReader, + _policy: *const CloneDataPolicy, + tag: u32, + _data: u32, + closure: *mut raw::c_void, +) -> *mut JSObject { + assert!( + tag < StructuredCloneTags::Max as u32, + "tag should be lower than StructuredCloneTags::Max" + ); + assert!( + tag > StructuredCloneTags::Min as u32, + "tag should be higher than StructuredCloneTags::Min" + ); + if tag == StructuredCloneTags::DomBlob as u32 { + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + return read_blob( + &GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)), + r, + &mut *(closure as *mut StructuredDataHolder), + ); + } + return ptr::null_mut(); } -unsafe fn write_blob(blob: Root<Blob>, - w: *mut JSStructuredCloneWriter) - -> Result<(), ()> { - let blob_vec = try!(blob.get_bytes()); - let blob_length = blob_vec.len(); - let type_string_bytes = blob.type_string().as_bytes().to_vec(); - let type_string_length = type_string_bytes.len(); - assert!(JS_WriteUint32Pair(w, StructuredCloneTags::DomBlob as u32, 0)); - write_length(w, blob_length); - write_length(w, type_string_length); - assert!(JS_WriteBytes(w, blob_vec.as_ptr() as *const raw::c_void, blob_length)); - assert!(JS_WriteBytes(w, type_string_bytes.as_ptr() as *const raw::c_void, type_string_length)); - return Ok(()) +unsafe extern "C" fn write_callback( + cx: *mut JSContext, + w: *mut JSStructuredCloneWriter, + obj: RawHandleObject, + _same_process_scope_required: *mut bool, + closure: *mut raw::c_void, +) -> bool { + if let Ok(blob) = root_from_object::<Blob>(*obj, cx) { + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + return write_blob( + &GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)), + blob, + w, + &mut *(closure as *mut StructuredDataHolder), + ); + } + return false; } -unsafe extern "C" fn read_callback(cx: *mut JSContext, - r: *mut JSStructuredCloneReader, - tag: u32, - _data: u32, - _closure: *mut raw::c_void) - -> *mut JSObject { - assert!(tag < StructuredCloneTags::Max as u32, "tag should be lower than StructuredCloneTags::Max"); - assert!(tag > StructuredCloneTags::Min as u32, "tag should be higher than StructuredCloneTags::Min"); - if tag == StructuredCloneTags::DomBlob as u32 { - return read_blob(cx, r) +unsafe extern "C" fn read_transfer_callback( + cx: *mut JSContext, + _r: *mut JSStructuredCloneReader, + tag: u32, + _content: *mut raw::c_void, + extra_data: u64, + closure: *mut raw::c_void, + return_object: RawMutableHandleObject, +) -> bool { + if tag == StructuredCloneTags::MessagePort as u32 { + let mut sc_holder = &mut *(closure as *mut StructuredDataHolder); + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + let owner = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)); + if let Ok(_) = <MessagePort as Transferable>::transfer_receive( + &owner, + &mut sc_holder, + extra_data, + return_object, + ) { + return true; + } } - return ptr::null_mut() + false } -unsafe extern "C" fn write_callback(_cx: *mut JSContext, - w: *mut JSStructuredCloneWriter, - obj: HandleObject, - _closure: *mut raw::c_void) - -> bool { - if let Ok(blob) = root_from_handleobject::<Blob>(obj) { - return write_blob(blob, w).is_ok() +/// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer> +unsafe extern "C" fn write_transfer_callback( + cx: *mut JSContext, + obj: RawHandleObject, + closure: *mut raw::c_void, + tag: *mut u32, + ownership: *mut TransferableOwnership, + _content: *mut *mut raw::c_void, + extra_data: *mut u64, +) -> bool { + if let Ok(port) = root_from_object::<MessagePort>(*obj, cx) { + *tag = StructuredCloneTags::MessagePort as u32; + *ownership = TransferableOwnership::SCTAG_TMO_CUSTOM; + let mut sc_holder = &mut *(closure as *mut StructuredDataHolder); + if let Ok(data) = port.transfer(&mut sc_holder) { + *extra_data = data; + return true; + } } - return false + false } -unsafe extern "C" fn read_transfer_callback(_cx: *mut JSContext, - _r: *mut JSStructuredCloneReader, - _tag: u32, - _content: *mut raw::c_void, - _extra_data: u64, - _closure: *mut raw::c_void, - _return_object: MutableHandleObject) - -> bool { - false +unsafe extern "C" fn free_transfer_callback( + _tag: u32, + _ownership: TransferableOwnership, + _content: *mut raw::c_void, + _extra_data: u64, + _closure: *mut raw::c_void, +) { } -unsafe extern "C" fn write_transfer_callback(_cx: *mut JSContext, - _obj: Handle<*mut JSObject>, - _closure: *mut raw::c_void, - _tag: *mut u32, - _ownership: *mut TransferableOwnership, - _content: *mut *mut raw::c_void, - _extra_data: *mut u64) - -> bool { +unsafe extern "C" fn can_transfer_callback( + cx: *mut JSContext, + obj: RawHandleObject, + _same_process_scope_required: *mut bool, + _closure: *mut raw::c_void, +) -> bool { + if let Ok(_port) = root_from_object::<MessagePort>(*obj, cx) { + return true; + } false } -unsafe extern "C" fn free_transfer_callback(_tag: u32, - _ownership: TransferableOwnership, - _content: *mut raw::c_void, - _extra_data: u64, - _closure: *mut raw::c_void) { +unsafe extern "C" fn report_error_callback( + _cx: *mut JSContext, + _errorid: u32, + _closure: *mut ::std::os::raw::c_void, + _error_message: *const ::std::os::raw::c_char, +) { } -unsafe extern "C" fn report_error_callback(_cx: *mut JSContext, _errorid: u32) { +unsafe extern "C" fn sab_cloned_callback( + _cx: *mut JSContext, + _receiving: bool, + _closure: *mut ::std::os::raw::c_void, +) -> bool { + false } static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredCloneCallbacks { @@ -161,83 +250,163 @@ static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredClon readTransfer: Some(read_transfer_callback), writeTransfer: Some(write_transfer_callback), freeTransfer: Some(free_transfer_callback), + canTransfer: Some(can_transfer_callback), + sabCloned: Some(sab_cloned_callback), }; -/// A buffer for a structured clone. -pub enum StructuredCloneData { - /// A non-serializable (default) variant - Struct(*mut u64, size_t), - /// A variant that can be serialized - Vector(Vec<u8>) +/// A data holder for results from, and inputs to, structured-data read/write operations. +/// https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data +pub enum StructuredDataHolder { + Read { + /// A map of deserialized blobs, stored temporarily here to keep them rooted. + blobs: Option<HashMap<StorageKey, DomRoot<Blob>>>, + /// A vec of transfer-received DOM ports, + /// to be made available to script through a message event. + message_ports: Option<Vec<DomRoot<MessagePort>>>, + /// A map of port implementations, + /// used as part of the "transfer-receiving" steps of ports, + /// to produce the DOM ports stored in `message_ports` above. + port_impls: Option<HashMap<MessagePortId, MessagePortImpl>>, + /// A map of blob implementations, + /// used as part of the "deserialize" steps of blobs, + /// to produce the DOM blobs stored in `blobs` above. + blob_impls: Option<HashMap<BlobId, BlobImpl>>, + }, + /// A data holder for transferred and serialized objects. + Write { + /// Transferred ports. + ports: Option<HashMap<MessagePortId, MessagePortImpl>>, + /// Serialized blobs. + blobs: Option<HashMap<BlobId, BlobImpl>>, + }, } -impl StructuredCloneData { - /// Writes a structured clone. Returns a `DataClone` error if that fails. - pub fn write(cx: *mut JSContext, message: HandleValue) -> Fallible<StructuredCloneData> { - let mut data = ptr::null_mut(); - let mut nbytes = 0; - let result = unsafe { - JS_WriteStructuredClone(cx, - message, - &mut data, - &mut nbytes, - &STRUCTURED_CLONE_CALLBACKS, - ptr::null_mut(), - HandleValue::undefined()) +/// Writes a structured clone. Returns a `DataClone` error if that fails. +pub fn write( + cx: SafeJSContext, + message: HandleValue, + transfer: Option<CustomAutoRooterGuard<Vec<*mut JSObject>>>, +) -> Fallible<StructuredSerializedData> { + unsafe { + rooted!(in(*cx) let mut val = UndefinedValue()); + if let Some(transfer) = transfer { + transfer.to_jsval(*cx, val.handle_mut()); + } + let mut sc_holder = StructuredDataHolder::Write { + ports: None, + blobs: None, }; + let sc_holder_ptr = &mut sc_holder as *mut _; + + let scbuf = NewJSAutoStructuredCloneBuffer( + StructuredCloneScope::DifferentProcess, + &STRUCTURED_CLONE_CALLBACKS, + ); + let scdata = &mut ((*scbuf).data_); + let policy = CloneDataPolicy { + allowIntraClusterClonableSharedObjects_: false, + allowSharedMemoryObjects_: false, + }; + let result = JS_WriteStructuredClone( + *cx, + message, + scdata, + StructuredCloneScope::DifferentProcess, + &policy, + &STRUCTURED_CLONE_CALLBACKS, + sc_holder_ptr as *mut raw::c_void, + val.handle(), + ); if !result { - unsafe { - JS_ClearPendingException(cx); - } + JS_ClearPendingException(*cx); return Err(Error::DataClone); } - Ok(StructuredCloneData::Struct(data, nbytes)) - } - /// Converts a StructuredCloneData to Vec<u8> for inter-thread sharing - pub fn move_to_arraybuffer(self) -> Vec<u8> { - match self { - StructuredCloneData::Struct(data, nbytes) => { - unsafe { - slice::from_raw_parts(data as *mut u8, nbytes).to_vec() - } - } - StructuredCloneData::Vector(msg) => msg - } - } + let nbytes = GetLengthOfJSStructuredCloneData(scdata); + let mut data = Vec::with_capacity(nbytes); + CopyJSStructuredCloneData(scdata, data.as_mut_ptr()); + data.set_len(nbytes); - /// Reads a structured clone. - /// - /// Panics if `JS_ReadStructuredClone` fails. - fn read_clone(global: &GlobalScope, - data: *mut u64, - nbytes: size_t, - rval: MutableHandleValue) { - let cx = global.get_cx(); - let globalhandle = global.reflector().get_jsobject(); - let _ac = JSAutoCompartment::new(cx, globalhandle.get()); - unsafe { - assert!(JS_ReadStructuredClone(cx, - data, - nbytes, - JS_STRUCTURED_CLONE_VERSION, - rval, - &STRUCTURED_CLONE_CALLBACKS, - ptr::null_mut())); - } + DeleteJSAutoStructuredCloneBuffer(scbuf); + + let (mut blob_impls, mut port_impls) = match sc_holder { + StructuredDataHolder::Write { blobs, ports } => (blobs, ports), + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; + + let data = StructuredSerializedData { + serialized: data, + ports: port_impls.take(), + blobs: blob_impls.take(), + }; + + Ok(data) } +} + +/// Read structured serialized data, possibly containing transferred objects. +/// Returns a vec of rooted transfer-received ports, or an error. +pub fn read( + global: &GlobalScope, + mut data: StructuredSerializedData, + rval: MutableHandleValue, +) -> Result<Vec<DomRoot<MessagePort>>, ()> { + let cx = global.get_cx(); + let _ac = enter_realm(&*global); + let mut sc_holder = StructuredDataHolder::Read { + blobs: None, + message_ports: None, + port_impls: data.ports.take(), + blob_impls: data.blobs.take(), + }; + let sc_holder_ptr = &mut sc_holder as *mut _; + unsafe { + let scbuf = NewJSAutoStructuredCloneBuffer( + StructuredCloneScope::DifferentProcess, + &STRUCTURED_CLONE_CALLBACKS, + ); + let scdata = &mut ((*scbuf).data_); + + WriteBytesToJSStructuredCloneData( + data.serialized.as_mut_ptr() as *const u8, + data.serialized.len(), + scdata, + ); - /// Thunk for the actual `read_clone` method. Resolves proper variant for read_clone. - pub fn read(self, global: &GlobalScope, rval: MutableHandleValue) { - match self { - StructuredCloneData::Vector(mut vec_msg) => { - let nbytes = vec_msg.len(); - let data = vec_msg.as_mut_ptr() as *mut u64; - StructuredCloneData::read_clone(global, data, nbytes, rval); + let result = JS_ReadStructuredClone( + *cx, + scdata, + JS_STRUCTURED_CLONE_VERSION, + StructuredCloneScope::DifferentProcess, + rval, + &CloneDataPolicy { + allowIntraClusterClonableSharedObjects_: false, + allowSharedMemoryObjects_: false, + }, + &STRUCTURED_CLONE_CALLBACKS, + sc_holder_ptr as *mut raw::c_void, + ); + + DeleteJSAutoStructuredCloneBuffer(scbuf); + + if result { + let (mut message_ports, port_impls) = match sc_holder { + StructuredDataHolder::Read { + message_ports, + port_impls, + .. + } => (message_ports, port_impls), + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; + + // Any transfer-received port-impls should have been taken out. + assert!(port_impls.is_none()); + + match message_ports.take() { + Some(ports) => return Ok(ports), + None => return Ok(Vec::with_capacity(0)), } - StructuredCloneData::Struct(data, nbytes) => StructuredCloneData::read_clone(global, data, nbytes, rval) } + Err(()) } } - -unsafe impl Send for StructuredCloneData {} diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 1bc9c165fb1..02b72fc3c2d 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Utilities for tracing JS-managed values. //! @@ -18,9 +18,9 @@ //! achieved via `unsafe_no_jsmanaged_fields!` or similar. //! 3. For all fields, `Foo::trace()` //! calls `trace()` on the field. -//! For example, for fields of type `JS<T>`, `JS<T>::trace()` calls +//! For example, for fields of type `Dom<T>`, `Dom<T>::trace()` calls //! `trace_reflector()`. -//! 4. `trace_reflector()` calls `JS::TraceEdge()` with a +//! 4. `trace_reflector()` calls `Dom::TraceEdge()` with a //! pointer to the `JSObject` for the reflector. This notifies the GC, which //! will add the object to the graph, and will trace that object as well. //! 5. When the GC finishes tracing, it [`finalizes`](../index.html#destruction) @@ -29,85 +29,157 @@ //! The `unsafe_no_jsmanaged_fields!()` macro adds an empty implementation of //! `JSTraceable` to a datatype. +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::{DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::utils::WindowProxyHandler; +use crate::dom::gpubuffer::GPUBufferState; +use crate::dom::gpucanvascontext::WebGPUContextId; +use crate::dom::gpucommandencoder::GPUCommandEncoderState; +use crate::dom::htmlimageelement::SourceSet; +use crate::dom::htmlmediaelement::{HTMLMediaElementFetchContext, MediaFrameRenderer}; +use crate::dom::identityhub::Identities; +use crate::script_runtime::{ContextForRequestInterrupt, StreamConsumer}; +use crate::script_thread::IncompleteParserContexts; +use crate::task::TaskBox; use app_units::Au; -use canvas_traits::{CanvasGradientStop, LinearGradientStyle, RadialGradientStyle}; -use canvas_traits::{CompositionOrBlending, LineCapStyle, LineJoinStyle, RepetitionStyle}; +use canvas_traits::canvas::{ + CanvasGradientStop, CanvasId, LinearGradientStyle, RadialGradientStyle, +}; +use canvas_traits::canvas::{ + CompositionOrBlending, Direction, LineCapStyle, LineJoinStyle, RepetitionStyle, TextAlign, + TextBaseline, +}; +use canvas_traits::webgl::WebGLVertexArrayId; +use canvas_traits::webgl::{ + ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, GlType, TexDataType, TexFormat, +}; +use canvas_traits::webgl::{GLLimits, WebGLQueryId, WebGLSamplerId}; +use canvas_traits::webgl::{WebGLBufferId, WebGLChan, WebGLContextId, WebGLError}; +use canvas_traits::webgl::{WebGLFramebufferId, WebGLMsgSender, WebGLPipeline, WebGLProgramId}; +use canvas_traits::webgl::{WebGLReceiver, WebGLRenderbufferId, WebGLSLVersion, WebGLSender}; +use canvas_traits::webgl::{WebGLShaderId, WebGLSyncId, WebGLTextureId, WebGLVersion}; +use content_security_policy::CspList; +use crossbeam_channel::{Receiver, Sender}; use cssparser::RGBA; use devtools_traits::{CSSError, TimelineMarkerType, WorkerId}; -use dom::abstractworker::SharedRt; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::js::{JS, Root}; -use dom::bindings::refcounted::{Trusted, TrustedPromise}; -use dom::bindings::reflector::{DomObject, Reflector}; -use dom::bindings::str::{DOMString, USVString}; -use dom::bindings::utils::WindowProxyHandler; -use dom::document::PendingRestyle; -use encoding::types::EncodingRef; -use euclid::{Matrix2D, Matrix4D, Point2D}; -use euclid::length::Length as EuclidLength; -use euclid::rect::Rect; -use euclid::size::Size2D; -use html5ever::tokenizer::buffer_queue::BufferQueue; -use html5ever_atoms::{Prefix, LocalName, Namespace, QualName}; -use hyper::header::Headers; -use hyper::method::Method; -use hyper::mime::Mime; -use hyper::status::StatusCode; +use embedder_traits::{EventLoopWaker, MediaMetadata}; +use encoding_rs::{Decoder, Encoding}; +use euclid::default::{Point2D, Rect, Rotation3D, Transform2D}; +use euclid::Length as EuclidLength; +use html5ever::buffer_queue::BufferQueue; +use html5ever::{LocalName, Namespace, Prefix, QualName}; +use http::header::HeaderMap; +use hyper::Method; +use hyper::StatusCode; +use indexmap::IndexMap; use ipc_channel::ipc::{IpcReceiver, IpcSender}; -use js::glue::{CallObjectTracer, CallValueTracer}; -use js::jsapi::{GCTraceKindToAscii, Heap, JSObject, JSTracer, TraceKind}; +use js::glue::{CallObjectTracer, CallScriptTracer, CallStringTracer, CallValueTracer}; +use js::jsapi::{ + GCTraceKindToAscii, Heap, JSObject, JSScript, JSString, JSTracer, JobQueue, TraceKind, +}; use js::jsval::JSVal; -use js::rust::Runtime; -use msg::constellation_msg::{FrameId, FrameType, PipelineId}; -use net_traits::{Metadata, NetworkError, ReferrerPolicy, ResourceThreads}; +use js::rust::{GCMethods, Handle, Runtime}; +use js::typedarray::TypedArray; +use js::typedarray::TypedArrayElement; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use media::WindowGLContext; +use metrics::{InteractiveMetrics, InteractiveWindow}; +use mime::Mime; +use msg::constellation_msg::{ + BlobId, BroadcastChannelRouterId, BrowsingContextId, HistoryStateId, MessagePortId, + MessagePortRouterId, PipelineId, TopLevelBrowsingContextId, +}; +use msg::constellation_msg::{ServiceWorkerId, ServiceWorkerRegistrationId}; use net_traits::filemanager_thread::RelativePos; use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache::{ImageCache, PendingImageId}; -use net_traits::request::{Request, RequestInit}; -use net_traits::response::{Response, ResponseBody}; +use net_traits::request::{CredentialsMode, ParserMetadata, Referrer, Request, RequestBuilder}; use net_traits::response::HttpsState; +use net_traits::response::{Response, ResponseBody}; use net_traits::storage_thread::StorageType; -use offscreen_gl_context::GLLimits; -use parking_lot::RwLock; +use net_traits::{Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming, ResourceThreads}; +use parking_lot::{Mutex as ParkMutex, RwLock}; use profile_traits::mem::ProfilerChan as MemProfilerChan; use profile_traits::time::ProfilerChan as TimeProfilerChan; -use script_layout_interface::OpaqueStyleAndLayoutData; -use script_layout_interface::reporter::CSSErrorReporter; +use script_layout_interface::message::PendingRestyle; use script_layout_interface::rpc::LayoutRPC; -use script_traits::{DocumentActivity, TimerEventId, TimerSource, TouchpadPressurePhase}; -use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType}; +use script_layout_interface::StyleAndOpaqueLayoutData; +use script_traits::serializable::BlobImpl; +use script_traits::transferable::MessagePortImpl; +use script_traits::{ + DocumentActivity, DrawAPaintImageResult, MediaSessionActionType, ScriptToConstellationChan, + TimerEventId, TimerSource, UntrustedNodeAddress, WebrenderIpcSender, WindowSizeData, + WindowSizeType, +}; use selectors::matching::ElementSelectorFlags; use serde::{Deserialize, Serialize}; +use servo_arc::Arc as ServoArc; use servo_atoms::Atom; +use servo_media::audio::analyser_node::AnalysisEngine; +use servo_media::audio::buffer_source_node::AudioBuffer; +use servo_media::audio::context::AudioContext; +use servo_media::audio::graph::NodeId; +use servo_media::audio::panner_node::{DistanceModel, PanningModel}; +use servo_media::audio::param::ParamType; +use servo_media::player::audio::AudioRenderer; +use servo_media::player::video::VideoFrame; +use servo_media::player::Player; +use servo_media::streams::registry::MediaStreamId; +use servo_media::streams::MediaStreamType; +use servo_media::webrtc::WebRtcController; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; use smallvec::SmallVec; +use std::borrow::Cow; use std::cell::{Cell, RefCell, UnsafeCell}; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use std::hash::{BuildHasher, Hash}; -use std::ops::{Deref, DerefMut}; +use std::mem; +use std::num::NonZeroU64; +use std::ops::{Deref, DerefMut, Range}; use std::path::PathBuf; use std::rc::Rc; -use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, AtomicUsize}; -use std::sync::mpsc::{Receiver, Sender}; -use std::time::{SystemTime, Instant}; +use std::sync::{Arc, Mutex}; +use std::thread::JoinHandle; +use std::time::{Instant, SystemTime}; +use style::animation::DocumentAnimationSet; use style::attr::{AttrIdentifier, AttrValue, LengthOrPercentageOrAuto}; +use style::author_styles::AuthorStyles; use style::context::QuirksMode; +use style::dom::OpaqueNode; use style::element_state::*; -use style::keyframes::Keyframe; use style::media_queries::MediaList; +use style::properties::style_structs::Font; use style::properties::PropertyDeclarationBlock; use style::selector_parser::{PseudoElement, Snapshot}; -use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked}; -use style::stylesheets::{CssRules, FontFaceRule, KeyframesRule, MediaRule}; -use style::stylesheets::{NamespaceRule, StyleRule, ImportRule, SupportsRule}; +use style::shared_lock::{Locked as StyleLocked, SharedRwLock as StyleSharedRwLock}; +use style::stylesheet_set::{AuthorStylesheetSet, DocumentStylesheetSet}; +use style::stylesheets::keyframes_rule::Keyframe; +use style::stylesheets::{CssRules, FontFaceRule, KeyframesRule, MediaRule, Stylesheet}; +use style::stylesheets::{ImportRule, NamespaceRule, StyleRule, SupportsRule, ViewportRule}; +use style::stylist::CascadeData; use style::values::specified::Length; -use style::viewport::ViewportRule; -use time::Duration; +use tendril::fmt::UTF8; +use tendril::stream::LossyDecoder; +use tendril::{StrTendril, TendrilSink}; +use time::{Duration, Timespec, Tm}; use uuid::Uuid; -use webrender_traits::{WebGLBufferId, WebGLError, WebGLFramebufferId, WebGLProgramId}; -use webrender_traits::{WebGLRenderbufferId, WebGLShaderId, WebGLTextureId}; -use webvr_traits::WebVRGamepadHand; +use webgpu::{ + wgpu::command::{ComputePass, RenderBundleEncoder, RenderPass}, + WebGPU, WebGPUAdapter, WebGPUBindGroup, WebGPUBindGroupLayout, WebGPUBuffer, + WebGPUCommandBuffer, WebGPUCommandEncoder, WebGPUComputePipeline, WebGPUDevice, + WebGPUPipelineLayout, WebGPUQueue, WebGPURenderBundle, WebGPURenderPipeline, WebGPUSampler, + WebGPUShaderModule, WebGPUTexture, WebGPUTextureView, +}; +use webrender_api::{DocumentId, ExternalImageId, ImageKey}; +use webxr_api::{Finger, Hand, Ray, View}; + +unsafe_no_jsmanaged_fields!(Tm); +unsafe_no_jsmanaged_fields!(JoinHandle<()>); /// A trait to allow tracing (only) DOM objects. pub unsafe trait JSTraceable { @@ -115,14 +187,52 @@ pub unsafe trait JSTraceable { unsafe fn trace(&self, trc: *mut JSTracer); } +unsafe_no_jsmanaged_fields!(Box<dyn TaskBox>, Box<dyn EventLoopWaker>); + +unsafe_no_jsmanaged_fields!(IncompleteParserContexts); + +unsafe_no_jsmanaged_fields!(MessagePortImpl); +unsafe_no_jsmanaged_fields!(MessagePortId); +unsafe_no_jsmanaged_fields!(MessagePortRouterId); + +unsafe_no_jsmanaged_fields!(ServiceWorkerId); +unsafe_no_jsmanaged_fields!(ServiceWorkerRegistrationId); + +unsafe_no_jsmanaged_fields!(BroadcastChannelRouterId); + +unsafe_no_jsmanaged_fields!(BlobId); +unsafe_no_jsmanaged_fields!(BlobImpl); + unsafe_no_jsmanaged_fields!(CSSError); -unsafe_no_jsmanaged_fields!(EncodingRef); +unsafe_no_jsmanaged_fields!(&'static Encoding); + +unsafe_no_jsmanaged_fields!(Decoder); unsafe_no_jsmanaged_fields!(Reflector); unsafe_no_jsmanaged_fields!(Duration); +unsafe_no_jsmanaged_fields!(TexDataType, TexFormat); + +unsafe_no_jsmanaged_fields!(*mut JobQueue); + +unsafe_no_jsmanaged_fields!(Cow<'static, str>); + +unsafe_no_jsmanaged_fields!(CspList); + +/// Trace a `JSScript`. +pub fn trace_script(tracer: *mut JSTracer, description: &str, script: &Heap<*mut JSScript>) { + unsafe { + trace!("tracing {}", description); + CallScriptTracer( + tracer, + script.ptr.get() as *mut _, + GCTraceKindToAscii(TraceKind::Script), + ); + } +} + /// Trace a `JSVal`. pub fn trace_jsval(tracer: *mut JSTracer, description: &str, val: &Heap<JSVal>) { unsafe { @@ -131,9 +241,11 @@ pub fn trace_jsval(tracer: *mut JSTracer, description: &str, val: &Heap<JSVal>) } trace!("tracing value {}", description); - CallValueTracer(tracer, - val.ptr.get() as *mut _, - GCTraceKindToAscii(val.get().trace_kind())); + CallValueTracer( + tracer, + val.ptr.get() as *mut _, + GCTraceKindToAscii(val.get().trace_kind()), + ); } } @@ -148,9 +260,23 @@ pub fn trace_reflector(tracer: *mut JSTracer, description: &str, reflector: &Ref pub fn trace_object(tracer: *mut JSTracer, description: &str, obj: &Heap<*mut JSObject>) { unsafe { trace!("tracing {}", description); - CallObjectTracer(tracer, - obj.ptr.get() as *mut _, - GCTraceKindToAscii(TraceKind::Object)); + CallObjectTracer( + tracer, + obj.ptr.get() as *mut _, + GCTraceKindToAscii(TraceKind::Object), + ); + } +} + +/// Trace a `JSString`. +pub fn trace_string(tracer: *mut JSTracer, description: &str, s: &Heap<*mut JSString>) { + unsafe { + trace!("tracing {}", description); + CallStringTracer( + tracer, + s.ptr.get() as *mut _, + GCTraceKindToAscii(TraceKind::String), + ); } } @@ -166,12 +292,32 @@ unsafe impl<T: JSTraceable> JSTraceable for Arc<T> { } } +unsafe impl<T: JSTraceable> JSTraceable for ServoArc<T> { + unsafe fn trace(&self, trc: *mut JSTracer) { + (**self).trace(trc) + } +} + +unsafe impl<T: JSTraceable> JSTraceable for RwLock<T> { + unsafe fn trace(&self, trc: *mut JSTracer) { + self.read().trace(trc) + } +} + unsafe impl<T: JSTraceable + ?Sized> JSTraceable for Box<T> { unsafe fn trace(&self, trc: *mut JSTracer) { (**self).trace(trc) } } +unsafe impl<T: JSTraceable> JSTraceable for [T] { + unsafe fn trace(&self, trc: *mut JSTracer) { + for e in self.iter() { + e.trace(trc); + } + } +} + unsafe impl<T: JSTraceable + Copy> JSTraceable for Cell<T> { unsafe fn trace(&self, trc: *mut JSTracer) { self.get().trace(trc) @@ -184,9 +330,24 @@ unsafe impl<T: JSTraceable> JSTraceable for UnsafeCell<T> { } } -unsafe impl<T: JSTraceable> JSTraceable for DOMRefCell<T> { +unsafe impl<T: JSTraceable> JSTraceable for DomRefCell<T> { unsafe fn trace(&self, trc: *mut JSTracer) { - (*self).borrow_for_gc_trace().trace(trc) + (*self).borrow().trace(trc) + } +} + +unsafe impl<T: JSTraceable> JSTraceable for RefCell<T> { + unsafe fn trace(&self, trc: *mut JSTracer) { + (*self).borrow().trace(trc) + } +} + +unsafe impl JSTraceable for Heap<*mut JSScript> { + unsafe fn trace(&self, trc: *mut JSTracer) { + if self.get().is_null() { + return; + } + trace_script(trc, "heap script", self); } } @@ -199,6 +360,15 @@ unsafe impl JSTraceable for Heap<*mut JSObject> { } } +unsafe impl JSTraceable for Heap<*mut JSString> { + unsafe fn trace(&self, trc: *mut JSTracer) { + if self.get().is_null() { + return; + } + trace_string(trc, "heap string", self); + } +} + unsafe impl JSTraceable for Heap<JSVal> { unsafe fn trace(&self, trc: *mut JSTracer) { trace_jsval(trc, "heap value", self); @@ -225,7 +395,22 @@ unsafe impl<T: JSTraceable> JSTraceable for VecDeque<T> { } } -unsafe impl<T: JSTraceable> JSTraceable for (T, T, T, T) { +unsafe impl<T: JSTraceable + Eq + Hash> JSTraceable for indexmap::IndexSet<T> { + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + for e in self.iter() { + e.trace(trc); + } + } +} + +unsafe impl<A, B, C, D> JSTraceable for (A, B, C, D) +where + A: JSTraceable, + B: JSTraceable, + C: JSTraceable, + D: JSTraceable, +{ unsafe fn trace(&self, trc: *mut JSTracer) { self.0.trace(trc); self.1.trace(trc); @@ -263,9 +448,10 @@ unsafe impl<T: JSTraceable, U: JSTraceable> JSTraceable for Result<T, U> { } unsafe impl<K, V, S> JSTraceable for HashMap<K, V, S> - where K: Hash + Eq + JSTraceable, - V: JSTraceable, - S: BuildHasher +where + K: Hash + Eq + JSTraceable, + V: JSTraceable, + S: BuildHasher, { #[inline] unsafe fn trace(&self, trc: *mut JSTracer) { @@ -277,8 +463,9 @@ unsafe impl<K, V, S> JSTraceable for HashMap<K, V, S> } unsafe impl<T, S> JSTraceable for HashSet<T, S> - where T: Hash + Eq + JSTraceable, - S: BuildHasher +where + T: Hash + Eq + JSTraceable, + S: BuildHasher, { #[inline] unsafe fn trace(&self, trc: *mut JSTracer) { @@ -298,6 +485,21 @@ unsafe impl<K: Ord + JSTraceable, V: JSTraceable> JSTraceable for BTreeMap<K, V> } } +unsafe impl<K, V, S> JSTraceable for IndexMap<K, V, S> +where + K: Hash + Eq + JSTraceable, + V: JSTraceable, + S: BuildHasher, +{ + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + for (k, v) in &*self { + k.trace(trc); + v.trace(trc); + } + } +} + unsafe impl<A: JSTraceable, B: JSTraceable> JSTraceable for (A, B) { #[inline] unsafe fn trace(&self, trc: *mut JSTracer) { @@ -317,34 +519,48 @@ unsafe impl<A: JSTraceable, B: JSTraceable, C: JSTraceable> JSTraceable for (A, } } +unsafe_no_jsmanaged_fields!(ActiveAttribInfo); +unsafe_no_jsmanaged_fields!(ActiveUniformInfo); +unsafe_no_jsmanaged_fields!(ActiveUniformBlockInfo); unsafe_no_jsmanaged_fields!(bool, f32, f64, String, AtomicBool, AtomicUsize, Uuid, char); unsafe_no_jsmanaged_fields!(usize, u8, u16, u32, u64); unsafe_no_jsmanaged_fields!(isize, i8, i16, i32, i64); +unsafe_no_jsmanaged_fields!(NonZeroU64); +unsafe_no_jsmanaged_fields!(Error); unsafe_no_jsmanaged_fields!(ServoUrl, ImmutableOrigin, MutableOrigin); -unsafe_no_jsmanaged_fields!(Image, ImageMetadata, ImageCache, PendingImageId); +unsafe_no_jsmanaged_fields!(Image, ImageMetadata, dyn ImageCache, PendingImageId); unsafe_no_jsmanaged_fields!(Metadata); unsafe_no_jsmanaged_fields!(NetworkError); unsafe_no_jsmanaged_fields!(Atom, Prefix, LocalName, Namespace, QualName); unsafe_no_jsmanaged_fields!(TrustedPromise); unsafe_no_jsmanaged_fields!(PropertyDeclarationBlock); +unsafe_no_jsmanaged_fields!(Font); // These three are interdependent, if you plan to put jsmanaged data // in one of these make sure it is propagated properly to containing structs -unsafe_no_jsmanaged_fields!(DocumentActivity, FrameId, FrameType, WindowSizeData, WindowSizeType, PipelineId); +unsafe_no_jsmanaged_fields!(DocumentActivity, WindowSizeData, WindowSizeType); +unsafe_no_jsmanaged_fields!( + BrowsingContextId, + HistoryStateId, + PipelineId, + TopLevelBrowsingContextId +); unsafe_no_jsmanaged_fields!(TimerEventId, TimerSource); unsafe_no_jsmanaged_fields!(TimelineMarkerType); unsafe_no_jsmanaged_fields!(WorkerId); -unsafe_no_jsmanaged_fields!(BufferQueue, QuirksMode); +unsafe_no_jsmanaged_fields!(BufferQueue, QuirksMode, StrTendril); unsafe_no_jsmanaged_fields!(Runtime); -unsafe_no_jsmanaged_fields!(Headers, Method); +unsafe_no_jsmanaged_fields!(ContextForRequestInterrupt); +unsafe_no_jsmanaged_fields!(HeaderMap, Method); unsafe_no_jsmanaged_fields!(WindowProxyHandler); -unsafe_no_jsmanaged_fields!(UntrustedNodeAddress); +unsafe_no_jsmanaged_fields!(UntrustedNodeAddress, OpaqueNode); unsafe_no_jsmanaged_fields!(LengthOrPercentageOrAuto); unsafe_no_jsmanaged_fields!(RGBA); unsafe_no_jsmanaged_fields!(StorageType); unsafe_no_jsmanaged_fields!(CanvasGradientStop, LinearGradientStyle, RadialGradientStyle); unsafe_no_jsmanaged_fields!(LineCapStyle, LineJoinStyle, CompositionOrBlending); +unsafe_no_jsmanaged_fields!(TextAlign, TextBaseline, Direction); unsafe_no_jsmanaged_fields!(RepetitionStyle); -unsafe_no_jsmanaged_fields!(WebGLError, GLLimits); +unsafe_no_jsmanaged_fields!(WebGLError, GLLimits, GlType); unsafe_no_jsmanaged_fields!(TimeProfilerChan); unsafe_no_jsmanaged_fields!(MemProfilerChan); unsafe_no_jsmanaged_fields!(PseudoElement); @@ -357,14 +573,16 @@ unsafe_no_jsmanaged_fields!(AttrIdentifier); unsafe_no_jsmanaged_fields!(AttrValue); unsafe_no_jsmanaged_fields!(Snapshot); unsafe_no_jsmanaged_fields!(PendingRestyle); +unsafe_no_jsmanaged_fields!(Stylesheet); unsafe_no_jsmanaged_fields!(HttpsState); unsafe_no_jsmanaged_fields!(Request); -unsafe_no_jsmanaged_fields!(RequestInit); -unsafe_no_jsmanaged_fields!(SharedRt); +unsafe_no_jsmanaged_fields!(RequestBuilder); unsafe_no_jsmanaged_fields!(StyleSharedRwLock); -unsafe_no_jsmanaged_fields!(TouchpadPressurePhase); unsafe_no_jsmanaged_fields!(USVString); +unsafe_no_jsmanaged_fields!(Referrer); unsafe_no_jsmanaged_fields!(ReferrerPolicy); +unsafe_no_jsmanaged_fields!(CredentialsMode); +unsafe_no_jsmanaged_fields!(ParserMetadata); unsafe_no_jsmanaged_fields!(Response); unsafe_no_jsmanaged_fields!(ResponseBody); unsafe_no_jsmanaged_fields!(ResourceThreads); @@ -372,17 +590,93 @@ unsafe_no_jsmanaged_fields!(StatusCode); unsafe_no_jsmanaged_fields!(SystemTime); unsafe_no_jsmanaged_fields!(Instant); unsafe_no_jsmanaged_fields!(RelativePos); -unsafe_no_jsmanaged_fields!(OpaqueStyleAndLayoutData); +unsafe_no_jsmanaged_fields!(StyleAndOpaqueLayoutData); unsafe_no_jsmanaged_fields!(PathBuf); -unsafe_no_jsmanaged_fields!(CSSErrorReporter); +unsafe_no_jsmanaged_fields!(DrawAPaintImageResult); +unsafe_no_jsmanaged_fields!(DocumentId); +unsafe_no_jsmanaged_fields!(ImageKey); +unsafe_no_jsmanaged_fields!(ExternalImageId); unsafe_no_jsmanaged_fields!(WebGLBufferId); +unsafe_no_jsmanaged_fields!(WebGLChan); unsafe_no_jsmanaged_fields!(WebGLFramebufferId); +unsafe_no_jsmanaged_fields!(WebGLMsgSender); +unsafe_no_jsmanaged_fields!(WebGLPipeline); unsafe_no_jsmanaged_fields!(WebGLProgramId); +unsafe_no_jsmanaged_fields!(WebGLQueryId); unsafe_no_jsmanaged_fields!(WebGLRenderbufferId); +unsafe_no_jsmanaged_fields!(WebGLSamplerId); unsafe_no_jsmanaged_fields!(WebGLShaderId); +unsafe_no_jsmanaged_fields!(WebGLSyncId); unsafe_no_jsmanaged_fields!(WebGLTextureId); +unsafe_no_jsmanaged_fields!(WebGLVertexArrayId); +unsafe_no_jsmanaged_fields!(WebGLVersion); +unsafe_no_jsmanaged_fields!(WebGLSLVersion); +unsafe_no_jsmanaged_fields!(Arc<ParkMutex<Identities>>); +unsafe_no_jsmanaged_fields!(WebGPU); +unsafe_no_jsmanaged_fields!(WebGPUAdapter); +unsafe_no_jsmanaged_fields!(WebGPUBuffer); +unsafe_no_jsmanaged_fields!(WebGPUBindGroup); +unsafe_no_jsmanaged_fields!(WebGPUBindGroupLayout); +unsafe_no_jsmanaged_fields!(WebGPUComputePipeline); +unsafe_no_jsmanaged_fields!(WebGPURenderPipeline); +unsafe_no_jsmanaged_fields!(WebGPURenderBundle); +unsafe_no_jsmanaged_fields!(WebGPUPipelineLayout); +unsafe_no_jsmanaged_fields!(WebGPUQueue); +unsafe_no_jsmanaged_fields!(WebGPUShaderModule); +unsafe_no_jsmanaged_fields!(WebGPUSampler); +unsafe_no_jsmanaged_fields!(WebGPUTexture); +unsafe_no_jsmanaged_fields!(WebGPUTextureView); +unsafe_no_jsmanaged_fields!(WebGPUContextId); +unsafe_no_jsmanaged_fields!(WebGPUCommandBuffer); +unsafe_no_jsmanaged_fields!(WebGPUCommandEncoder); +unsafe_no_jsmanaged_fields!(WebGPUDevice); +unsafe_no_jsmanaged_fields!(Option<RenderPass>); +unsafe_no_jsmanaged_fields!(Option<RenderBundleEncoder>); +unsafe_no_jsmanaged_fields!(Option<ComputePass>); +unsafe_no_jsmanaged_fields!(GPUBufferState); +unsafe_no_jsmanaged_fields!(GPUCommandEncoderState); +unsafe_no_jsmanaged_fields!(Range<u64>); unsafe_no_jsmanaged_fields!(MediaList); -unsafe_no_jsmanaged_fields!(WebVRGamepadHand); +unsafe_no_jsmanaged_fields!( + webxr_api::Registry, + webxr_api::Session, + webxr_api::Frame, + webxr_api::LayerId, + webxr_api::InputSource, + webxr_api::InputId, + webxr_api::Joint, + webxr_api::HitTestId, + webxr_api::HitTestResult +); +unsafe_no_jsmanaged_fields!(ScriptToConstellationChan); +unsafe_no_jsmanaged_fields!(InteractiveMetrics); +unsafe_no_jsmanaged_fields!(InteractiveWindow); +unsafe_no_jsmanaged_fields!(CanvasId); +unsafe_no_jsmanaged_fields!(SourceSet); +unsafe_no_jsmanaged_fields!(AudioBuffer); +unsafe_no_jsmanaged_fields!(Arc<Mutex<AudioContext>>); +unsafe_no_jsmanaged_fields!(NodeId); +unsafe_no_jsmanaged_fields!(AnalysisEngine, DistanceModel, PanningModel, ParamType); +unsafe_no_jsmanaged_fields!(Arc<Mutex<dyn Player>>); +unsafe_no_jsmanaged_fields!(WebRtcController); +unsafe_no_jsmanaged_fields!(MediaStreamId, MediaStreamType); +unsafe_no_jsmanaged_fields!(Mutex<MediaFrameRenderer>); +unsafe_no_jsmanaged_fields!(ResourceFetchTiming); +unsafe_no_jsmanaged_fields!(Timespec); +unsafe_no_jsmanaged_fields!(HTMLMediaElementFetchContext); +unsafe_no_jsmanaged_fields!(Rotation3D<f64>, Transform2D<f32>); +unsafe_no_jsmanaged_fields!(Point2D<f32>, Rect<Au>); +unsafe_no_jsmanaged_fields!(Rect<f32>); +unsafe_no_jsmanaged_fields!(CascadeData); +unsafe_no_jsmanaged_fields!(WindowGLContext); +unsafe_no_jsmanaged_fields!(VideoFrame); +unsafe_no_jsmanaged_fields!(WebGLContextId); +unsafe_no_jsmanaged_fields!(Arc<Mutex<dyn AudioRenderer>>); +unsafe_no_jsmanaged_fields!(MediaSessionActionType); +unsafe_no_jsmanaged_fields!(MediaMetadata); +unsafe_no_jsmanaged_fields!(WebrenderIpcSender); +unsafe_no_jsmanaged_fields!(StreamConsumer); +unsafe_no_jsmanaged_fields!(DocumentAnimationSet); unsafe impl<'a> JSTraceable for &'a str { #[inline] @@ -398,7 +692,10 @@ unsafe impl<A, B> JSTraceable for fn(A) -> B { } } -unsafe impl<T> JSTraceable for IpcSender<T> where T: Deserialize + Serialize { +unsafe impl<T> JSTraceable for IpcSender<T> +where + T: for<'de> Deserialize<'de> + Serialize, +{ #[inline] unsafe fn trace(&self, _: *mut JSTracer) { // Do nothing @@ -406,7 +703,7 @@ unsafe impl<T> JSTraceable for IpcSender<T> where T: Deserialize + Serialize { } // Safe thanks to the Send bound. -unsafe impl JSTraceable for Box<LayoutRPC + Send + 'static> { +unsafe impl JSTraceable for Box<dyn LayoutRPC + Send + 'static> { #[inline] unsafe fn trace(&self, _: *mut JSTracer) { // Do nothing @@ -420,7 +717,10 @@ unsafe impl JSTraceable for () { } } -unsafe impl<T> JSTraceable for IpcReceiver<T> where T: Deserialize + Serialize { +unsafe impl<T> JSTraceable for IpcReceiver<T> +where + T: for<'de> Deserialize<'de> + Serialize, +{ #[inline] unsafe fn trace(&self, _: *mut JSTracer) { // Do nothing @@ -448,21 +748,62 @@ unsafe impl<T: Send> JSTraceable for Sender<T> { } } -unsafe impl JSTraceable for Matrix2D<f32> { +unsafe impl<T: Send> JSTraceable for WebGLReceiver<T> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + #[inline] + unsafe fn trace(&self, _: *mut JSTracer) { + // Do nothing + } +} + +unsafe impl<T: Send> JSTraceable for WebGLSender<T> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + #[inline] + unsafe fn trace(&self, _: *mut JSTracer) { + // Do nothing + } +} + +unsafe impl<U> JSTraceable for euclid::Vector2D<f32, U> { #[inline] unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing } } -unsafe impl JSTraceable for Matrix4D<f64> { +unsafe impl<T, U> JSTraceable for euclid::Scale<f32, T, U> { #[inline] unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing } } -unsafe impl JSTraceable for Point2D<f32> { +unsafe impl<T, U> JSTraceable for euclid::RigidTransform3D<f32, T, U> { + #[inline] + unsafe fn trace(&self, _trc: *mut JSTracer) { + // Do nothing + } +} + +unsafe impl<T, U> JSTraceable for euclid::RigidTransform3D<f64, T, U> { + #[inline] + unsafe fn trace(&self, _trc: *mut JSTracer) { + // Do nothing + } +} + +unsafe impl<T, U> JSTraceable for euclid::Transform3D<f32, T, U> { + #[inline] + unsafe fn trace(&self, _trc: *mut JSTracer) { + // Do nothing + } +} + +unsafe impl<T, U> JSTraceable for euclid::Transform3D<f64, T, U> { #[inline] unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing @@ -476,30 +817,45 @@ unsafe impl<T> JSTraceable for EuclidLength<u64, T> { } } -unsafe impl JSTraceable for Rect<Au> { +unsafe impl<U> JSTraceable for euclid::Size2D<i32, U> { #[inline] unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing } } -unsafe impl JSTraceable for Rect<f32> { +unsafe impl<U> JSTraceable for euclid::Size2D<f32, U> { #[inline] unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing } } -unsafe impl JSTraceable for Size2D<i32> { +unsafe impl<U> JSTraceable for euclid::Size2D<u32, U> { #[inline] unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing } } -unsafe impl JSTraceable for Mutex<Option<SharedRt>> { +unsafe impl<U> JSTraceable for euclid::Rect<i32, U> { + #[inline] unsafe fn trace(&self, _trc: *mut JSTracer) { - // Do nothing. + // Do nothing + } +} + +unsafe impl<Space> JSTraceable for Ray<Space> { + #[inline] + unsafe fn trace(&self, _trc: *mut JSTracer) { + // Do nothing + } +} + +unsafe impl<Eye> JSTraceable for View<Eye> { + #[inline] + unsafe fn trace(&self, _trc: *mut JSTracer) { + // Do nothing } } @@ -569,50 +925,144 @@ unsafe impl JSTraceable for StyleLocked<PropertyDeclarationBlock> { } } -unsafe impl JSTraceable for RwLock<SharedRt> { +unsafe impl JSTraceable for StyleLocked<MediaList> { unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing. } } -unsafe impl JSTraceable for StyleLocked<MediaList> { - unsafe fn trace(&self, _trc: *mut JSTracer) { - // Do nothing. +unsafe impl<T> JSTraceable for TypedArray<T, Box<Heap<*mut JSObject>>> +where + T: TypedArrayElement, +{ + unsafe fn trace(&self, trc: *mut JSTracer) { + self.underlying_object().trace(trc); + } +} + +unsafe impl<S> JSTraceable for DocumentStylesheetSet<S> +where + S: JSTraceable + ::style::stylesheets::StylesheetInDocument + PartialEq + 'static, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + for (s, _origin) in self.iter() { + s.trace(tracer) + } + } +} + +unsafe impl<S> JSTraceable for AuthorStylesheetSet<S> +where + S: JSTraceable + ::style::stylesheets::StylesheetInDocument + PartialEq + 'static, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + for s in self.iter() { + s.trace(tracer) + } + } +} + +unsafe impl<S> JSTraceable for AuthorStyles<S> +where + S: JSTraceable + ::style::stylesheets::StylesheetInDocument + PartialEq + 'static, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + self.stylesheets.trace(tracer) + } +} + +unsafe impl<Sink> JSTraceable for LossyDecoder<Sink> +where + Sink: JSTraceable + TendrilSink<UTF8>, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + self.inner_sink().trace(tracer); + } +} + +unsafe impl<J> JSTraceable for Hand<J> +where + J: JSTraceable, +{ + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + // exhaustive match so we don't miss new fields + let Hand { + ref wrist, + ref thumb_metacarpal, + ref thumb_phalanx_proximal, + ref thumb_phalanx_distal, + ref thumb_phalanx_tip, + ref index, + ref middle, + ref ring, + ref little, + } = *self; + wrist.trace(trc); + thumb_metacarpal.trace(trc); + thumb_phalanx_proximal.trace(trc); + thumb_phalanx_distal.trace(trc); + thumb_phalanx_tip.trace(trc); + index.trace(trc); + middle.trace(trc); + ring.trace(trc); + little.trace(trc); + } +} + +unsafe impl<J> JSTraceable for Finger<J> +where + J: JSTraceable, +{ + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + // exhaustive match so we don't miss new fields + let Finger { + ref metacarpal, + ref phalanx_proximal, + ref phalanx_intermediate, + ref phalanx_distal, + ref phalanx_tip, + } = *self; + metacarpal.trace(trc); + phalanx_proximal.trace(trc); + phalanx_intermediate.trace(trc); + phalanx_distal.trace(trc); + phalanx_tip.trace(trc); } } /// Holds a set of JSTraceables that need to be rooted struct RootedTraceableSet { - set: Vec<*const JSTraceable>, + set: Vec<*const dyn JSTraceable>, } thread_local!( /// TLV Holds a set of JSTraceables that need to be rooted - static ROOTED_TRACEABLES: RefCell<RootedTraceableSet> = - RefCell::new(RootedTraceableSet::new()); + static ROOTED_TRACEABLES: RefCell<RootedTraceableSet> = RefCell::new(RootedTraceableSet::new()); ); impl RootedTraceableSet { fn new() -> RootedTraceableSet { - RootedTraceableSet { - set: vec![], - } + RootedTraceableSet { set: vec![] } } - unsafe fn remove(traceable: *const JSTraceable) { + unsafe fn remove(traceable: *const dyn JSTraceable) { ROOTED_TRACEABLES.with(|ref traceables| { let mut traceables = traceables.borrow_mut(); - let idx = - match traceables.set.iter() - .rposition(|x| *x == traceable) { - Some(idx) => idx, - None => unreachable!(), - }; + let idx = match traceables + .set + .iter() + .rposition(|x| *x as *const () == traceable as *const ()) + { + Some(idx) => idx, + None => unreachable!(), + }; traceables.set.remove(idx); }); } - unsafe fn add(traceable: *const JSTraceable) { + unsafe fn add(traceable: *const dyn JSTraceable) { ROOTED_TRACEABLES.with(|ref traceables| { traceables.borrow_mut().set.push(traceable); }) @@ -627,41 +1077,11 @@ impl RootedTraceableSet { /// Roots any JSTraceable thing /// -/// If you have a valid DomObject, use Root. -/// If you have GC things like *mut JSObject or JSVal, use rooted!. -/// If you have an arbitrary number of DomObjects to root, use rooted_vec!. -/// If you know what you're doing, use this. -#[derive(JSTraceable)] -pub struct RootedTraceable<'a, T: 'static + JSTraceable> { - ptr: &'a T, -} - -impl<'a, T: JSTraceable + 'static> RootedTraceable<'a, T> { - /// Root a JSTraceable thing for the life of this RootedTraceable - pub fn new(traceable: &'a T) -> RootedTraceable<'a, T> { - unsafe { - RootedTraceableSet::add(traceable); - } - RootedTraceable { - ptr: traceable, - } - } -} - -impl<'a, T: JSTraceable + 'static> Drop for RootedTraceable<'a, T> { - fn drop(&mut self) { - unsafe { - RootedTraceableSet::remove(self.ptr); - } - } -} - -/// Roots any JSTraceable thing -/// -/// If you have a valid DomObject, use Root. +/// If you have a valid DomObject, use DomRoot. /// If you have GC things like *mut JSObject or JSVal, use rooted!. /// If you have an arbitrary number of DomObjects to root, use rooted_vec!. /// If you know what you're doing, use this. +#[unrooted_must_root_lint::allow_unrooted_interior] pub struct RootedTraceableBox<T: 'static + JSTraceable> { ptr: *mut T, } @@ -673,32 +1093,58 @@ unsafe impl<T: JSTraceable + 'static> JSTraceable for RootedTraceableBox<T> { } impl<T: JSTraceable + 'static> RootedTraceableBox<T> { - /// Root a JSTraceable thing for the life of this RootedTraceable + /// DomRoot a JSTraceable thing for the life of this RootedTraceableBox pub fn new(traceable: T) -> RootedTraceableBox<T> { - let traceable = Box::into_raw(box traceable); + Self::from_box(Box::new(traceable)) + } + + /// Consumes a boxed JSTraceable and roots it for the life of this RootedTraceableBox. + pub fn from_box(boxed_traceable: Box<T>) -> RootedTraceableBox<T> { + let traceable = Box::into_raw(boxed_traceable); unsafe { RootedTraceableSet::add(traceable); } - RootedTraceableBox { - ptr: traceable, - } + RootedTraceableBox { ptr: traceable } + } +} + +impl<T> RootedTraceableBox<Heap<T>> +where + Heap<T>: JSTraceable + 'static, + T: GCMethods + Copy, +{ + pub fn handle(&self) -> Handle<T> { + unsafe { Handle::from_raw((*self.ptr).handle()) } + } +} + +impl<T: JSTraceable + MallocSizeOf> MallocSizeOf for RootedTraceableBox<T> { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + // Briefly resurrect the real Box value so we can rely on the existing calculations. + // Then immediately forget about it again to avoid dropping the box. + let inner = unsafe { Box::from_raw(self.ptr) }; + let size = inner.size_of(ops); + mem::forget(inner); + size + } +} + +impl<T: JSTraceable + Default> Default for RootedTraceableBox<T> { + fn default() -> RootedTraceableBox<T> { + RootedTraceableBox::new(T::default()) } } impl<T: JSTraceable> Deref for RootedTraceableBox<T> { type Target = T; fn deref(&self) -> &T { - unsafe { - &*self.ptr - } + unsafe { &*self.ptr } } } impl<T: JSTraceable> DerefMut for RootedTraceableBox<T> { fn deref_mut(&mut self) -> &mut T { - unsafe { - &mut *self.ptr - } + unsafe { &mut *self.ptr } } } @@ -714,10 +1160,10 @@ impl<T: JSTraceable + 'static> Drop for RootedTraceableBox<T> { /// A vector of items to be rooted with `RootedVec`. /// Guaranteed to be empty when not rooted. /// Usage: `rooted_vec!(let mut v);` or if you have an -/// iterator of `Root`s, `rooted_vec!(let v <- iterator);`. +/// iterator of `DomRoot`s, `rooted_vec!(let v <- iterator);`. #[allow(unrooted_must_root)] #[derive(JSTraceable)] -#[allow_unrooted_interior] +#[unrooted_must_root_lint::allow_unrooted_interior] pub struct RootableVec<T: JSTraceable> { v: Vec<T>, } @@ -725,14 +1171,12 @@ pub struct RootableVec<T: JSTraceable> { impl<T: JSTraceable> RootableVec<T> { /// Create a vector of items of type T that can be rooted later. pub fn new_unrooted() -> RootableVec<T> { - RootableVec { - v: vec![], - } + RootableVec { v: vec![] } } } /// A vector of items that are rooted for the lifetime 'a. -#[allow_unrooted_interior] +#[unrooted_must_root_lint::allow_unrooted_interior] pub struct RootedVec<'a, T: 'static + JSTraceable> { root: &'a mut RootableVec<T>, } @@ -744,25 +1188,22 @@ impl<'a, T: 'static + JSTraceable> RootedVec<'a, T> { unsafe { RootedTraceableSet::add(root); } - RootedVec { - root: root, - } + RootedVec { root: root } } } -impl<'a, T: 'static + JSTraceable + DomObject> RootedVec<'a, JS<T>> { - /// Create a vector of items of type JS<T> that is rooted for +impl<'a, T: 'static + JSTraceable + DomObject> RootedVec<'a, Dom<T>> { + /// Create a vector of items of type Dom<T> that is rooted for /// the lifetime of this struct - pub fn from_iter<I>(root: &'a mut RootableVec<JS<T>>, iter: I) -> Self - where I: Iterator<Item = Root<T>> + pub fn from_iter<I>(root: &'a mut RootableVec<Dom<T>>, iter: I) -> Self + where + I: Iterator<Item = DomRoot<T>>, { unsafe { RootedTraceableSet::add(root); } - root.v.extend(iter.map(|item| JS::from_ref(&*item))); - RootedVec { - root: root, - } + root.v.extend(iter.map(|item| Dom::from_ref(&*item))); + RootedVec { root: root } } } diff --git a/components/script/dom/bindings/transferable.rs b/components/script/dom/bindings/transferable.rs new file mode 100644 index 00000000000..608988db35d --- /dev/null +++ b/components/script/dom/bindings/transferable.rs @@ -0,0 +1,21 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Trait representing the concept of [transferable objects] +//! (https://html.spec.whatwg.org/multipage/#transferable-objects). + +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::structuredclone::StructuredDataHolder; +use crate::dom::globalscope::GlobalScope; +use js::jsapi::MutableHandleObject; + +pub trait Transferable: DomObject { + fn transfer(&self, sc_holder: &mut StructuredDataHolder) -> Result<u64, ()>; + fn transfer_receive( + owner: &GlobalScope, + sc_holder: &mut StructuredDataHolder, + extra_data: u64, + return_object: MutableHandleObject, + ) -> Result<(), ()>; +} diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index 824bb7de276..17bbae95f1b 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -1,62 +1,79 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Various utilities to glue JavaScript and the DOM implementation together. -use dom::bindings::codegen::InterfaceObjectMap; -use dom::bindings::codegen::PrototypeList; -use dom::bindings::codegen::PrototypeList::{MAX_PROTO_CHAIN_LENGTH, PROTO_OR_IFACE_LENGTH}; -use dom::bindings::conversions::{jsstring_to_str, private_from_proto_check}; -use dom::bindings::error::{Error, throw_dom_exception, throw_invalid_this}; -use dom::bindings::inheritance::TopTypeId; -use dom::bindings::str::DOMString; -use dom::bindings::trace::trace_object; -use dom::browsingcontext; -use dom::domexception::DOMException; -use dom::globalscope::GlobalScope; -use heapsize::HeapSizeOf; -use js; -use js::JS_CALLEE; +use crate::dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionBinding::DOMExceptionMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::InterfaceObjectMap; +use crate::dom::bindings::codegen::PrototypeList; +use crate::dom::bindings::codegen::PrototypeList::{MAX_PROTO_CHAIN_LENGTH, PROTO_OR_IFACE_LENGTH}; +use crate::dom::bindings::conversions::{ + jsstring_to_str, private_from_proto_check, PrototypeCheck, +}; +use crate::dom::bindings::error::{throw_dom_exception, throw_invalid_this, Error}; +use crate::dom::bindings::inheritance::TopTypeId; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::trace_object; +use crate::dom::windowproxy; +use crate::script_runtime::JSContext as SafeJSContext; +use js::conversions::ToJSValConvertible; +use js::glue::SetIsFrameIdCallback; +use js::glue::SetThrowDOMExceptionCallback; use js::glue::{CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, IsWrapper}; -use js::glue::{GetCrossCompartmentWrapper, CreateCrossOriginWrapper, GetSecurityWrapper, GetOpaqueWrapper, WrapperNew}; -use js::glue::{GetPrincipalOrigin, CreateWrapperProxyHandler, UncheckedUnwrapObject}; -use js::glue::{RUST_FUNCTION_VALUE_TO_JITINFO, RUST_JSID_IS_INT, RUST_JSID_IS_STRING}; -use js::glue::{RUST_JSID_TO_INT, RUST_JSID_TO_STRING, UnwrapObject}; -use js::glue::{SetThrowDOMExceptionCallback}; -use js::jsapi::{JS_GetClass, JS_GetCompartmentPrincipals, JSPrincipals}; -use js::jsapi::{CallArgs, DOMCallbacks, GetGlobalForObjectCrossCompartment}; -use js::jsapi::{HandleId, HandleObject, HandleValue, Heap, JSAutoCompartment, JSContext}; +use js::glue::{ + CreateCrossOriginWrapper, GetCrossCompartmentWrapper, GetOpaqueWrapper, GetSecurityWrapper, + JS_GetReservedSlot, WrapperNew, +}; +use js::glue::{CreateWrapperProxyHandler, GetPrincipalOrigin, UncheckedUnwrapObject}; +use js::glue::{UnwrapObjectDynamic, UnwrapObjectStatic, RUST_JSID_TO_INT, RUST_JSID_TO_STRING}; +use js::glue::{ + RUST_FUNCTION_VALUE_TO_JITINFO, RUST_JSID_IS_INT, RUST_JSID_IS_STRING, RUST_JSID_IS_VOID, +}; +use js::jsapi::jsid; +use js::jsapi::HandleId as RawHandleId; +use js::jsapi::HandleObject as RawHandleObject; +use js::jsapi::MutableHandleIdVector as RawMutableHandleIdVector; +use js::jsapi::MutableHandleObject as RawMutableHandleObject; +use js::jsapi::RootedId; +use js::jsapi::{AtomToLinearString, GetLinearStringCharAt, GetLinearStringLength}; +use js::jsapi::{CallArgs, DOMCallbacks, GetNonCCWObjectGlobal}; +use js::jsapi::{Heap, JSAutoRealm, JSContext, JS_FreezeObject}; +use js::jsapi::{JSAtom, JS_IsExceptionPending, JS_IsGlobalObject}; use js::jsapi::{JSJitInfo, JSObject, JSTracer, JSWrapObjectCallbacks}; -use js::jsapi::{JS_DeletePropertyById, JS_EnumerateStandardClasses}; -use js::jsapi::{JS_ForwardGetPropertyTo, JS_GetLatin1StringCharsAndLength}; -use js::jsapi::{JS_GetProperty, JS_GetPrototype, JS_GetReservedSlot, JS_HasProperty}; -use js::jsapi::{JS_HasPropertyById, JS_IsExceptionPending, JS_IsGlobalObject}; -use js::jsapi::{JS_ResolveStandardClass, JS_SetProperty, ToWindowProxyIfWindow}; -use js::jsapi::{JS_StringHasLatin1Chars, MutableHandleValue, ObjectOpResult}; +use js::jsapi::{JSPrincipals, JS_GetClass, JS_GetCompartmentPrincipals}; +use js::jsapi::{ + JS_DeprecatedStringHasLatin1Chars, JS_ResolveStandardClass, ObjectOpResult, StringIsArrayIndex, +}; +use js::jsapi::{JS_EnumerateStandardClasses, JS_GetLatin1StringCharsAndLength}; use js::jsval::{JSVal, UndefinedValue}; -use js::rust::{GCMethods, ToString, get_object_class, is_dom_class}; +use js::rust::is_window; +use js::rust::wrappers::JS_DeletePropertyById; +use js::rust::wrappers::JS_ForwardGetPropertyTo; +use js::rust::wrappers::JS_GetProperty; +use js::rust::wrappers::JS_GetPrototype; +use js::rust::wrappers::JS_HasProperty; +use js::rust::wrappers::JS_HasPropertyById; +use js::rust::wrappers::JS_SetProperty; use js::rust::{get_context_compartment, get_object_compartment}; -use libc; +use js::rust::{get_object_class, is_dom_class, GCMethods, ToString, ToWindowProxyIfWindow}; +use js::rust::{Handle, HandleId, HandleObject, HandleValue, MutableHandleValue}; +use js::typedarray::{CreateWith, Float32Array}; +use js::JS_CALLEE; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use servo_url::MutableOrigin; -use std::ffi::{CString, CStr}; +use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_void}; use std::ptr; use std::slice; use std::str; -use dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionBinding::DOMExceptionMethods; -use js::glue::SetIsFrameIdCallback; -use js::jsapi::jsid; -use js::jsapi::RootedId; -use js::rust::is_window; -use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding::DissimilarOriginWindowBinding::DissimilarOriginWindowMethods; /// Proxy handler for a WindowProxy. pub struct WindowProxyHandler(pub *const libc::c_void); -impl HeapSizeOf for WindowProxyHandler { - fn heap_size_of_children(&self) -> usize { +impl MallocSizeOf for WindowProxyHandler { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { // FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs. 0 } @@ -96,7 +113,9 @@ enum CrossOriginObjectType { unsafe fn identify_cross_origin_object(obj: HandleObject) -> CrossOriginObjectType { let obj = UncheckedUnwrapObject(obj.get(), /* stopAtWindowProxy = */ 0); let obj_class = JS_GetClass(obj); - let name = str::from_utf8(CStr::from_ptr((*obj_class).name).to_bytes()).unwrap().to_owned(); + let name = str::from_utf8(CStr::from_ptr((*obj_class).name).to_bytes()) + .unwrap() + .to_owned(); match &*name { "Location" => CrossOriginObjectType::CrossOriginLocation, "Window" => CrossOriginObjectType::CrossOriginWindow, @@ -108,7 +127,7 @@ unsafe fn target_subsumes_obj(cx: *mut JSContext, obj: HandleObject) -> bool { //step 1 get compartment let obj_c = get_object_compartment(obj.get()); let ctx_c = get_context_compartment(cx); - + //step 2 get principals let obj_p = JS_GetCompartmentPrincipals(obj_c); let ctx_p = JS_GetCompartmentPrincipals(ctx_c); @@ -138,8 +157,8 @@ unsafe fn get_cross_origin_wrapper() -> *const ::libc::c_void { CreateCrossOriginWrapper() } -//TODO is same_origin_domain equivalent to subsumes for our purposes -pub unsafe extern fn subsumes(obj: *mut JSPrincipals, other: *mut JSPrincipals) -> bool { +//TODO is same_origin_domain equivalent to subsumes for our purposes +pub unsafe extern "C" fn subsumes(obj: *mut JSPrincipals, other: *mut JSPrincipals) -> bool { let obj = &ServoJSPrincipal(obj); let other = &ServoJSPrincipal(other); let obj_origin = obj.origin(); @@ -150,7 +169,7 @@ pub unsafe extern fn subsumes(obj: *mut JSPrincipals, other: *mut JSPrincipals) unsafe fn select_wrapper(cx: *mut JSContext, obj: HandleObject) -> *const libc::c_void { let security_wrapper = !target_subsumes_obj(cx, obj); if !security_wrapper { - return GetCrossCompartmentWrapper() + return GetCrossCompartmentWrapper(); }; if identify_cross_origin_object(obj) != CrossOriginObjectType::CrossOriginOpaque { @@ -160,8 +179,7 @@ unsafe fn select_wrapper(cx: *mut JSContext, obj: HandleObject) -> *const libc:: get_opaque_wrapper() } - -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] /// Static data associated with a global object. pub struct GlobalStaticData { /// The WindowProxy proxy handler for this global. @@ -172,7 +190,7 @@ impl GlobalStaticData { /// Creates a new GlobalStaticData. pub fn new() -> GlobalStaticData { GlobalStaticData { - windowproxy_handler: browsingcontext::new_window_proxy_handler(), + windowproxy_handler: windowproxy::new_window_proxy_handler(), } } } @@ -191,9 +209,8 @@ pub const DOM_PROTOTYPE_SLOT: u32 = js::JSCLASS_GLOBAL_SLOT_COUNT; // changes. pub const JSCLASS_DOM_GLOBAL: u32 = js::JSCLASS_USERBIT1; - /// The struct that holds inheritance information for DOM object reflectors. -#[derive(Copy, Clone)] +#[derive(Clone, Copy)] pub struct DOMClass { /// A list of interfaces that this object implements, in order of decreasing /// derivedness. @@ -202,8 +219,8 @@ pub struct DOMClass { /// The type ID of that interface. pub type_id: TopTypeId, - /// The HeapSizeOf function wrapper for that interface. - pub heap_size_of: unsafe fn(*const c_void) -> usize, + /// The MallocSizeOf function wrapper for that interface. + pub malloc_size_of: unsafe fn(ops: &mut MallocSizeOfOps, *const c_void) -> usize, /// The `Globals` flag for this global interface, if any. pub global: InterfaceObjectMap::Globals, @@ -225,12 +242,33 @@ impl Clone for DOMJSClass { } unsafe impl Sync for DOMJSClass {} +/// Returns a JSVal representing a frozen array of ports +pub fn to_frozen_array<T: ToJSValConvertible>(convertibles: &[T], cx: SafeJSContext) -> JSVal { + rooted!(in(*cx) let mut ports = UndefinedValue()); + unsafe { convertibles.to_jsval(*cx, ports.handle_mut()) }; + + rooted!(in(*cx) let obj = ports.to_object()); + unsafe { JS_FreezeObject(*cx, RawHandleObject::from(obj.handle())) }; + *ports +} + +/// Creates a Float32 array +pub fn create_typed_array(cx: SafeJSContext, src: &[f32], dst: &Heap<*mut JSObject>) { + rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); + unsafe { + let _ = Float32Array::create(*cx, CreateWith::Slice(src), array.handle_mut()); + } + (*dst).set(array.get()); +} + /// Returns the ProtoOrIfaceArray for the given global object. /// Fails if `global` is not a DOM global object. pub fn get_proto_or_iface_array(global: *mut JSObject) -> *mut ProtoOrIfaceArray { unsafe { - assert!(((*get_object_class(global)).flags & JSCLASS_DOM_GLOBAL) != 0); - JS_GetReservedSlot(global, DOM_PROTOTYPE_SLOT).to_private() as *mut ProtoOrIfaceArray + assert_ne!(((*get_object_class(global)).flags & JSCLASS_DOM_GLOBAL), 0); + let mut slot = UndefinedValue(); + JS_GetReservedSlot(global, DOM_PROTOTYPE_SLOT, &mut slot); + slot.to_private() as *mut ProtoOrIfaceArray } } @@ -241,14 +279,15 @@ pub type ProtoOrIfaceArray = [*mut JSObject; PROTO_OR_IFACE_LENGTH]; /// set to true and `*vp` to the value, otherwise `*found` is set to false. /// /// Returns false on JSAPI failure. -pub unsafe fn get_property_on_prototype(cx: *mut JSContext, - proxy: HandleObject, - receiver: HandleValue, - id: HandleId, - found: *mut bool, - vp: MutableHandleValue) - -> bool { - rooted!(in(cx) let mut proto = ptr::null_mut()); +pub unsafe fn get_property_on_prototype( + cx: *mut JSContext, + proxy: HandleObject, + receiver: HandleValue, + id: HandleId, + found: *mut bool, + vp: MutableHandleValue, +) -> bool { + rooted!(in(cx) let mut proto = ptr::null_mut::<JSObject>()); if !JS_GetPrototype(cx, proxy, proto.handle_mut()) || proto.is_null() { *found = false; return true; @@ -258,8 +297,7 @@ pub unsafe fn get_property_on_prototype(cx: *mut JSContext, return false; } *found = has_property; - let no_output = vp.ptr.is_null(); - if !has_property || no_output { + if !has_property { return true; } @@ -268,48 +306,105 @@ pub unsafe fn get_property_on_prototype(cx: *mut JSContext, /// Get an array index from the given `jsid`. Returns `None` if the given /// `jsid` is not an integer. -pub fn get_array_index_from_id(_cx: *mut JSContext, id: HandleId) -> Option<u32> { - unsafe { - if RUST_JSID_IS_INT(id) { - return Some(RUST_JSID_TO_INT(id) as u32); - } +pub unsafe fn get_array_index_from_id(_cx: *mut JSContext, id: HandleId) -> Option<u32> { + let raw_id = id.into(); + if RUST_JSID_IS_INT(raw_id) { + return Some(RUST_JSID_TO_INT(raw_id) as u32); + } + + if RUST_JSID_IS_VOID(raw_id) || !RUST_JSID_IS_STRING(raw_id) { + return None; + } + + let atom = RUST_JSID_TO_STRING(raw_id) as *mut JSAtom; + let s = AtomToLinearString(atom); + if GetLinearStringLength(s) == 0 { + return None; + } + + let chars = [GetLinearStringCharAt(s, 0)]; + let first_char = char::decode_utf16(chars.iter().cloned()) + .next() + .map_or('\0', |r| r.unwrap_or('\0')); + if first_char < 'a' || first_char > 'z' { + return None; + } + + let mut i = 0; + if StringIsArrayIndex(s, &mut i) { + Some(i) + } else { None } - // if id is length atom, -1, otherwise - /*return if JSID_IS_ATOM(id) { - let atom = JSID_TO_ATOM(id); - //let s = *GetAtomChars(id); - if s > 'a' && s < 'z' { - return -1; - } - let i = 0; - let str = AtomToLinearString(JSID_TO_ATOM(id)); - return if StringIsArray(str, &mut i) != 0 { i } else { -1 } + /*let s = jsstr_to_string(cx, RUST_JSID_TO_STRING(raw_id)); + if s.len() == 0 { + return None; + } + + let first = s.chars().next().unwrap(); + if first.is_ascii_lowercase() { + return None; + } + + let mut i: u32 = 0; + let is_array = if s.is_ascii() { + let chars = s.as_bytes(); + StringIsArrayIndex1(chars.as_ptr() as *const _, chars.len() as u32, &mut i) } else { - IdToInt32(cx, id); + let chars = s.encode_utf16().collect::<Vec<u16>>(); + let slice = chars.as_slice(); + StringIsArrayIndex2(slice.as_ptr(), chars.len() as u32, &mut i) + }; + + if is_array { + Some(i) + } else { + None }*/ } /// Find the enum equivelent of a string given by `v` in `pairs`. /// Returns `Err(())` on JSAPI failure (there is a pending exception), and /// `Ok((None, value))` if there was no matching string. -pub unsafe fn find_enum_value<'a, T>(cx: *mut JSContext, - v: HandleValue, - pairs: &'a [(&'static str, T)]) - -> Result<(Option<&'a T>, DOMString), ()> { +pub unsafe fn find_enum_value<'a, T>( + cx: *mut JSContext, + v: HandleValue, + pairs: &'a [(&'static str, T)], +) -> Result<(Option<&'a T>, DOMString), ()> { let jsstr = ToString(cx, v); if jsstr.is_null() { return Err(()); } let search = jsstring_to_str(cx, jsstr); - Ok((pairs.iter().find(|&&(key, _)| search == *key).map(|&(_, ref ev)| ev), search)) + Ok(( + pairs + .iter() + .find(|&&(key, _)| search == *key) + .map(|&(_, ref ev)| ev), + search, + )) +} + +/// Returns wether `obj` is a platform object using dynamic unwrap +/// <https://heycam.github.io/webidl/#dfn-platform-object> +pub fn is_platform_object_dynamic(obj: *mut JSObject, cx: *mut JSContext) -> bool { + is_platform_object(obj, &|o| unsafe { + UnwrapObjectDynamic(o, cx, /* stopAtWindowProxy = */ 0) + }) +} + +/// Returns wether `obj` is a platform object using static unwrap +/// <https://heycam.github.io/webidl/#dfn-platform-object> +pub fn is_platform_object_static(obj: *mut JSObject) -> bool { + is_platform_object(obj, &|o| unsafe { UnwrapObjectStatic(o) }) } -/// Returns wether `obj` is a platform object -/// https://heycam.github.io/webidl/#dfn-platform-object -pub fn is_platform_object(obj: *mut JSObject) -> bool { +fn is_platform_object( + obj: *mut JSObject, + unwrap_obj: &dyn Fn(*mut JSObject) -> *mut JSObject, +) -> bool { unsafe { // Fast-path the common case let mut clasp = get_object_class(obj); @@ -318,7 +413,7 @@ pub fn is_platform_object(obj: *mut JSObject) -> bool { } // Now for simplicity check for security wrappers before anything else if IsWrapper(obj) { - let unwrapped_obj = UnwrapObject(obj, /* stopAtWindowProxy = */ 0); + let unwrapped_obj = unwrap_obj(obj); if unwrapped_obj.is_null() { return false; } @@ -332,23 +427,26 @@ pub fn is_platform_object(obj: *mut JSObject) -> bool { /// Get the property with name `property` from `object`. /// Returns `Err(())` on JSAPI failure (there is a pending exception), and /// `Ok(false)` if there was no property with the given name. -pub fn get_dictionary_property(cx: *mut JSContext, - object: HandleObject, - property: &str, - rval: MutableHandleValue) - -> Result<bool, ()> { - fn has_property(cx: *mut JSContext, - object: HandleObject, - property: &CString, - found: &mut bool) - -> bool { +pub fn get_dictionary_property( + cx: *mut JSContext, + object: HandleObject, + property: &str, + rval: MutableHandleValue, +) -> Result<bool, ()> { + fn has_property( + cx: *mut JSContext, + object: HandleObject, + property: &CString, + found: &mut bool, + ) -> bool { unsafe { JS_HasProperty(cx, object, property.as_ptr(), found) } } - fn get_property(cx: *mut JSContext, - object: HandleObject, - property: &CString, - value: MutableHandleValue) - -> bool { + fn get_property( + cx: *mut JSContext, + object: HandleObject, + property: &CString, + value: MutableHandleValue, + ) -> bool { unsafe { JS_GetProperty(cx, object, property.as_ptr(), value) } } @@ -376,11 +474,12 @@ pub fn get_dictionary_property(cx: *mut JSContext, /// Set the property with name `property` from `object`. /// Returns `Err(())` on JSAPI failure, or null object, /// and Ok(()) otherwise -pub fn set_dictionary_property(cx: *mut JSContext, - object: HandleObject, - property: &str, - value: HandleValue) - -> Result<(), ()> { +pub fn set_dictionary_property( + cx: *mut JSContext, + object: HandleObject, + property: &str, + value: HandleValue, +) -> Result<(), ()> { if object.get().is_null() { return Err(()); } @@ -396,12 +495,13 @@ pub fn set_dictionary_property(cx: *mut JSContext, } /// Returns whether `proxy` has a property `id` on its prototype. -pub unsafe fn has_property_on_prototype(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - found: &mut bool) - -> bool { - rooted!(in(cx) let mut proto = ptr::null_mut()); +pub unsafe fn has_property_on_prototype( + cx: *mut JSContext, + proxy: HandleObject, + id: HandleId, + found: &mut bool, +) -> bool { + rooted!(in(cx) let mut proto = ptr::null_mut::<JSObject>()); if !JS_GetPrototype(cx, proxy, proto.handle_mut()) { return false; } @@ -426,32 +526,39 @@ pub unsafe fn trace_global(tracer: *mut JSTracer, obj: *mut JSObject) { let array = get_proto_or_iface_array(obj); for proto in (*array).iter() { if !proto.is_null() { - trace_object(tracer, - "prototype", - &*(proto as *const *mut JSObject as *const Heap<*mut JSObject>)); + trace_object( + tracer, + "prototype", + &*(proto as *const *mut JSObject as *const Heap<*mut JSObject>), + ); } } } /// Enumerate lazy properties of a global object. -pub unsafe extern "C" fn enumerate_global(cx: *mut JSContext, obj: HandleObject) -> bool { +pub unsafe extern "C" fn enumerate_global( + cx: *mut JSContext, + obj: RawHandleObject, + _props: RawMutableHandleIdVector, + _enumerable_only: bool, +) -> bool { assert!(JS_IsGlobalObject(obj.get())); if !JS_EnumerateStandardClasses(cx, obj) { return false; } for init_fun in InterfaceObjectMap::MAP.values() { - init_fun(cx, obj); + init_fun(SafeJSContext::from_ptr(cx), Handle::from_raw(obj)); } true } /// Resolve a lazy global property, for interface objects and named constructors. pub unsafe extern "C" fn resolve_global( - cx: *mut JSContext, - obj: HandleObject, - id: HandleId, - rval: *mut bool) - -> bool { + cx: *mut JSContext, + obj: RawHandleObject, + id: RawHandleId, + rval: *mut bool, +) -> bool { assert!(JS_IsGlobalObject(obj.get())); if !JS_ResolveStandardClass(cx, obj, id, rval) { return false; @@ -465,7 +572,7 @@ pub unsafe extern "C" fn resolve_global( } let string = RUST_JSID_TO_STRING(id); - if !JS_StringHasLatin1Chars(string) { + if !JS_DeprecatedStringHasLatin1Chars(string) { *rval = false; return true; } @@ -475,7 +582,7 @@ pub unsafe extern "C" fn resolve_global( let bytes = slice::from_raw_parts(ptr, length as usize); if let Some(init_fun) = InterfaceObjectMap::MAP.get(bytes) { - init_fun(cx, obj); + init_fun(SafeJSContext::from_ptr(cx), Handle::from_raw(obj)); *rval = true; } else { *rval = false; @@ -483,10 +590,11 @@ pub unsafe extern "C" fn resolve_global( true } -unsafe extern "C" fn wrap(cx: *mut JSContext, - _existing: HandleObject, - obj: HandleObject) - -> *mut JSObject { +unsafe extern "C" fn wrap( + cx: *mut JSContext, + _existing: RawHandleObject, + obj: RawHandleObject, +) -> *mut JSObject { // FIXME terrible idea. need security wrappers // https://github.com/servo/servo/issues/2382 let wrapper = select_wrapper(cx, obj); @@ -502,7 +610,7 @@ unsafe extern "C" fn is_frame_id(cx: *mut JSContext, obj: *mut JSObject, id_arg: // println!("is frame id"); /*if IsWrapper(obj) { return false; - } + } //let id = RootedId{_base: cx, ptr: idArg}; //will this work for window and dissimilaroriginwindow? probs not @@ -519,17 +627,20 @@ unsafe extern "C" fn is_frame_id(cx: *mut JSContext, obj: *mut JSObject, id_arg: false } -unsafe extern "C" fn pre_wrap(cx: *mut JSContext, - _existing: HandleObject, - obj: HandleObject, - _object_passed_to_wrap: HandleObject) - -> *mut JSObject { +unsafe extern "C" fn pre_wrap( + cx: *mut JSContext, + _scope: RawHandleObject, + _orig_obj: RawHandleObject, + obj: RawHandleObject, + _object_passed_to_wrap: RawHandleObject, + rval: RawMutableHandleObject, +) { SetThrowDOMExceptionCallback(Some(throw_dom_exception_callback)); SetIsFrameIdCallback(Some(is_frame_id)); - let _ac = JSAutoCompartment::new(cx, obj.get()); + let _ac = JSAutoRealm::new(cx, obj.get()); let obj = ToWindowProxyIfWindow(obj.get()); assert!(!obj.is_null()); - obj + rval.set(obj) } /// Callback table for use with JS_SetWrapObjectCallbacks @@ -539,41 +650,48 @@ pub static WRAP_CALLBACKS: JSWrapObjectCallbacks = JSWrapObjectCallbacks { }; /// Deletes the property `id` from `object`. -pub unsafe fn delete_property_by_id(cx: *mut JSContext, - object: HandleObject, - id: HandleId, - bp: *mut ObjectOpResult) - -> bool { +pub unsafe fn delete_property_by_id( + cx: *mut JSContext, + object: HandleObject, + id: HandleId, + bp: *mut ObjectOpResult, +) -> bool { JS_DeletePropertyById(cx, object, id, bp) } -unsafe fn generic_call(cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal, - is_lenient: bool, - call: unsafe extern fn(*const JSJitInfo, *mut JSContext, - HandleObject, *mut libc::c_void, u32, - *mut JSVal) - -> bool) - -> bool { +unsafe fn generic_call( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, + is_lenient: bool, + call: unsafe extern "C" fn( + *const JSJitInfo, + *mut JSContext, + RawHandleObject, + *mut libc::c_void, + u32, + *mut JSVal, + ) -> bool, +) -> bool { let args = CallArgs::from_vp(vp, argc); + + let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); + let proto_id = (*info).__bindgen_anon_2.protoID; + let thisobj = args.thisv(); if !thisobj.get().is_null_or_undefined() && !thisobj.get().is_object() { + throw_invalid_this(cx, proto_id); return false; } - let obj = if thisobj.get().is_object() { + + rooted!(in(cx) let obj = if thisobj.get().is_object() { thisobj.get().to_object() } else { - GetGlobalForObjectCrossCompartment(JS_CALLEE(cx, vp).to_object_or_null()) - }; - rooted!(in(cx) let obj = obj); - let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); - let proto_id = (*info).protoID; - let depth = (*info).depth; - let proto_check = |class: &'static DOMClass| { - class.interface_chain[depth as usize] as u16 == proto_id - }; - let this = match private_from_proto_check(obj.get(), proto_check) { + GetNonCCWObjectGlobal(JS_CALLEE(cx, vp).to_object_or_null()) + }); + let depth = (*info).__bindgen_anon_3.depth as usize; + let proto_check = PrototypeCheck::Depth { depth, proto_id }; + let this = match private_from_proto_check(obj.get(), cx, proto_check) { Ok(val) => val, Err(()) => { if is_lenient { @@ -584,42 +702,53 @@ unsafe fn generic_call(cx: *mut JSContext, throw_invalid_this(cx, proto_id); return false; } - } + }, }; - call(info, cx, obj.handle(), this as *mut libc::c_void, argc, vp) + call( + info, + cx, + obj.handle().into(), + this as *mut libc::c_void, + argc, + vp, + ) } /// Generic method of IDL interface. -pub unsafe extern "C" fn generic_method(cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal) - -> bool { +pub unsafe extern "C" fn generic_method( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { generic_call(cx, argc, vp, false, CallJitMethodOp) } /// Generic getter of IDL interface. -pub unsafe extern "C" fn generic_getter(cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal) - -> bool { +pub unsafe extern "C" fn generic_getter( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { generic_call(cx, argc, vp, false, CallJitGetterOp) } /// Generic lenient getter of IDL interface. -pub unsafe extern "C" fn generic_lenient_getter(cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal) - -> bool { +pub unsafe extern "C" fn generic_lenient_getter( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { generic_call(cx, argc, vp, true, CallJitGetterOp) } -unsafe extern "C" fn call_setter(info: *const JSJitInfo, - cx: *mut JSContext, - handle: HandleObject, - this: *mut libc::c_void, - argc: u32, - vp: *mut JSVal) - -> bool { +unsafe extern "C" fn call_setter( + info: *const JSJitInfo, + cx: *mut JSContext, + handle: RawHandleObject, + this: *mut libc::c_void, + argc: u32, + vp: *mut JSVal, +) -> bool { if !CallJitSetterOp(info, cx, handle, this, argc, vp) { return false; } @@ -628,31 +757,34 @@ unsafe extern "C" fn call_setter(info: *const JSJitInfo, } /// Generic setter of IDL interface. -pub unsafe extern "C" fn generic_setter(cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal) - -> bool { +pub unsafe extern "C" fn generic_setter( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { generic_call(cx, argc, vp, false, call_setter) } /// Generic lenient setter of IDL interface. -pub unsafe extern "C" fn generic_lenient_setter(cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal) - -> bool { +pub unsafe extern "C" fn generic_lenient_setter( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { generic_call(cx, argc, vp, true, call_setter) } -unsafe extern "C" fn instance_class_has_proto_at_depth(clasp: *const js::jsapi::Class, - proto_id: u32, - depth: u32) - -> bool { +unsafe extern "C" fn instance_class_has_proto_at_depth( + clasp: *const js::jsapi::JSClass, + proto_id: u32, + depth: u32, +) -> bool { let domclass: *const DOMJSClass = clasp as *const _; let domclass = &*domclass; domclass.dom_class.interface_chain[depth as usize] as u32 == proto_id } -#[allow(missing_docs)] // FIXME +#[allow(missing_docs)] // FIXME pub const DOM_CALLBACKS: DOMCallbacks = DOMCallbacks { instanceClassMatchesProto: Some(instance_class_has_proto_at_depth), }; diff --git a/components/script/dom/bindings/weakref.rs b/components/script/dom/bindings/weakref.rs index e6201bf3141..5c74e111816 100644 --- a/components/script/dom/bindings/weakref.rs +++ b/components/script/dom/bindings/weakref.rs @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Weak-referenceable JS-managed DOM objects. //! @@ -11,17 +11,20 @@ //! slot. When all associated `WeakRef` values are dropped, the //! `WeakBox` itself is dropped too. -use core::nonzero::NonZero; -use dom::bindings::js::Root; -use dom::bindings::reflector::DomObject; -use dom::bindings::trace::JSTraceable; -use heapsize::HeapSizeOf; -use js::jsapi::{JSTracer, JS_GetReservedSlot, JS_SetReservedSlot}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::trace::JSTraceable; +use js::glue::JS_GetReservedSlot; +use js::jsapi::{JSTracer, JS_SetReservedSlot}; use js::jsval::PrivateValue; +use js::jsval::UndefinedValue; use libc::c_void; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use std::cell::{Cell, UnsafeCell}; use std::mem; use std::ops::{Deref, DerefMut, Drop}; +use std::ptr; /// The index of the slot wherein a pointer to the weak holder cell is /// stored for weak-referenceable bindings. We use slot 1 for holding it, @@ -30,19 +33,20 @@ use std::ops::{Deref, DerefMut, Drop}; pub const DOM_WEAK_SLOT: u32 = 1; /// A weak reference to a JS-managed DOM object. -#[allow_unrooted_interior] +#[allow(unrooted_must_root)] +#[unrooted_must_root_lint::allow_unrooted_interior] pub struct WeakRef<T: WeakReferenceable> { - ptr: NonZero<*mut WeakBox<T>>, + ptr: ptr::NonNull<WeakBox<T>>, } /// The inner box of weak references, public for the finalization in codegen. -#[must_root] +#[unrooted_must_root_lint::must_root] pub struct WeakBox<T: WeakReferenceable> { /// The reference count. When it reaches zero, the `value` field should /// have already been set to `None`. The pointee contributes one to the count. pub count: Cell<usize>, /// The pointer to the JS-managed object, set to None when it is collected. - pub value: Cell<Option<NonZero<*const T>>>, + pub value: Cell<Option<ptr::NonNull<T>>>, } /// Trait implemented by weak-referenceable interfaces. @@ -51,26 +55,29 @@ pub trait WeakReferenceable: DomObject + Sized { fn downgrade(&self) -> WeakRef<Self> { unsafe { let object = self.reflector().get_jsobject().get(); - let mut ptr = JS_GetReservedSlot(object, - DOM_WEAK_SLOT) - .to_private() as *mut WeakBox<Self>; + let mut slot = UndefinedValue(); + JS_GetReservedSlot(object, DOM_WEAK_SLOT, &mut slot); + let mut ptr = slot.to_private() as *mut WeakBox<Self>; if ptr.is_null() { trace!("Creating new WeakBox holder for {:p}.", self); - ptr = Box::into_raw(box WeakBox { + ptr = Box::into_raw(Box::new(WeakBox { count: Cell::new(1), - value: Cell::new(Some(NonZero::new(self))), - }); - JS_SetReservedSlot(object, DOM_WEAK_SLOT, PrivateValue(ptr as *const c_void)); + value: Cell::new(Some(ptr::NonNull::from(self))), + })); + let val = PrivateValue(ptr as *const c_void); + JS_SetReservedSlot(object, DOM_WEAK_SLOT, &val); } let box_ = &*ptr; assert!(box_.value.get().is_some()); let new_count = box_.count.get() + 1; - trace!("Incrementing WeakBox refcount for {:p} to {}.", - self, - new_count); + trace!( + "Incrementing WeakBox refcount for {:p} to {}.", + self, + new_count + ); box_.count.set(new_count); WeakRef { - ptr: NonZero::new(ptr), + ptr: ptr::NonNull::new_unchecked(ptr), } } } @@ -84,40 +91,42 @@ impl<T: WeakReferenceable> WeakRef<T> { value.downgrade() } - /// Root a weak reference. Returns `None` if the object was already collected. - pub fn root(&self) -> Option<Root<T>> { - unsafe { &**self.ptr }.value.get().map(Root::new) + /// DomRoot a weak reference. Returns `None` if the object was already collected. + pub fn root(&self) -> Option<DomRoot<T>> { + unsafe { &*self.ptr.as_ptr() } + .value + .get() + .map(|ptr| unsafe { DomRoot::from_ref(&*ptr.as_ptr()) }) } /// Return whether the weakly-referenced object is still alive. pub fn is_alive(&self) -> bool { - unsafe { &**self.ptr }.value.get().is_some() + unsafe { &*self.ptr.as_ptr() }.value.get().is_some() } } impl<T: WeakReferenceable> Clone for WeakRef<T> { fn clone(&self) -> WeakRef<T> { unsafe { - let box_ = &**self.ptr; + let box_ = &*self.ptr.as_ptr(); let new_count = box_.count.get() + 1; box_.count.set(new_count); - WeakRef { - ptr: self.ptr, - } + WeakRef { ptr: self.ptr } } } } -impl<T: WeakReferenceable> HeapSizeOf for WeakRef<T> { - fn heap_size_of_children(&self) -> usize { +impl<T: WeakReferenceable> MallocSizeOf for WeakRef<T> { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { 0 } } impl<T: WeakReferenceable> PartialEq for WeakRef<T> { - fn eq(&self, other: &Self) -> bool { + fn eq(&self, other: &Self) -> bool { unsafe { - (**self.ptr).value.get() == (**other.ptr).value.get() + (*self.ptr.as_ptr()).value.get().map(ptr::NonNull::as_ptr) == + (*other.ptr.as_ptr()).value.get().map(ptr::NonNull::as_ptr) } } } @@ -125,8 +134,8 @@ impl<T: WeakReferenceable> PartialEq for WeakRef<T> { impl<T: WeakReferenceable> PartialEq<T> for WeakRef<T> { fn eq(&self, other: &T) -> bool { unsafe { - match (**self.ptr).value.get() { - Some(ptr) => *ptr == other, + match self.ptr.as_ref().value.get() { + Some(ptr) => ptr::eq(ptr.as_ptr(), other), None => false, } } @@ -143,7 +152,7 @@ impl<T: WeakReferenceable> Drop for WeakRef<T> { fn drop(&mut self) { unsafe { let (count, value) = { - let weak_box = &**self.ptr; + let weak_box = &*self.ptr.as_ptr(); assert!(weak_box.count.get() > 0); let count = weak_box.count.get() - 1; weak_box.count.set(count); @@ -151,7 +160,7 @@ impl<T: WeakReferenceable> Drop for WeakRef<T> { }; if count == 0 { assert!(value.is_none()); - mem::drop(Box::from_raw(*self.ptr)); + mem::drop(Box::from_raw(self.ptr.as_ptr())); } } } @@ -179,15 +188,17 @@ impl<T: WeakReferenceable> MutableWeakRef<T> { } } - /// Root a mutable weak reference. Returns `None` if the object + /// DomRoot a mutable weak reference. Returns `None` if the object /// was already collected. - pub fn root(&self) -> Option<Root<T>> { - unsafe { &*self.cell.get() }.as_ref().and_then(WeakRef::root) + pub fn root(&self) -> Option<DomRoot<T>> { + unsafe { &*self.cell.get() } + .as_ref() + .and_then(WeakRef::root) } } -impl<T: WeakReferenceable> HeapSizeOf for MutableWeakRef<T> { - fn heap_size_of_children(&self) -> usize { +impl<T: WeakReferenceable> MallocSizeOf for MutableWeakRef<T> { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { 0 } } @@ -207,8 +218,8 @@ unsafe impl<T: WeakReferenceable> JSTraceable for MutableWeakRef<T> { /// A vector of weak references. On tracing, the vector retains /// only references which still point to live objects. -#[allow_unrooted_interior] -#[derive(HeapSizeOf)] +#[unrooted_must_root_lint::allow_unrooted_interior] +#[derive(MallocSizeOf)] pub struct WeakRefVec<T: WeakReferenceable> { vec: Vec<WeakRef<T>>, } @@ -225,7 +236,10 @@ impl<T: WeakReferenceable> WeakRefVec<T> { let mut i = 0; while i < self.vec.len() { if self.vec[i].is_alive() { - f(WeakRefEntry { vec: self, index: &mut i }); + f(WeakRefEntry { + vec: self, + index: &mut i, + }); } else { self.vec.swap_remove(i); } @@ -254,8 +268,8 @@ impl<T: WeakReferenceable> DerefMut for WeakRefVec<T> { /// An entry of a vector of weak references. Passed to the closure /// given to `WeakRefVec::update`. -#[allow_unrooted_interior] -pub struct WeakRefEntry<'a, T: WeakReferenceable + 'a> { +#[unrooted_must_root_lint::allow_unrooted_interior] +pub struct WeakRefEntry<'a, T: WeakReferenceable> { vec: &'a mut WeakRefVec<T>, index: &'a mut usize, } @@ -282,3 +296,34 @@ impl<'a, T: WeakReferenceable + 'a> Drop for WeakRefEntry<'a, T> { *self.index += 1; } } + +#[derive(MallocSizeOf)] +pub struct DOMTracker<T: WeakReferenceable> { + dom_objects: DomRefCell<WeakRefVec<T>>, +} + +impl<T: WeakReferenceable> DOMTracker<T> { + pub fn new() -> Self { + Self { + dom_objects: DomRefCell::new(WeakRefVec::new()), + } + } + + pub fn track(&self, dom_object: &T) { + self.dom_objects.borrow_mut().push(WeakRef::new(dom_object)); + } + + pub fn for_each<F: FnMut(DomRoot<T>)>(&self, mut f: F) { + self.dom_objects.borrow_mut().update(|weak_ref| { + let root = weak_ref.root().unwrap(); + f(root); + }); + } +} + +#[allow(unsafe_code)] +unsafe impl<T: WeakReferenceable> JSTraceable for DOMTracker<T> { + unsafe fn trace(&self, _: *mut JSTracer) { + self.dom_objects.borrow_mut().retain_alive(); + } +} diff --git a/components/script/dom/bindings/xmlname.rs b/components/script/dom/bindings/xmlname.rs index 4522c9c4ac0..b4e6aab289f 100644 --- a/components/script/dom/bindings/xmlname.rs +++ b/components/script/dom/bindings/xmlname.rs @@ -1,38 +1,34 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Functions for validating and extracting qualified XML names. -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::str::DOMString; -use html5ever_atoms::{Prefix, LocalName, Namespace}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::str::DOMString; +use html5ever::{LocalName, Namespace, Prefix}; /// Validate a qualified name. See https://dom.spec.whatwg.org/#validate for details. pub fn validate_qualified_name(qualified_name: &str) -> ErrorResult { + // Step 2. match xml_name_type(qualified_name) { - XMLName::InvalidXMLName => { - // Step 1. - Err(Error::InvalidCharacter) - }, - XMLName::Name => { - // Step 2. - Err(Error::Namespace) - }, + XMLName::InvalidXMLName => Err(Error::InvalidCharacter), + XMLName::Name => Err(Error::InvalidCharacter), // see whatwg/dom#671 XMLName::QName => Ok(()), } } /// Validate a namespace and qualified name and extract their parts. /// See https://dom.spec.whatwg.org/#validate-and-extract for details. -pub fn validate_and_extract(namespace: Option<DOMString>, - qualified_name: &str) - -> Fallible<(Namespace, Option<Prefix>, LocalName)> { +pub fn validate_and_extract( + namespace: Option<DOMString>, + qualified_name: &str, +) -> Fallible<(Namespace, Option<Prefix>, LocalName)> { // Step 1. let namespace = namespace_from_domstring(namespace); // Step 2. - try!(validate_qualified_name(qualified_name)); + validate_qualified_name(qualified_name)?; let colon = ':'; @@ -76,7 +72,7 @@ pub fn validate_and_extract(namespace: Option<DOMString>, (ns, p) => { // Step 10. Ok((ns, p.map(Prefix::from), LocalName::from(local_name))) - } + }, } } @@ -95,36 +91,36 @@ pub fn xml_name_type(name: &str) -> XMLName { fn is_valid_start(c: char) -> bool { match c { ':' | - 'A'...'Z' | + 'A'..='Z' | '_' | - 'a'...'z' | - '\u{C0}'...'\u{D6}' | - '\u{D8}'...'\u{F6}' | - '\u{F8}'...'\u{2FF}' | - '\u{370}'...'\u{37D}' | - '\u{37F}'...'\u{1FFF}' | - '\u{200C}'...'\u{200D}' | - '\u{2070}'...'\u{218F}' | - '\u{2C00}'...'\u{2FEF}' | - '\u{3001}'...'\u{D7FF}' | - '\u{F900}'...'\u{FDCF}' | - '\u{FDF0}'...'\u{FFFD}' | - '\u{10000}'...'\u{EFFFF}' => true, + 'a'..='z' | + '\u{C0}'..='\u{D6}' | + '\u{D8}'..='\u{F6}' | + '\u{F8}'..='\u{2FF}' | + '\u{370}'..='\u{37D}' | + '\u{37F}'..='\u{1FFF}' | + '\u{200C}'..='\u{200D}' | + '\u{2070}'..='\u{218F}' | + '\u{2C00}'..='\u{2FEF}' | + '\u{3001}'..='\u{D7FF}' | + '\u{F900}'..='\u{FDCF}' | + '\u{FDF0}'..='\u{FFFD}' | + '\u{10000}'..='\u{EFFFF}' => true, _ => false, } } fn is_valid_continuation(c: char) -> bool { is_valid_start(c) || - match c { - '-' | - '.' | - '0'...'9' | - '\u{B7}' | - '\u{300}'...'\u{36F}' | - '\u{203F}'...'\u{2040}' => true, - _ => false, - } + match c { + '-' | + '.' | + '0'..='9' | + '\u{B7}' | + '\u{300}'..='\u{36F}' | + '\u{203F}'..='\u{2040}' => true, + _ => false, + } } let mut iter = name.chars(); @@ -140,7 +136,7 @@ pub fn xml_name_type(name: &str) -> XMLName { non_qname_colons = true; } c - } + }, }; for c in iter { diff --git a/components/script/dom/biquadfilternode.rs b/components/script/dom/biquadfilternode.rs new file mode 100644 index 00000000000..540f094bfe5 --- /dev/null +++ b/components/script/dom/biquadfilternode.rs @@ -0,0 +1,187 @@ +/* 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 crate::dom::audionode::AudioNode; +use crate::dom::audioparam::AudioParam; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate; +use crate::dom::bindings::codegen::Bindings::BiquadFilterNodeBinding::BiquadFilterNodeMethods; +use crate::dom::bindings::codegen::Bindings::BiquadFilterNodeBinding::BiquadFilterOptions; +use crate::dom::bindings::codegen::Bindings::BiquadFilterNodeBinding::BiquadFilterType; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::biquad_filter_node::BiquadFilterNodeMessage; +use servo_media::audio::biquad_filter_node::{BiquadFilterNodeOptions, FilterType}; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage}; +use servo_media::audio::param::ParamType; +use std::cell::Cell; +use std::f32; + +#[dom_struct] +pub struct BiquadFilterNode { + node: AudioNode, + gain: Dom<AudioParam>, + frequency: Dom<AudioParam>, + q: Dom<AudioParam>, + detune: Dom<AudioParam>, + filter: Cell<BiquadFilterType>, +} + +impl BiquadFilterNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + window: &Window, + context: &BaseAudioContext, + options: &BiquadFilterOptions, + ) -> Fallible<BiquadFilterNode> { + let node_options = + options + .parent + .unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers); + let filter = Cell::new(options.type_); + let options = options.into(); + let node = AudioNode::new_inherited( + AudioNodeInit::BiquadFilterNode(options), + context, + node_options, + 1, // inputs + 1, // outputs + )?; + let gain = AudioParam::new( + window, + context, + node.node_id(), + ParamType::Gain, + AutomationRate::A_rate, + options.gain, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let q = AudioParam::new( + window, + context, + node.node_id(), + ParamType::Q, + AutomationRate::A_rate, + options.q, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let frequency = AudioParam::new( + window, + context, + node.node_id(), + ParamType::Frequency, + AutomationRate::A_rate, + options.frequency, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let detune = AudioParam::new( + window, + context, + node.node_id(), + ParamType::Detune, + AutomationRate::A_rate, + options.detune, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + Ok(BiquadFilterNode { + node, + filter, + gain: Dom::from_ref(&gain), + q: Dom::from_ref(&q), + frequency: Dom::from_ref(&frequency), + detune: Dom::from_ref(&detune), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &BiquadFilterOptions, + ) -> Fallible<DomRoot<BiquadFilterNode>> { + let node = BiquadFilterNode::new_inherited(window, context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &BiquadFilterOptions, + ) -> Fallible<DomRoot<BiquadFilterNode>> { + BiquadFilterNode::new(window, context, options) + } +} + +impl BiquadFilterNodeMethods for BiquadFilterNode { + // https://webaudio.github.io/web-audio-api/#dom-biquadfilternode-gain + fn Gain(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.gain) + } + + // https://webaudio.github.io/web-audio-api/#dom-biquadfilternode-q + fn Q(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.q) + } + + // https://webaudio.github.io/web-audio-api/#dom-biquadfilternode-detune + fn Detune(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.detune) + } + + // https://webaudio.github.io/web-audio-api/#dom-biquadfilternode-frequency + fn Frequency(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.frequency) + } + + // https://webaudio.github.io/web-audio-api/#dom-biquadfilternode-type + fn Type(&self) -> BiquadFilterType { + self.filter.get() + } + + // https://webaudio.github.io/web-audio-api/#dom-biquadfilternode-type + fn SetType(&self, filter: BiquadFilterType) { + self.filter.set(filter); + self.node.message(AudioNodeMessage::BiquadFilterNode( + BiquadFilterNodeMessage::SetFilterType(filter.into()), + )); + } +} + +impl<'a> From<&'a BiquadFilterOptions> for BiquadFilterNodeOptions { + fn from(options: &'a BiquadFilterOptions) -> Self { + Self { + gain: *options.gain, + q: *options.Q, + frequency: *options.frequency, + detune: *options.detune, + filter: options.type_.into(), + } + } +} + +impl From<BiquadFilterType> for FilterType { + fn from(filter: BiquadFilterType) -> FilterType { + match filter { + BiquadFilterType::Lowpass => FilterType::LowPass, + BiquadFilterType::Highpass => FilterType::HighPass, + BiquadFilterType::Bandpass => FilterType::BandPass, + BiquadFilterType::Lowshelf => FilterType::LowShelf, + BiquadFilterType::Highshelf => FilterType::HighShelf, + BiquadFilterType::Peaking => FilterType::Peaking, + BiquadFilterType::Allpass => FilterType::AllPass, + BiquadFilterType::Notch => FilterType::Notch, + } + } +} diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs index c076282dcc1..44011df9494 100644 --- a/components/script/dom/blob.rs +++ b/components/script/dom/blob.rs @@ -1,348 +1,203 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BlobBinding; -use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; -use dom::bindings::codegen::UnionTypes::BlobOrString; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::body::{run_array_buffer_data_algorithm, FetchedData}; +use crate::dom::bindings::codegen::Bindings::BlobBinding; +use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; +use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::serializable::{Serializable, StorageKey}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::structuredclone::StructuredDataHolder; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::readablestream::ReadableStream; +use crate::realms::{AlreadyInRealm, InRealm}; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use encoding::all::UTF_8; -use encoding::types::{EncoderTrap, Encoding}; -use ipc_channel::ipc; -use net_traits::{CoreResourceMsg, IpcSend}; -use net_traits::blob_url_store::{BlobBuf, get_blob_origin}; -use net_traits::filemanager_thread::{FileManagerThreadMsg, ReadFileProgress, RelativePos}; -use std::mem; -use std::ops::Index; -use std::path::PathBuf; +use encoding_rs::UTF_8; +use js::jsapi::JSObject; +use msg::constellation_msg::{BlobId, BlobIndex, PipelineNamespaceId}; +use net_traits::filemanager_thread::RelativePos; +use script_traits::serializable::BlobImpl; +use std::collections::HashMap; +use std::num::NonZeroU32; +use std::ptr::NonNull; +use std::rc::Rc; use uuid::Uuid; -/// File-based blob -#[derive(JSTraceable)] -pub struct FileBlob { - id: Uuid, - name: Option<PathBuf>, - cache: DOMRefCell<Option<Vec<u8>>>, - size: u64, -} - - -/// Different backends of Blob -#[must_root] -#[derive(JSTraceable)] -pub enum BlobImpl { - /// File-based blob, whose content lives in the net process - File(FileBlob), - /// Memory-based blob, whose content lives in the script process - Memory(Vec<u8>), - /// Sliced blob, including parent blob reference and - /// relative positions of current slicing range, - /// IMPORTANT: The depth of tree is only two, i.e. the parent Blob must be - /// either File-based or Memory-based - Sliced(JS<Blob>, RelativePos), -} - -impl BlobImpl { - /// Construct memory-backed BlobImpl - #[allow(unrooted_must_root)] - pub fn new_from_bytes(bytes: Vec<u8>) -> BlobImpl { - BlobImpl::Memory(bytes) - } - - /// Construct file-backed BlobImpl from File ID - pub fn new_from_file(file_id: Uuid, name: PathBuf, size: u64) -> BlobImpl { - BlobImpl::File(FileBlob { - id: file_id, - name: Some(name), - cache: DOMRefCell::new(None), - size: size, - }) - } -} - // https://w3c.github.io/FileAPI/#blob #[dom_struct] pub struct Blob { reflector_: Reflector, - #[ignore_heap_size_of = "No clear owner"] - blob_impl: DOMRefCell<BlobImpl>, - /// content-type string - type_string: String, + blob_id: BlobId, } impl Blob { - #[allow(unrooted_must_root)] - pub fn new( - global: &GlobalScope, blob_impl: BlobImpl, typeString: String) - -> Root<Blob> { - let boxed_blob = box Blob::new_inherited(blob_impl, typeString); - reflect_dom_object(boxed_blob, global, BlobBinding::Wrap) + pub fn new(global: &GlobalScope, blob_impl: BlobImpl) -> DomRoot<Blob> { + let dom_blob = reflect_dom_object(Box::new(Blob::new_inherited(&blob_impl)), global); + global.track_blob(&dom_blob, blob_impl); + dom_blob } #[allow(unrooted_must_root)] - pub fn new_inherited(blob_impl: BlobImpl, type_string: String) -> Blob { + pub fn new_inherited(blob_impl: &BlobImpl) -> Blob { Blob { reflector_: Reflector::new(), - blob_impl: DOMRefCell::new(blob_impl), - // NOTE: Guarding the format correctness here, - // https://w3c.github.io/FileAPI/#dfn-type - type_string: normalize_type_string(&type_string), + blob_id: blob_impl.blob_id(), } } - #[allow(unrooted_must_root)] - fn new_sliced(parent: &Blob, rel_pos: RelativePos, - relative_content_type: DOMString) -> Root<Blob> { - let blob_impl = match *parent.blob_impl.borrow() { - BlobImpl::File(_) => { - // Create new parent node - BlobImpl::Sliced(JS::from_ref(parent), rel_pos) - } - BlobImpl::Memory(_) => { - // Create new parent node - BlobImpl::Sliced(JS::from_ref(parent), rel_pos) - } - BlobImpl::Sliced(ref grandparent, ref old_rel_pos) => { - // Adjust the slicing position, using same parent - BlobImpl::Sliced(grandparent.clone(), old_rel_pos.slice_inner(&rel_pos)) - } - }; - - Blob::new(&parent.global(), blob_impl, relative_content_type.into()) - } - // https://w3c.github.io/FileAPI/#constructorBlob - pub fn Constructor(global: &GlobalScope, - blobParts: Option<Vec<BlobOrString>>, - blobPropertyBag: &BlobBinding::BlobPropertyBag) - -> Fallible<Root<Blob>> { - // TODO: accept other blobParts types - ArrayBuffer or ArrayBufferView + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + blobParts: Option<Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>>, + blobPropertyBag: &BlobBinding::BlobPropertyBag, + ) -> Fallible<DomRoot<Blob>> { let bytes: Vec<u8> = match blobParts { None => Vec::new(), Some(blobparts) => match blob_parts_to_bytes(blobparts) { Ok(bytes) => bytes, Err(_) => return Err(Error::InvalidCharacter), - } + }, }; - Ok(Blob::new(global, BlobImpl::new_from_bytes(bytes), blobPropertyBag.type_.to_string())) + let type_string = normalize_type_string(&blobPropertyBag.type_.to_string()); + let blob_impl = BlobImpl::new_from_bytes(bytes, type_string); + + Ok(Blob::new(global, blob_impl)) } /// Get a slice to inner data, this might incur synchronous read and caching pub fn get_bytes(&self) -> Result<Vec<u8>, ()> { - match *self.blob_impl.borrow() { - BlobImpl::File(ref f) => { - let (buffer, is_new_buffer) = match *f.cache.borrow() { - Some(ref bytes) => (bytes.clone(), false), - None => { - let bytes = read_file(&self.global(), f.id.clone())?; - (bytes, true) - } - }; - - // Cache - if is_new_buffer { - *f.cache.borrow_mut() = Some(buffer.clone()); - } - - Ok(buffer) - } - BlobImpl::Memory(ref s) => Ok(s.clone()), - BlobImpl::Sliced(ref parent, ref rel_pos) => { - parent.get_bytes().map(|v| { - let range = rel_pos.to_abs_range(v.len()); - v.index(range).to_vec() - }) - } - } + self.global().get_blob_bytes(&self.blob_id) } /// Get a copy of the type_string pub fn type_string(&self) -> String { - self.type_string.clone() + self.global().get_blob_type_string(&self.blob_id) } /// Get a FileID representing the Blob content, /// used by URL.createObjectURL pub fn get_blob_url_id(&self) -> Uuid { - let opt_sliced_parent = match *self.blob_impl.borrow() { - BlobImpl::Sliced(ref parent, ref rel_pos) => { - Some((parent.promote(/* set_valid is */ false), rel_pos.clone(), parent.Size())) - } - _ => None - }; + self.global().get_blob_url_id(&self.blob_id) + } - match opt_sliced_parent { - Some((parent_id, rel_pos, size)) => self.create_sliced_url_id(&parent_id, &rel_pos, size), - None => self.promote(/* set_valid is */ true), - } + /// <https://w3c.github.io/FileAPI/#blob-get-stream> + pub fn get_stream(&self) -> DomRoot<ReadableStream> { + self.global().get_blob_stream(&self.blob_id) } +} - /// Promote non-Slice blob: - /// 1. Memory-based: The bytes in data slice will be transferred to file manager thread. - /// 2. File-based: If set_valid, then activate the FileID so it can serve as URL - /// Depending on set_valid, the returned FileID can be part of - /// valid or invalid Blob URL. - fn promote(&self, set_valid: bool) -> Uuid { - let mut bytes = vec![]; - let global_url = self.global().get_url(); - - match *self.blob_impl.borrow_mut() { - BlobImpl::Sliced(_, _) => { - debug!("Sliced can't have a sliced parent"); - // Return dummy id - return Uuid::new_v4(); - } - BlobImpl::File(ref f) => { - if set_valid { - let origin = get_blob_origin(&global_url); - let (tx, rx) = ipc::channel().unwrap(); - - let msg = FileManagerThreadMsg::ActivateBlobURL(f.id.clone(), tx, origin.clone()); - self.send_to_file_manager(msg); - - match rx.recv().unwrap() { - Ok(_) => return f.id.clone(), - // Return a dummy id on error - Err(_) => return Uuid::new_v4(), - } - } else { - // no need to activate - return f.id.clone(); - } - } - BlobImpl::Memory(ref mut bytes_in) => mem::swap(bytes_in, &mut bytes), +impl Serializable for Blob { + /// <https://w3c.github.io/FileAPI/#ref-for-serialization-steps> + fn serialize(&self, sc_holder: &mut StructuredDataHolder) -> Result<StorageKey, ()> { + let blob_impls = match sc_holder { + StructuredDataHolder::Write { blobs, .. } => blobs, + _ => panic!("Unexpected variant of StructuredDataHolder"), }; - let origin = get_blob_origin(&global_url); + let blob_id = self.blob_id.clone(); - let blob_buf = BlobBuf { - filename: None, - type_string: self.type_string.clone(), - size: bytes.len() as u64, - bytes: bytes.to_vec(), - }; + // 1. Get a clone of the blob impl. + let blob_impl = self.global().serialize_blob(&blob_id); - let (tx, rx) = ipc::channel().unwrap(); - let msg = FileManagerThreadMsg::PromoteMemory(blob_buf, set_valid, tx, origin.clone()); - self.send_to_file_manager(msg); - - match rx.recv().unwrap() { - Ok(id) => { - *self.blob_impl.borrow_mut() = BlobImpl::File(FileBlob { - id: id.clone(), - name: None, - cache: DOMRefCell::new(Some(bytes.to_vec())), - size: bytes.len() as u64, - }); - id - } - // Dummy id - Err(_) => Uuid::new_v4(), - } - } + // We clone the data, but the clone gets its own Id. + let new_blob_id = blob_impl.blob_id(); - /// Get a FileID representing sliced parent-blob content - fn create_sliced_url_id(&self, parent_id: &Uuid, - rel_pos: &RelativePos, parent_len: u64) -> Uuid { - let origin = get_blob_origin(&self.global().get_url()); - - let (tx, rx) = ipc::channel().unwrap(); - let msg = FileManagerThreadMsg::AddSlicedURLEntry(parent_id.clone(), - rel_pos.clone(), - tx, origin.clone()); - self.send_to_file_manager(msg); - match rx.recv().expect("File manager thread is down") { - Ok(new_id) => { - *self.blob_impl.borrow_mut() = BlobImpl::File(FileBlob { - id: new_id.clone(), - name: None, - cache: DOMRefCell::new(None), - size: rel_pos.to_abs_range(parent_len as usize).len() as u64, - }); - - // Return the indirect id reference - new_id - } - Err(_) => { - // Return dummy id - Uuid::new_v4() - } - } - } + // 2. Store the object at a given key. + let blobs = blob_impls.get_or_insert_with(|| HashMap::new()); + blobs.insert(new_blob_id.clone(), blob_impl); - /// Cleanups at the time of destruction/closing - fn clean_up_file_resource(&self) { - if let BlobImpl::File(ref f) = *self.blob_impl.borrow() { - let origin = get_blob_origin(&self.global().get_url()); + let PipelineNamespaceId(name_space) = new_blob_id.namespace_id; + let BlobIndex(index) = new_blob_id.index; + let index = index.get(); - let (tx, rx) = ipc::channel().unwrap(); + let name_space = name_space.to_ne_bytes(); + let index = index.to_ne_bytes(); - let msg = FileManagerThreadMsg::DecRef(f.id.clone(), origin, tx); - self.send_to_file_manager(msg); - let _ = rx.recv().unwrap(); - } - } + let storage_key = StorageKey { + index: u32::from_ne_bytes(index), + name_space: u32::from_ne_bytes(name_space), + }; - fn send_to_file_manager(&self, msg: FileManagerThreadMsg) { - let global = self.global(); - let resource_threads = global.resource_threads(); - let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg)); + // 3. Return the storage key. + Ok(storage_key) } -} -impl Drop for Blob { - fn drop(&mut self) { - self.clean_up_file_resource(); - } -} + /// <https://w3c.github.io/FileAPI/#ref-for-deserialization-steps> + fn deserialize( + owner: &GlobalScope, + sc_holder: &mut StructuredDataHolder, + storage_key: StorageKey, + ) -> Result<(), ()> { + // 1. Re-build the key for the storage location + // of the serialized object. + let namespace_id = PipelineNamespaceId(storage_key.name_space.clone()); + let index = BlobIndex( + NonZeroU32::new(storage_key.index.clone()).expect("Deserialized blob index is zero"), + ); + + let id = BlobId { + namespace_id, + index, + }; -fn read_file(global: &GlobalScope, id: Uuid) -> Result<Vec<u8>, ()> { - let resource_threads = global.resource_threads(); - let (chan, recv) = ipc::channel().map_err(|_|())?; - let origin = get_blob_origin(&global.get_url()); - let check_url_validity = false; - let msg = FileManagerThreadMsg::ReadFile(chan, id, check_url_validity, origin); - let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg)); - - let mut bytes = vec![]; - - loop { - match recv.recv().unwrap() { - Ok(ReadFileProgress::Meta(mut blob_buf)) => { - bytes.append(&mut blob_buf.bytes); - } - Ok(ReadFileProgress::Partial(mut bytes_in)) => { - bytes.append(&mut bytes_in); - } - Ok(ReadFileProgress::EOF) => { - return Ok(bytes); - } - Err(_) => return Err(()), + let (blobs, blob_impls) = match sc_holder { + StructuredDataHolder::Read { + blobs, blob_impls, .. + } => (blobs, blob_impls), + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; + + // 2. Get the transferred object from its storage, using the key. + let blob_impls_map = blob_impls + .as_mut() + .expect("The SC holder does not have any blob impls"); + let blob_impl = blob_impls_map + .remove(&id) + .expect("No blob to be deserialized found."); + if blob_impls_map.is_empty() { + *blob_impls = None; } + + let deserialized_blob = Blob::new(&*owner, blob_impl); + + let blobs = blobs.get_or_insert_with(|| HashMap::new()); + blobs.insert(storage_key, deserialized_blob); + + Ok(()) } } /// Extract bytes from BlobParts, used by Blob and File constructor -/// https://w3c.github.io/FileAPI/#constructorBlob -pub fn blob_parts_to_bytes(blobparts: Vec<BlobOrString>) -> Result<Vec<u8>, ()> { +/// <https://w3c.github.io/FileAPI/#constructorBlob> +#[allow(unsafe_code)] +pub fn blob_parts_to_bytes( + mut blobparts: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>, +) -> Result<Vec<u8>, ()> { let mut ret = vec![]; - - for blobpart in &blobparts { + for blobpart in &mut blobparts { match blobpart { - &BlobOrString::String(ref s) => { - let mut bytes = UTF_8.encode(s, EncoderTrap::Replace).map_err(|_|())?; - ret.append(&mut bytes); + &mut ArrayBufferOrArrayBufferViewOrBlobOrString::String(ref s) => { + ret.extend(s.as_bytes()); + }, + &mut ArrayBufferOrArrayBufferViewOrBlobOrString::Blob(ref b) => { + let bytes = b.get_bytes().unwrap_or(vec![]); + ret.extend(bytes); + }, + &mut ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBuffer(ref mut a) => unsafe { + let bytes = a.as_slice(); + ret.extend(bytes); }, - &BlobOrString::Blob(ref b) => { - let mut bytes = b.get_bytes().unwrap_or(vec![]); - ret.append(&mut bytes); + &mut ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBufferView(ref mut a) => unsafe { + let bytes = a.as_slice(); + ret.extend(bytes); }, } } @@ -353,41 +208,98 @@ pub fn blob_parts_to_bytes(blobparts: Vec<BlobOrString>) -> Result<Vec<u8>, ()> impl BlobMethods for Blob { // https://w3c.github.io/FileAPI/#dfn-size fn Size(&self) -> u64 { - match *self.blob_impl.borrow() { - BlobImpl::File(ref f) => f.size, - BlobImpl::Memory(ref v) => v.len() as u64, - BlobImpl::Sliced(ref parent, ref rel_pos) => - rel_pos.to_abs_range(parent.Size() as usize).len() as u64, - } + self.global().get_blob_size(&self.blob_id) } // https://w3c.github.io/FileAPI/#dfn-type fn Type(&self) -> DOMString { - DOMString::from(self.type_string.clone()) + DOMString::from(self.type_string()) + } + + // <https://w3c.github.io/FileAPI/#blob-get-stream> + fn Stream(&self, _cx: JSContext) -> NonNull<JSObject> { + self.get_stream().get_js_stream() } // https://w3c.github.io/FileAPI/#slice-method-algo - fn Slice(&self, - start: Option<i64>, - end: Option<i64>, - content_type: Option<DOMString>) - -> Root<Blob> { + fn Slice( + &self, + start: Option<i64>, + end: Option<i64>, + content_type: Option<DOMString>, + ) -> DomRoot<Blob> { + let type_string = + normalize_type_string(&content_type.unwrap_or(DOMString::from("")).to_string()); let rel_pos = RelativePos::from_opts(start, end); - Blob::new_sliced(self, rel_pos, content_type.unwrap_or(DOMString::from(""))) + let blob_impl = BlobImpl::new_sliced(rel_pos, self.blob_id.clone(), type_string); + Blob::new(&*self.global(), blob_impl) + } + + // https://w3c.github.io/FileAPI/#text-method-algo + fn Text(&self) -> Rc<Promise> { + let global = self.global(); + let in_realm_proof = AlreadyInRealm::assert(&global); + let p = Promise::new_in_current_realm(&global, InRealm::Already(&in_realm_proof)); + let id = self.get_blob_url_id(); + global.read_file_async( + id, + p.clone(), + Box::new(|promise, bytes| match bytes { + Ok(b) => { + let (text, _, _) = UTF_8.decode(&b); + let text = DOMString::from(text); + promise.resolve_native(&text); + }, + Err(e) => { + promise.reject_error(e); + }, + }), + ); + p + } + + // https://w3c.github.io/FileAPI/#arraybuffer-method-algo + fn ArrayBuffer(&self) -> Rc<Promise> { + let global = self.global(); + let in_realm_proof = AlreadyInRealm::assert(&global); + let p = Promise::new_in_current_realm(&global, InRealm::Already(&in_realm_proof)); + + let id = self.get_blob_url_id(); + + global.read_file_async( + id, + p.clone(), + Box::new(|promise, bytes| { + match bytes { + Ok(b) => { + let cx = promise.global().get_cx(); + let result = run_array_buffer_data_algorithm(cx, b); + + match result { + Ok(FetchedData::ArrayBuffer(a)) => promise.resolve_native(&a), + Err(e) => promise.reject_error(e), + _ => panic!("Unexpected result from run_array_buffer_data_algorithm"), + } + }, + Err(e) => promise.reject_error(e), + }; + }), + ); + p } } /// Get the normalized, MIME-parsable type string -/// https://w3c.github.io/FileAPI/#dfn-type +/// <https://w3c.github.io/FileAPI/#dfn-type> /// XXX: We will relax the restriction here, /// since the spec has some problem over this part. /// see https://github.com/w3c/FileAPI/issues/43 -fn normalize_type_string(s: &str) -> String { +pub fn normalize_type_string(s: &str) -> String { if is_ascii_printable(s) { - let s_lower = s.to_lowercase(); + let s_lower = s.to_ascii_lowercase(); // match s_lower.parse() as Result<Mime, ()> { - // Ok(_) => s_lower, - // Err(_) => "".to_string() + // Ok(_) => s_lower, + // Err(_) => "".to_string() s_lower } else { "".to_string() diff --git a/components/script/dom/bluetooth.rs b/components/script/dom/bluetooth.rs index 3c3b88a27ac..2bdc636dc7a 100644 --- a/components/script/dom/bluetooth.rs +++ b/components/script/dom/bluetooth.rs @@ -1,78 +1,86 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use bluetooth_traits::{BluetoothError, BluetoothRequest, GATTType}; use bluetooth_traits::{BluetoothResponse, BluetoothResponseResult}; use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted}; use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSequence}; use bluetooth_traits::scanfilter::{RequestDeviceoptions, ServiceUUIDSequence}; -use core::clone::Clone; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BluetoothBinding::{self, BluetoothDataFilterInit, BluetoothLEScanFilterInit}; -use dom::bindings::codegen::Bindings::BluetoothBinding::{BluetoothMethods, RequestDeviceOptions}; -use dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::BluetoothPermissionDescriptor; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerBinding:: - BluetoothRemoteGATTServerMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionName, PermissionState}; -use dom::bindings::codegen::UnionTypes::StringOrUnsignedLong; -use dom::bindings::error::Error::{self, Network, Security, Type}; -use dom::bindings::error::Fallible; -use dom::bindings::js::{JS, Root}; -use dom::bindings::refcounted::{Trusted, TrustedPromise}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::bluetoothdevice::BluetoothDevice; -use dom::bluetoothpermissionresult::BluetoothPermissionResult; -use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID, UUID}; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::permissions::{get_descriptor_permission_state, PermissionAlgorithm}; -use dom::promise::Promise; +use crate::realms::{AlreadyInRealm, InRealm}; +use crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::BluetoothBinding::BluetoothDataFilterInit; +use crate::dom::bindings::codegen::Bindings::BluetoothBinding::{BluetoothMethods, RequestDeviceOptions}; +use crate::dom::bindings::codegen::Bindings::BluetoothBinding::BluetoothLEScanFilterInit; +use crate::dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::BluetoothPermissionDescriptor; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerBinding:: +BluetoothRemoteGATTServerMethods; +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionName, PermissionState}; +use crate::dom::bindings::codegen::UnionTypes::{ArrayBufferViewOrArrayBuffer, StringOrUnsignedLong}; +use crate::dom::bindings::error::Error::{self, Network, Security, Type}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::{DomObject, reflect_dom_object}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bluetoothdevice::BluetoothDevice; +use crate::dom::bluetoothpermissionresult::BluetoothPermissionResult; +use crate::dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID, UUID}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::permissions::{get_descriptor_permission_state, PermissionAlgorithm}; +use crate::dom::promise::Promise; +use crate::script_runtime::JSContext; +use crate::task::TaskOnce; use dom_struct::dom_struct; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use js::conversions::ConversionResult; -use js::jsapi::{JSAutoCompartment, JSContext, JSObject}; +use js::jsapi::JSObject; use js::jsval::{ObjectValue, UndefinedValue}; -use script_thread::Runnable; -use std::cell::Ref; +use profile_traits::ipc as ProfiledIpc; use std::collections::HashMap; use std::rc::Rc; use std::str::FromStr; use std::sync::{Arc, Mutex}; -const KEY_CONVERSION_ERROR: &'static str = "This `manufacturerData` key can not be parsed as unsigned short:"; -const FILTER_EMPTY_ERROR: &'static str = "'filters' member, if present, must be nonempty to find any devices."; +const KEY_CONVERSION_ERROR: &'static str = + "This `manufacturerData` key can not be parsed as unsigned short:"; +const FILTER_EMPTY_ERROR: &'static str = + "'filters' member, if present, must be nonempty to find any devices."; const FILTER_ERROR: &'static str = "A filter must restrict the devices in some way."; -const MANUFACTURER_DATA_ERROR: &'static str = "'manufacturerData', if present, must be non-empty to filter devices."; -const MASK_LENGTH_ERROR: &'static str = "`mask`, if present, must have the same length as `dataPrefix`."; +const MANUFACTURER_DATA_ERROR: &'static str = + "'manufacturerData', if present, must be non-empty to filter devices."; +const MASK_LENGTH_ERROR: &'static str = + "`mask`, if present, must have the same length as `dataPrefix`."; // 248 is the maximum number of UTF-8 code units in a Bluetooth Device Name. const MAX_DEVICE_NAME_LENGTH: usize = 248; const NAME_PREFIX_ERROR: &'static str = "'namePrefix', if present, must be nonempty."; const NAME_TOO_LONG_ERROR: &'static str = "A device name can't be longer than 248 bytes."; -const SERVICE_DATA_ERROR: &'static str = "'serviceData', if present, must be non-empty to filter devices."; +const SERVICE_DATA_ERROR: &'static str = + "'serviceData', if present, must be non-empty to filter devices."; const SERVICE_ERROR: &'static str = "'services', if present, must contain at least one service."; const OPTIONS_ERROR: &'static str = "Fields of 'options' conflict with each other. Either 'acceptAllDevices' member must be true, or 'filters' member must be set to a value."; -const BT_DESC_CONVERSION_ERROR: &'static str = "Can't convert to an IDL value of type BluetoothPermissionDescriptor"; +const BT_DESC_CONVERSION_ERROR: &'static str = + "Can't convert to an IDL value of type BluetoothPermissionDescriptor"; -#[derive(HeapSizeOf, JSTraceable)] +#[derive(JSTraceable, MallocSizeOf)] +#[allow(non_snake_case)] pub struct AllowedBluetoothDevice { pub deviceId: DOMString, pub mayUseGATT: bool, } -#[derive(HeapSizeOf, JSTraceable)] +#[derive(JSTraceable, MallocSizeOf)] pub struct BluetoothExtraPermissionData { - allowed_devices: DOMRefCell<Vec<AllowedBluetoothDevice>>, + allowed_devices: DomRefCell<Vec<AllowedBluetoothDevice>>, } impl BluetoothExtraPermissionData { pub fn new() -> BluetoothExtraPermissionData { BluetoothExtraPermissionData { - allowed_devices: DOMRefCell::new(Vec::new()), + allowed_devices: DomRefCell::new(Vec::new()), } } @@ -85,7 +93,10 @@ impl BluetoothExtraPermissionData { } pub fn allowed_devices_contains_id(&self, id: DOMString) -> bool { - self.allowed_devices.borrow().iter().any(|d| d.deviceId == id) + self.allowed_devices + .borrow() + .iter() + .any(|d| d.deviceId == id) } } @@ -95,23 +106,24 @@ struct BluetoothContext<T: AsyncBluetoothListener + DomObject> { } pub trait AsyncBluetoothListener { - fn handle_response(&self, result: BluetoothResponse, cx: *mut JSContext, promise: &Rc<Promise>); + fn handle_response(&self, result: BluetoothResponse, promise: &Rc<Promise>); } -impl<T: AsyncBluetoothListener + DomObject> BluetoothContext<T> { +impl<T> BluetoothContext<T> +where + T: AsyncBluetoothListener + DomObject, +{ #[allow(unrooted_must_root)] fn response(&mut self, response: BluetoothResponseResult) { let promise = self.promise.take().expect("bt promise is missing").root(); - let promise_cx = promise.global().get_cx(); - // JSAutoCompartment needs to be manually made. + // JSAutoRealm needs to be manually made. // Otherwise, Servo will crash. - let _ac = JSAutoCompartment::new(promise_cx, promise.reflector().get_jsobject().get()); match response { - Ok(response) => self.receiver.root().handle_response(response, promise_cx, &promise), + Ok(response) => self.receiver.root().handle_response(response, &promise), // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice // Step 3 - 4. - Err(error) => promise.reject_error(promise_cx, Error::from(error)), + Err(error) => promise.reject_error(Error::from(error)), } } } @@ -120,46 +132,46 @@ impl<T: AsyncBluetoothListener + DomObject> BluetoothContext<T> { #[dom_struct] pub struct Bluetooth { eventtarget: EventTarget, - device_instance_map: DOMRefCell<HashMap<String, JS<BluetoothDevice>>>, + device_instance_map: DomRefCell<HashMap<String, Dom<BluetoothDevice>>>, } impl Bluetooth { pub fn new_inherited() -> Bluetooth { Bluetooth { eventtarget: EventTarget::new_inherited(), - device_instance_map: DOMRefCell::new(HashMap::new()), + device_instance_map: DomRefCell::new(HashMap::new()), } } - pub fn new(global: &GlobalScope) -> Root<Bluetooth> { - reflect_dom_object(box Bluetooth::new_inherited(), - global, - BluetoothBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<Bluetooth> { + reflect_dom_object(Box::new(Bluetooth::new_inherited()), global) } fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> { self.global().as_window().bluetooth_thread() } - pub fn get_device_map(&self) -> &DOMRefCell<HashMap<String, JS<BluetoothDevice>>> { + pub fn get_device_map(&self) -> &DomRefCell<HashMap<String, Dom<BluetoothDevice>>> { &self.device_instance_map } // https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices - fn request_bluetooth_devices(&self, - p: &Rc<Promise>, - filters: &Option<Vec<BluetoothLEScanFilterInit>>, - optional_services: &Option<Vec<BluetoothServiceUUID>>, - sender: IpcSender<BluetoothResponseResult>) { + fn request_bluetooth_devices( + &self, + p: &Rc<Promise>, + filters: &Option<Vec<BluetoothLEScanFilterInit>>, + optional_services: &[BluetoothServiceUUID], + sender: IpcSender<BluetoothResponseResult>, + ) { // TODO: Step 1: Triggered by user activation. // Step 2.2: There are no requiredServiceUUIDS, we scan for all devices. - let mut uuid_filters = vec!(); + let mut uuid_filters = vec![]; if let &Some(ref filters) = filters { // Step 2.1. - if filters.is_empty() { - p.reject_error(p.global().get_cx(), Type(FILTER_EMPTY_ERROR.to_owned())); + if filters.is_empty() { + p.reject_error(Type(FILTER_EMPTY_ERROR.to_owned())); return; } @@ -172,7 +184,7 @@ impl Bluetooth { // Step 2.4.2. Ok(f) => uuid_filters.push(f), Err(e) => { - p.reject_error(p.global().get_cx(), e); + p.reject_error(e); return; }, } @@ -180,105 +192,116 @@ impl Bluetooth { } } - let mut optional_services_uuids = vec!(); - if let &Some(ref opt_services) = optional_services { - for opt_service in opt_services { - // Step 2.5 - 2.6. - let uuid = match BluetoothUUID::service(opt_service.clone()) { - Ok(u) => u.to_string(), - Err(e) => { - p.reject_error(p.global().get_cx(), e); - return; - }, - }; - - // Step 2.7. - // Note: What we are doing here, is adding the not blocklisted UUIDs to the result vector, - // instead of removing them from an already filled vector. - if !uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) { - optional_services_uuids.push(uuid); - } + let mut optional_services_uuids = vec![]; + for opt_service in optional_services { + // Step 2.5 - 2.6. + let uuid = match BluetoothUUID::service(opt_service.clone()) { + Ok(u) => u.to_string(), + Err(e) => { + p.reject_error(e); + return; + }, + }; + + // Step 2.7. + // Note: What we are doing here, is adding the not blocklisted UUIDs to the result vector, + // instead of removing them from an already filled vector. + if !uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) { + optional_services_uuids.push(uuid); } } - let option = RequestDeviceoptions::new(BluetoothScanfilterSequence::new(uuid_filters), - ServiceUUIDSequence::new(optional_services_uuids)); + let option = RequestDeviceoptions::new( + BluetoothScanfilterSequence::new(uuid_filters), + ServiceUUIDSequence::new(optional_services_uuids), + ); // Step 4 - 5. - if let PermissionState::Denied = get_descriptor_permission_state(PermissionName::Bluetooth, None) { - return p.reject_error(p.global().get_cx(), Error::NotFound); + if let PermissionState::Denied = + get_descriptor_permission_state(PermissionName::Bluetooth, None) + { + return p.reject_error(Error::NotFound); } // Note: Step 3, 6 - 8 are implemented in // components/net/bluetooth_thread.rs in request_device function. - self.get_bluetooth_thread().send(BluetoothRequest::RequestDevice(option, sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::RequestDevice(option, sender)) + .unwrap(); } } pub fn response_async<T: AsyncBluetoothListener + DomObject + 'static>( - promise: &Rc<Promise>, - receiver: &T) -> IpcSender<BluetoothResponseResult> { + promise: &Rc<Promise>, + receiver: &T, +) -> IpcSender<BluetoothResponseResult> { let (action_sender, action_receiver) = ipc::channel().unwrap(); let task_source = receiver.global().networking_task_source(); let context = Arc::new(Mutex::new(BluetoothContext { promise: Some(TrustedPromise::new(promise.clone())), receiver: Trusted::new(receiver), })); - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - struct ListenerRunnable<T: AsyncBluetoothListener + DomObject> { - context: Arc<Mutex<BluetoothContext<T>>>, - action: BluetoothResponseResult, - } + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + struct ListenerTask<T: AsyncBluetoothListener + DomObject> { + context: Arc<Mutex<BluetoothContext<T>>>, + action: BluetoothResponseResult, + } - impl<T: AsyncBluetoothListener + DomObject> Runnable for ListenerRunnable<T> { - fn handler(self: Box<Self>) { - let this = *self; - let mut context = this.context.lock().unwrap(); - context.response(this.action); + impl<T> TaskOnce for ListenerTask<T> + where + T: AsyncBluetoothListener + DomObject, + { + fn run_once(self) { + let mut context = self.context.lock().unwrap(); + context.response(self.action); + } } - } - let runnable = box ListenerRunnable { - context: context.clone(), - action: message.to().unwrap(), - }; + let task = ListenerTask { + context: context.clone(), + action: message.to().unwrap(), + }; - let result = task_source.queue_wrapperless(runnable); - if let Err(err) = result { - warn!("failed to deliver network data: {:?}", err); - } - }); + let result = task_source.queue_unconditionally(task); + if let Err(err) = result { + warn!("failed to deliver network data: {:?}", err); + } + }), + ); action_sender } -#[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren -pub fn get_gatt_children<T, F> ( - attribute: &T, - single: bool, - uuid_canonicalizer: F, - uuid: Option<StringOrUnsignedLong>, - instance_id: String, - connected: bool, - child_type: GATTType) - -> Rc<Promise> - where T: AsyncBluetoothListener + DomObject + 'static, - F: FnOnce(StringOrUnsignedLong) -> Fallible<UUID> { - let p = Promise::new(&attribute.global()); - let p_cx = p.global().get_cx(); +pub fn get_gatt_children<T, F>( + attribute: &T, + single: bool, + uuid_canonicalizer: F, + uuid: Option<StringOrUnsignedLong>, + instance_id: String, + connected: bool, + child_type: GATTType, +) -> Rc<Promise> +where + T: AsyncBluetoothListener + DomObject + 'static, + F: FnOnce(StringOrUnsignedLong) -> Fallible<UUID>, +{ + let in_realm_proof = AlreadyInRealm::assert(&attribute.global()); + let p = Promise::new_in_current_realm(&attribute.global(), InRealm::Already(&in_realm_proof)); let result_uuid = if let Some(u) = uuid { // Step 1. let canonicalized = match uuid_canonicalizer(u) { Ok(canonicalized_uuid) => canonicalized_uuid.to_string(), Err(e) => { - p.reject_error(p_cx, e); + p.reject_error(e); return p; - } + }, }; // Step 2. if uuid_is_blocklisted(canonicalized.as_ref(), Blocklist::All) { - p.reject_error(p_cx, Security); + p.reject_error(Security); return p; } Some(canonicalized) @@ -288,7 +311,7 @@ pub fn get_gatt_children<T, F> ( // Step 3 - 4. if !connected { - p.reject_error(p_cx, Network); + p.reject_error(Network); return p; } @@ -297,8 +320,18 @@ pub fn get_gatt_children<T, F> ( // Note: Steps 6 - 7 are implemented in components/bluetooth/lib.rs in get_descriptor function // and in handle_response function. let sender = response_async(&p, attribute); - attribute.global().as_window().bluetooth_thread().send( - BluetoothRequest::GetGATTChildren(instance_id, result_uuid, single, child_type, sender)).unwrap(); + attribute + .global() + .as_window() + .bluetooth_thread() + .send(BluetoothRequest::GetGATTChildren( + instance_id, + result_uuid, + single, + child_type, + sender, + )) + .unwrap(); return p; } @@ -306,11 +339,12 @@ pub fn get_gatt_children<T, F> ( fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<BluetoothScanfilter> { // Step 1. if filter.services.is_none() && - filter.name.is_none() && - filter.namePrefix.is_none() && - filter.manufacturerData.is_none() && - filter.serviceData.is_none() { - return Err(Type(FILTER_ERROR.to_owned())); + filter.name.is_none() && + filter.namePrefix.is_none() && + filter.manufacturerData.is_none() && + filter.serviceData.is_none() + { + return Err(Type(FILTER_ERROR.to_owned())); } // Step 2: There is no empty canonicalizedFilter member, @@ -324,15 +358,15 @@ fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<Bluetooth return Err(Type(SERVICE_ERROR.to_owned())); } - let mut services_vec = vec!(); + let mut services_vec = vec![]; for service in services { // Step 3.2 - 3.3. - let uuid = try!(BluetoothUUID::service(service.clone())).to_string(); + let uuid = BluetoothUUID::service(service.clone())?.to_string(); // Step 3.4. if uuid_is_blocklisted(uuid.as_ref(), Blocklist::All) { - return Err(Security) + return Err(Security); } services_vec.push(uuid); @@ -340,7 +374,7 @@ fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<Bluetooth // Step 3.5. services_vec }, - None => vec!(), + None => vec![], }; // Step 4. @@ -387,13 +421,18 @@ fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<Bluetooth // Step 7.1 - 7.2. let manufacturer_id = match u16::from_str(key.as_ref()) { Ok(id) => id, - Err(err) => return Err(Type(format!("{} {} {}", KEY_CONVERSION_ERROR, key, err))), + Err(err) => { + return Err(Type(format!("{} {} {}", KEY_CONVERSION_ERROR, key, err))); + }, }; // Step 7.3: No need to convert to IDL values since this is only used by native code. // Step 7.4 - 7.5. - map.insert(manufacturer_id, try!(canonicalize_bluetooth_data_filter_init(bdfi))); + map.insert( + manufacturer_id, + canonicalize_bluetooth_data_filter_init(bdfi)?, + ); } Some(map) }, @@ -413,11 +452,11 @@ fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<Bluetooth // Step 9.1. Ok(number) => StringOrUnsignedLong::UnsignedLong(number), // Step 9.2. - _ => StringOrUnsignedLong::String(key.clone()) + _ => StringOrUnsignedLong::String(key.clone()), }; // Step 9.3 - 9.4. - let service = try!(BluetoothUUID::service(service_name)).to_string(); + let service = BluetoothUUID::service(service_name)?.to_string(); // Step 9.5. if uuid_is_blocklisted(service.as_ref(), Blocklist::All) { @@ -427,7 +466,7 @@ fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<Bluetooth // Step 9.6: No need to convert to IDL values since this is only used by native code. // Step 9.7 - 9.8. - map.insert(service, try!(canonicalize_bluetooth_data_filter_init(bdfi))); + map.insert(service, canonicalize_bluetooth_data_filter_init(bdfi)?); } Some(map) }, @@ -435,18 +474,34 @@ fn canonicalize_filter(filter: &BluetoothLEScanFilterInit) -> Fallible<Bluetooth }; // Step 10. - Ok(BluetoothScanfilter::new(name, name_prefix, services_vec, manufacturer_data, service_data)) + Ok(BluetoothScanfilter::new( + name, + name_prefix, + services_vec, + manufacturer_data, + service_data, + )) } // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdatafilterinit-canonicalizing -fn canonicalize_bluetooth_data_filter_init(bdfi: &BluetoothDataFilterInit) -> Fallible<(Vec<u8>, Vec<u8>)> { +fn canonicalize_bluetooth_data_filter_init( + bdfi: &BluetoothDataFilterInit, +) -> Fallible<(Vec<u8>, Vec<u8>)> { // Step 1. - let data_prefix = bdfi.dataPrefix.clone().unwrap_or(vec![]); + let data_prefix = match bdfi.dataPrefix { + Some(ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref avb)) => avb.to_vec(), + Some(ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref ab)) => ab.to_vec(), + None => vec![], + }; // Step 2. // If no mask present, mask will be a sequence of 0xFF bytes the same length as dataPrefix. // Masking dataPrefix with this, leaves dataPrefix untouched. - let mask = bdfi.mask.clone().unwrap_or(vec![0xFF; data_prefix.len()]); + let mask = match bdfi.mask { + Some(ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref avb)) => avb.to_vec(), + Some(ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref ab)) => ab.to_vec(), + None => vec![0xFF; data_prefix.len()], + }; // Step 3. if mask.len() != data_prefix.len() { @@ -471,14 +526,14 @@ impl From<BluetoothError> for Error { } impl BluetoothMethods for Bluetooth { - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice - fn RequestDevice(&self, option: &RequestDeviceOptions) -> Rc<Promise> { - let p = Promise::new(&self.global()); + fn RequestDevice(&self, option: &RequestDeviceOptions, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); // Step 1. if (option.filters.is_some() && option.acceptAllDevices) || - (option.filters.is_none() && !option.acceptAllDevices) { - p.reject_error(p.global().get_cx(), Error::Type(OPTIONS_ERROR.to_owned())); + (option.filters.is_none() && !option.acceptAllDevices) + { + p.reject_error(Error::Type(OPTIONS_ERROR.to_owned())); return p; } @@ -489,54 +544,61 @@ impl BluetoothMethods for Bluetooth { return p; } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-getavailability - fn GetAvailability(&self) -> Rc<Promise> { - let p = Promise::new(&self.global()); + fn GetAvailability(&self, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); // Step 1. We did not override the method // Step 2 - 3. in handle_response let sender = response_async(&p, self); - self.get_bluetooth_thread().send( - BluetoothRequest::GetAvailability(sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::GetAvailability(sender)) + .unwrap(); p } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-onavailabilitychanged - event_handler!(availabilitychanged, GetOnavailabilitychanged, SetOnavailabilitychanged); + event_handler!( + availabilitychanged, + GetOnavailabilitychanged, + SetOnavailabilitychanged + ); } impl AsyncBluetoothListener for Bluetooth { - fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) { + fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>) { match response { // https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices // Step 11, 13 - 14. BluetoothResponse::RequestDevice(device) => { let mut device_instance_map = self.device_instance_map.borrow_mut(); if let Some(existing_device) = device_instance_map.get(&device.id.clone()) { - return promise.resolve_native(promise_cx, &**existing_device); + return promise.resolve_native(&**existing_device); } - let bt_device = BluetoothDevice::new(&self.global(), - DOMString::from(device.id.clone()), - device.name.map(DOMString::from), - &self); - device_instance_map.insert(device.id.clone(), JS::from_ref(&bt_device)); - - self.global().as_window().bluetooth_extra_permission_data().add_new_allowed_device( - AllowedBluetoothDevice { + let bt_device = BluetoothDevice::new( + &self.global(), + DOMString::from(device.id.clone()), + device.name.map(DOMString::from), + &self, + ); + device_instance_map.insert(device.id.clone(), Dom::from_ref(&bt_device)); + + self.global() + .as_window() + .bluetooth_extra_permission_data() + .add_new_allowed_device(AllowedBluetoothDevice { deviceId: DOMString::from(device.id), mayUseGATT: true, - } - ); + }); // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-requestdevice // Step 5. - promise.resolve_native(promise_cx, &bt_device); + promise.resolve_native(&bt_device); }, // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetooth-getavailability // Step 2 - 3. BluetoothResponse::GetAvailability(is_available) => { - promise.resolve_native(promise_cx, &is_available); - } - _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())), + promise.resolve_native(&is_available); + }, + _ => promise.reject_error(Error::Type("Something went wrong...".to_owned())), } } } @@ -545,25 +607,28 @@ impl PermissionAlgorithm for Bluetooth { type Descriptor = BluetoothPermissionDescriptor; type Status = BluetoothPermissionResult; - #[allow(unsafe_code)] - fn create_descriptor(cx: *mut JSContext, - permission_descriptor_obj: *mut JSObject) - -> Result<BluetoothPermissionDescriptor, Error> { - rooted!(in(cx) let mut property = UndefinedValue()); - property.handle_mut().set(ObjectValue(permission_descriptor_obj)); - unsafe { - match BluetoothPermissionDescriptor::new(cx, property.handle()) { - Ok(ConversionResult::Success(descriptor)) => Ok(descriptor), - Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())), - Err(_) => Err(Error::Type(String::from(BT_DESC_CONVERSION_ERROR))), - } + fn create_descriptor( + cx: JSContext, + permission_descriptor_obj: *mut JSObject, + ) -> Result<BluetoothPermissionDescriptor, Error> { + rooted!(in(*cx) let mut property = UndefinedValue()); + property + .handle_mut() + .set(ObjectValue(permission_descriptor_obj)); + match BluetoothPermissionDescriptor::new(cx, property.handle()) { + Ok(ConversionResult::Success(descriptor)) => Ok(descriptor), + Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())), + Err(_) => Err(Error::Type(String::from(BT_DESC_CONVERSION_ERROR))), } } // https://webbluetoothcg.github.io/web-bluetooth/#query-the-bluetooth-permission - fn permission_query(cx: *mut JSContext, promise: &Rc<Promise>, - descriptor: &BluetoothPermissionDescriptor, - status: &BluetoothPermissionResult) { + fn permission_query( + _cx: JSContext, + promise: &Rc<Promise>, + descriptor: &BluetoothPermissionDescriptor, + status: &BluetoothPermissionResult, + ) { // Step 1: We are not using the `global` variable. // Step 2. @@ -572,7 +637,7 @@ impl PermissionAlgorithm for Bluetooth { // Step 3. if let PermissionState::Denied = status.get_state() { status.set_devices(Vec::new()); - return promise.resolve_native(cx, status); + return promise.resolve_native(status); } // Step 4. @@ -580,7 +645,10 @@ impl PermissionAlgorithm for Bluetooth { // Step 5. let global = status.global(); - let allowed_devices = global.as_window().bluetooth_extra_permission_data().get_allowed_devices(); + let allowed_devices = global + .as_window() + .bluetooth_extra_permission_data() + .get_allowed_devices(); let bluetooth = status.get_bluetooth(); let device_map = bluetooth.get_device_map().borrow(); @@ -603,23 +671,28 @@ impl PermissionAlgorithm for Bluetooth { for filter in filters { match canonicalize_filter(&filter) { Ok(f) => scan_filters.push(f), - Err(error) => return promise.reject_error(cx, error), + Err(error) => return promise.reject_error(error), } } // Step 6.2.2. // Instead of creating an internal slot we send an ipc message to the Bluetooth thread // to check if one of the filters matches. - let (sender, receiver) = ipc::channel().unwrap(); - status.get_bluetooth_thread() - .send(BluetoothRequest::MatchesFilter(device_id.clone(), - BluetoothScanfilterSequence::new(scan_filters), - sender)).unwrap(); + let (sender, receiver) = + ProfiledIpc::channel(global.time_profiler_chan().clone()).unwrap(); + status + .get_bluetooth_thread() + .send(BluetoothRequest::MatchesFilter( + device_id.clone(), + BluetoothScanfilterSequence::new(scan_filters), + sender, + )) + .unwrap(); match receiver.recv().unwrap() { Ok(true) => (), Ok(false) => continue, - Err(error) => return promise.reject_error(cx, Error::from(error)), + Err(error) => return promise.reject_error(Error::from(error)), }; } @@ -627,7 +700,7 @@ impl PermissionAlgorithm for Bluetooth { // TODO: Implement this correctly, not just using device ids here. // https://webbluetoothcg.github.io/web-bluetooth/#get-the-bluetoothdevice-representing if let Some(device) = device_map.get(&device_id) { - matching_devices.push(JS::from_ref(&**device)); + matching_devices.push(Dom::from_ref(&**device)); } } @@ -636,32 +709,46 @@ impl PermissionAlgorithm for Bluetooth { // https://w3c.github.io/permissions/#dom-permissions-query // Step 7. - promise.resolve_native(cx, status); + promise.resolve_native(status); } // https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission - fn permission_request(cx: *mut JSContext, promise: &Rc<Promise>, - descriptor: &BluetoothPermissionDescriptor, - status: &BluetoothPermissionResult) { + fn permission_request( + _cx: JSContext, + promise: &Rc<Promise>, + descriptor: &BluetoothPermissionDescriptor, + status: &BluetoothPermissionResult, + ) { // Step 1. if descriptor.filters.is_some() == descriptor.acceptAllDevices { - return promise.reject_error(cx, Error::Type(OPTIONS_ERROR.to_owned())); + return promise.reject_error(Error::Type(OPTIONS_ERROR.to_owned())); } // Step 2. let sender = response_async(promise, status); let bluetooth = status.get_bluetooth(); - bluetooth.request_bluetooth_devices(promise, &descriptor.filters, &descriptor.optionalServices, sender); + bluetooth.request_bluetooth_devices( + promise, + &descriptor.filters, + &descriptor.optionalServices, + sender, + ); // NOTE: Step 3. is in BluetoothPermissionResult's `handle_response` function. } #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#revoke-bluetooth-access - fn permission_revoke(_descriptor: &BluetoothPermissionDescriptor, status: &BluetoothPermissionResult) { + fn permission_revoke( + _descriptor: &BluetoothPermissionDescriptor, + status: &BluetoothPermissionResult, + ) { // Step 1. let global = status.global(); - let allowed_devices = global.as_window().bluetooth_extra_permission_data().get_allowed_devices(); + let allowed_devices = global + .as_window() + .bluetooth_extra_permission_data() + .get_allowed_devices(); // Step 2. let bluetooth = status.get_bluetooth(); let device_map = bluetooth.get_device_map().borrow(); @@ -669,7 +756,8 @@ impl PermissionAlgorithm for Bluetooth { let id = DOMString::from(id.clone()); // Step 2.1. if allowed_devices.iter().any(|d| d.deviceId == id) && - !device.is_represented_device_null() { + !device.is_represented_device_null() + { // Note: We don't need to update the allowed_services, // because we store it in the lower level // where it is already up-to-date diff --git a/components/script/dom/bluetoothadvertisingevent.rs b/components/script/dom/bluetoothadvertisingevent.rs index 604a1c82025..7d31fad2dc7 100644 --- a/components/script/dom/bluetoothadvertisingevent.rs +++ b/components/script/dom/bluetoothadvertisingevent.rs @@ -1,19 +1,19 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::BluetoothAdvertisingEventBinding::{self, BluetoothAdvertisingEventInit}; -use dom::bindings::codegen::Bindings::BluetoothAdvertisingEventBinding::BluetoothAdvertisingEventMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root, RootedReference}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::bluetoothdevice::BluetoothDevice; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::globalscope::GlobalScope; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::BluetoothAdvertisingEventBinding::BluetoothAdvertisingEventInit; +use crate::dom::bindings::codegen::Bindings::BluetoothAdvertisingEventBinding::BluetoothAdvertisingEventMethods; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bluetoothdevice::BluetoothDevice; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; @@ -21,23 +21,25 @@ use servo_atoms::Atom; #[dom_struct] pub struct BluetoothAdvertisingEvent { event: Event, - device: JS<BluetoothDevice>, + device: Dom<BluetoothDevice>, name: Option<DOMString>, appearance: Option<u16>, tx_power: Option<i8>, rssi: Option<i8>, } +#[allow(non_snake_case)] impl BluetoothAdvertisingEvent { - pub fn new_inherited(device: &BluetoothDevice, - name: Option<DOMString>, - appearance: Option<u16>, - tx_power: Option<i8>, - rssi: Option<i8>) - -> BluetoothAdvertisingEvent { + pub fn new_inherited( + device: &BluetoothDevice, + name: Option<DOMString>, + appearance: Option<u16>, + tx_power: Option<i8>, + rssi: Option<i8>, + ) -> BluetoothAdvertisingEvent { BluetoothAdvertisingEvent { event: Event::new_inherited(), - device: JS::from_ref(device), + device: Dom::from_ref(device), name: name, appearance: appearance, tx_power: tx_power, @@ -45,23 +47,23 @@ impl BluetoothAdvertisingEvent { } } - pub fn new(global: &GlobalScope, - type_: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable, - device: &BluetoothDevice, - name: Option<DOMString>, - appearance: Option<u16>, - txPower: Option<i8>, - rssi: Option<i8>) - -> Root<BluetoothAdvertisingEvent> { - let ev = reflect_dom_object(box BluetoothAdvertisingEvent::new_inherited(device, - name, - appearance, - txPower, - rssi), - global, - BluetoothAdvertisingEventBinding::Wrap); + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + device: &BluetoothDevice, + name: Option<DOMString>, + appearance: Option<u16>, + txPower: Option<i8>, + rssi: Option<i8>, + ) -> DomRoot<BluetoothAdvertisingEvent> { + let ev = reflect_dom_object( + Box::new(BluetoothAdvertisingEvent::new_inherited( + device, name, appearance, txPower, rssi, + )), + global, + ); { let event = ev.upcast::<Event>(); event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); @@ -70,34 +72,36 @@ impl BluetoothAdvertisingEvent { } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-bluetoothadvertisingevent - pub fn Constructor(window: &Window, - type_: DOMString, - init: &BluetoothAdvertisingEventInit) - -> Fallible<Root<BluetoothAdvertisingEvent>> { + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &BluetoothAdvertisingEventInit, + ) -> Fallible<DomRoot<BluetoothAdvertisingEvent>> { let global = window.upcast::<GlobalScope>(); - let device = init.device.r(); let name = init.name.clone(); let appearance = init.appearance.clone(); let txPower = init.txPower.clone(); let rssi = init.rssi.clone(); let bubbles = EventBubbles::from(init.parent.bubbles); let cancelable = EventCancelable::from(init.parent.cancelable); - Ok(BluetoothAdvertisingEvent::new(global, - Atom::from(type_), - bubbles, - cancelable, - device, - name, - appearance, - txPower, - rssi)) + Ok(BluetoothAdvertisingEvent::new( + global, + Atom::from(type_), + bubbles, + cancelable, + &init.device, + name, + appearance, + txPower, + rssi, + )) } } impl BluetoothAdvertisingEventMethods for BluetoothAdvertisingEvent { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-device - fn Device(&self) -> Root<BluetoothDevice> { - Root::from_ref(&*self.device) + fn Device(&self) -> DomRoot<BluetoothDevice> { + DomRoot::from_ref(&*self.device) } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothadvertisingevent-name diff --git a/components/script/dom/bluetoothcharacteristicproperties.rs b/components/script/dom/bluetoothcharacteristicproperties.rs index a477c130a2d..f48eb52772c 100644 --- a/components/script/dom/bluetoothcharacteristicproperties.rs +++ b/components/script/dom/bluetoothcharacteristicproperties.rs @@ -1,17 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding; -use dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding:: - BluetoothCharacteristicPropertiesMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::BluetoothCharacteristicPropertiesMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; // https://webbluetoothcg.github.io/web-bluetooth/#characteristicproperties - #[dom_struct] +#[dom_struct] pub struct BluetoothCharacteristicProperties { reflector_: Reflector, broadcast: bool, @@ -25,17 +23,19 @@ pub struct BluetoothCharacteristicProperties { writable_auxiliaries: bool, } +#[allow(non_snake_case)] impl BluetoothCharacteristicProperties { - pub fn new_inherited(broadcast: bool, - read: bool, - write_without_response: bool, - write: bool, - notify: bool, - indicate: bool, - authenticated_signed_writes: bool, - reliable_write: bool, - writable_auxiliaries: bool) - -> BluetoothCharacteristicProperties { + pub fn new_inherited( + broadcast: bool, + read: bool, + write_without_response: bool, + write: bool, + notify: bool, + indicate: bool, + authenticated_signed_writes: bool, + reliable_write: bool, + writable_auxiliaries: bool, + ) -> BluetoothCharacteristicProperties { BluetoothCharacteristicProperties { reflector_: Reflector::new(), broadcast: broadcast, @@ -50,30 +50,34 @@ impl BluetoothCharacteristicProperties { } } - pub fn new(global: &GlobalScope, - broadcast: bool, - read: bool, - writeWithoutResponse: bool, - write: bool, - notify: bool, - indicate: bool, - authenticatedSignedWrites: bool, - reliableWrite: bool, - writableAuxiliaries: bool) - -> Root<BluetoothCharacteristicProperties> { - reflect_dom_object(box BluetoothCharacteristicProperties::new_inherited(broadcast, - read, - writeWithoutResponse, - write, - notify, - indicate, - authenticatedSignedWrites, - reliableWrite, - writableAuxiliaries), - global, - BluetoothCharacteristicPropertiesBinding::Wrap) - } + pub fn new( + global: &GlobalScope, + broadcast: bool, + read: bool, + writeWithoutResponse: bool, + write: bool, + notify: bool, + indicate: bool, + authenticatedSignedWrites: bool, + reliableWrite: bool, + writableAuxiliaries: bool, + ) -> DomRoot<BluetoothCharacteristicProperties> { + reflect_dom_object( + Box::new(BluetoothCharacteristicProperties::new_inherited( + broadcast, + read, + writeWithoutResponse, + write, + notify, + indicate, + authenticatedSignedWrites, + reliableWrite, + writableAuxiliaries, + )), + global, + ) } +} impl BluetoothCharacteristicPropertiesMethods for BluetoothCharacteristicProperties { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothcharacteristicproperties-broadcast diff --git a/components/script/dom/bluetoothdevice.rs b/components/script/dom/bluetoothdevice.rs index d3e742678bc..bff2e0871ff 100644 --- a/components/script/dom/bluetoothdevice.rs +++ b/components/script/dom/bluetoothdevice.rs @@ -1,32 +1,31 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bluetooth::{response_async, AsyncBluetoothListener, Bluetooth}; +use crate::dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties; +use crate::dom::bluetoothremotegattcharacteristic::BluetoothRemoteGATTCharacteristic; +use crate::dom::bluetoothremotegattdescriptor::BluetoothRemoteGATTDescriptor; +use crate::dom::bluetoothremotegattserver::BluetoothRemoteGATTServer; +use crate::dom::bluetoothremotegattservice::BluetoothRemoteGATTService; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::realms::InRealm; use bluetooth_traits::{BluetoothCharacteristicMsg, BluetoothDescriptorMsg}; use bluetooth_traits::{BluetoothRequest, BluetoothResponse, BluetoothServiceMsg}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BluetoothDeviceBinding; -use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::error::Error; -use dom::bindings::error::ErrorResult; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::bluetooth::{AsyncBluetoothListener, Bluetooth, response_async}; -use dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties; -use dom::bluetoothremotegattcharacteristic::BluetoothRemoteGATTCharacteristic; -use dom::bluetoothremotegattdescriptor::BluetoothRemoteGATTDescriptor; -use dom::bluetoothremotegattserver::BluetoothRemoteGATTServer; -use dom::bluetoothremotegattservice::BluetoothRemoteGATTService; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; use dom_struct::dom_struct; -use ipc_channel::ipc::{self, IpcSender}; -use js::jsapi::JSContext; +use ipc_channel::ipc::IpcSender; +use profile_traits::ipc; use std::cell::Cell; use std::collections::HashMap; use std::rc::Rc; @@ -37,122 +36,146 @@ pub struct BluetoothDevice { eventtarget: EventTarget, id: DOMString, name: Option<DOMString>, - gatt: MutNullableJS<BluetoothRemoteGATTServer>, - context: JS<Bluetooth>, - attribute_instance_map: (DOMRefCell<HashMap<String, JS<BluetoothRemoteGATTService>>>, - DOMRefCell<HashMap<String, JS<BluetoothRemoteGATTCharacteristic>>>, - DOMRefCell<HashMap<String, JS<BluetoothRemoteGATTDescriptor>>>), + gatt: MutNullableDom<BluetoothRemoteGATTServer>, + context: Dom<Bluetooth>, + attribute_instance_map: ( + DomRefCell<HashMap<String, Dom<BluetoothRemoteGATTService>>>, + DomRefCell<HashMap<String, Dom<BluetoothRemoteGATTCharacteristic>>>, + DomRefCell<HashMap<String, Dom<BluetoothRemoteGATTDescriptor>>>, + ), watching_advertisements: Cell<bool>, } impl BluetoothDevice { - pub fn new_inherited(id: DOMString, - name: Option<DOMString>, - context: &Bluetooth) - -> BluetoothDevice { + pub fn new_inherited( + id: DOMString, + name: Option<DOMString>, + context: &Bluetooth, + ) -> BluetoothDevice { BluetoothDevice { eventtarget: EventTarget::new_inherited(), id: id, name: name, gatt: Default::default(), - context: JS::from_ref(context), - attribute_instance_map: (DOMRefCell::new(HashMap::new()), - DOMRefCell::new(HashMap::new()), - DOMRefCell::new(HashMap::new())), + context: Dom::from_ref(context), + attribute_instance_map: ( + DomRefCell::new(HashMap::new()), + DomRefCell::new(HashMap::new()), + DomRefCell::new(HashMap::new()), + ), watching_advertisements: Cell::new(false), } } - pub fn new(global: &GlobalScope, - id: DOMString, - name: Option<DOMString>, - context: &Bluetooth) - -> Root<BluetoothDevice> { - reflect_dom_object(box BluetoothDevice::new_inherited(id, - name, - context), - global, - BluetoothDeviceBinding::Wrap) + pub fn new( + global: &GlobalScope, + id: DOMString, + name: Option<DOMString>, + context: &Bluetooth, + ) -> DomRoot<BluetoothDevice> { + reflect_dom_object( + Box::new(BluetoothDevice::new_inherited(id, name, context)), + global, + ) } - pub fn get_gatt(&self) -> Root<BluetoothRemoteGATTServer> { - self.gatt.or_init(|| { - BluetoothRemoteGATTServer::new(&self.global(), self) - }) + pub fn get_gatt(&self) -> DomRoot<BluetoothRemoteGATTServer> { + self.gatt + .or_init(|| BluetoothRemoteGATTServer::new(&self.global(), self)) } - fn get_context(&self) -> Root<Bluetooth> { - Root::from_ref(&self.context) + fn get_context(&self) -> DomRoot<Bluetooth> { + DomRoot::from_ref(&self.context) } - pub fn get_or_create_service(&self, - service: &BluetoothServiceMsg, - server: &BluetoothRemoteGATTServer) - -> Root<BluetoothRemoteGATTService> { + pub fn get_or_create_service( + &self, + service: &BluetoothServiceMsg, + server: &BluetoothRemoteGATTServer, + ) -> DomRoot<BluetoothRemoteGATTService> { let (ref service_map_ref, _, _) = self.attribute_instance_map; let mut service_map = service_map_ref.borrow_mut(); if let Some(existing_service) = service_map.get(&service.instance_id) { - return Root::from_ref(&existing_service); + return DomRoot::from_ref(&existing_service); } - let bt_service = BluetoothRemoteGATTService::new(&server.global(), - &server.Device(), - DOMString::from(service.uuid.clone()), - service.is_primary, - service.instance_id.clone()); - service_map.insert(service.instance_id.clone(), JS::from_ref(&bt_service)); + let bt_service = BluetoothRemoteGATTService::new( + &server.global(), + &server.Device(), + DOMString::from(service.uuid.clone()), + service.is_primary, + service.instance_id.clone(), + ); + service_map.insert(service.instance_id.clone(), Dom::from_ref(&bt_service)); return bt_service; } - pub fn get_or_create_characteristic(&self, - characteristic: &BluetoothCharacteristicMsg, - service: &BluetoothRemoteGATTService) - -> Root<BluetoothRemoteGATTCharacteristic> { + pub fn get_or_create_characteristic( + &self, + characteristic: &BluetoothCharacteristicMsg, + service: &BluetoothRemoteGATTService, + ) -> DomRoot<BluetoothRemoteGATTCharacteristic> { let (_, ref characteristic_map_ref, _) = self.attribute_instance_map; let mut characteristic_map = characteristic_map_ref.borrow_mut(); if let Some(existing_characteristic) = characteristic_map.get(&characteristic.instance_id) { - return Root::from_ref(&existing_characteristic); + return DomRoot::from_ref(&existing_characteristic); } - let properties = - BluetoothCharacteristicProperties::new(&service.global(), - characteristic.broadcast, - characteristic.read, - characteristic.write_without_response, - characteristic.write, - characteristic.notify, - characteristic.indicate, - characteristic.authenticated_signed_writes, - characteristic.reliable_write, - characteristic.writable_auxiliaries); - let bt_characteristic = BluetoothRemoteGATTCharacteristic::new(&service.global(), - service, - DOMString::from(characteristic.uuid.clone()), - &properties, - characteristic.instance_id.clone()); - characteristic_map.insert(characteristic.instance_id.clone(), JS::from_ref(&bt_characteristic)); + let properties = BluetoothCharacteristicProperties::new( + &service.global(), + characteristic.broadcast, + characteristic.read, + characteristic.write_without_response, + characteristic.write, + characteristic.notify, + characteristic.indicate, + characteristic.authenticated_signed_writes, + characteristic.reliable_write, + characteristic.writable_auxiliaries, + ); + let bt_characteristic = BluetoothRemoteGATTCharacteristic::new( + &service.global(), + service, + DOMString::from(characteristic.uuid.clone()), + &properties, + characteristic.instance_id.clone(), + ); + characteristic_map.insert( + characteristic.instance_id.clone(), + Dom::from_ref(&bt_characteristic), + ); return bt_characteristic; } pub fn is_represented_device_null(&self) -> bool { - let (sender, receiver) = ipc::channel().unwrap(); - self.get_bluetooth_thread().send( - BluetoothRequest::IsRepresentedDeviceNull(self.Id().to_string(), sender)).unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::IsRepresentedDeviceNull( + self.Id().to_string(), + sender, + )) + .unwrap(); receiver.recv().unwrap() } - pub fn get_or_create_descriptor(&self, - descriptor: &BluetoothDescriptorMsg, - characteristic: &BluetoothRemoteGATTCharacteristic) - -> Root<BluetoothRemoteGATTDescriptor> { + pub fn get_or_create_descriptor( + &self, + descriptor: &BluetoothDescriptorMsg, + characteristic: &BluetoothRemoteGATTCharacteristic, + ) -> DomRoot<BluetoothRemoteGATTDescriptor> { let (_, _, ref descriptor_map_ref) = self.attribute_instance_map; let mut descriptor_map = descriptor_map_ref.borrow_mut(); if let Some(existing_descriptor) = descriptor_map.get(&descriptor.instance_id) { - return Root::from_ref(&existing_descriptor); + return DomRoot::from_ref(&existing_descriptor); } - let bt_descriptor = BluetoothRemoteGATTDescriptor::new(&characteristic.global(), - characteristic, - DOMString::from(descriptor.uuid.clone()), - descriptor.instance_id.clone()); - descriptor_map.insert(descriptor.instance_id.clone(), JS::from_ref(&bt_descriptor)); + let bt_descriptor = BluetoothRemoteGATTDescriptor::new( + &characteristic.global(), + characteristic, + DOMString::from(descriptor.uuid.clone()), + descriptor.instance_id.clone(), + ); + descriptor_map.insert( + descriptor.instance_id.clone(), + Dom::from_ref(&bt_descriptor), + ); return bt_descriptor; } @@ -183,11 +206,17 @@ impl BluetoothDevice { // Step 5, 6.4, 7. // TODO: Step 6: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic. - let _ = self.get_bluetooth_thread().send( - BluetoothRequest::SetRepresentedToNull(service_ids, characteristic_ids, descriptor_ids)); + let _ = self + .get_bluetooth_thread() + .send(BluetoothRequest::SetRepresentedToNull( + service_ids, + characteristic_ids, + descriptor_ids, + )); // Step 8. - self.upcast::<EventTarget>().fire_bubbling_event(atom!("gattserverdisconnected")); + self.upcast::<EventTarget>() + .fire_bubbling_event(atom!("gattserverdisconnected")); } // https://webbluetoothcg.github.io/web-bluetooth/#garbage-collect-the-connection @@ -208,15 +237,19 @@ impl BluetoothDevice { } // Step 3. - let (sender, receiver) = ipc::channel().unwrap(); - self.get_bluetooth_thread().send( - BluetoothRequest::GATTServerDisconnect(String::from(self.Id()), sender)).unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::GATTServerDisconnect( + String::from(self.Id()), + sender, + )) + .unwrap(); receiver.recv().unwrap().map_err(Error::from) } } impl BluetoothDeviceMethods for BluetoothDevice { - // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-id + // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-id fn Id(&self) -> DOMString { self.id.clone() } @@ -227,26 +260,34 @@ impl BluetoothDeviceMethods for BluetoothDevice { } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-gatt - fn GetGatt(&self) -> Option<Root<BluetoothRemoteGATTServer>> { + fn GetGatt(&self) -> Option<DomRoot<BluetoothRemoteGATTServer>> { // Step 1. - if self.global().as_window().bluetooth_extra_permission_data() - .allowed_devices_contains_id(self.id.clone()) && !self.is_represented_device_null() { - return Some(self.get_gatt()) + if self + .global() + .as_window() + .bluetooth_extra_permission_data() + .allowed_devices_contains_id(self.id.clone()) && + !self.is_represented_device_null() + { + return Some(self.get_gatt()); } // Step 2. None } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-watchadvertisements - fn WatchAdvertisements(&self) -> Rc<Promise> { - let p = Promise::new(&self.global()); + fn WatchAdvertisements(&self, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); let sender = response_async(&p, self); // TODO: Step 1. // Note: Steps 2 - 3 are implemented in components/bluetooth/lib.rs in watch_advertisements function // and in handle_response function. - self.get_bluetooth_thread().send( - BluetoothRequest::WatchAdvertisements(String::from(self.Id()), sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::WatchAdvertisements( + String::from(self.Id()), + sender, + )) + .unwrap(); return p; } @@ -263,20 +304,24 @@ impl BluetoothDeviceMethods for BluetoothDevice { } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdeviceeventhandlers-ongattserverdisconnected - event_handler!(gattserverdisconnected, GetOngattserverdisconnected, SetOngattserverdisconnected); + event_handler!( + gattserverdisconnected, + GetOngattserverdisconnected, + SetOngattserverdisconnected + ); } impl AsyncBluetoothListener for BluetoothDevice { - fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) { + fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>) { match response { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothdevice-unwatchadvertisements BluetoothResponse::WatchAdvertisements(_result) => { // Step 3.1. self.watching_advertisements.set(true); // Step 3.2. - promise.resolve_native(promise_cx, &()); + promise.resolve_native(&()); }, - _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())), + _ => promise.reject_error(Error::Type("Something went wrong...".to_owned())), } } } diff --git a/components/script/dom/bluetoothpermissionresult.rs b/components/script/dom/bluetoothpermissionresult.rs index da5de4bf5b1..0f7266e0443 100644 --- a/components/script/dom/bluetoothpermissionresult.rs +++ b/components/script/dom/bluetoothpermissionresult.rs @@ -1,33 +1,34 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::BluetoothPermissionResultMethods; +use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods; +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatusBinding::PermissionStatusMethods; +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{ + PermissionName, PermissionState, +}; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bluetooth::{AllowedBluetoothDevice, AsyncBluetoothListener, Bluetooth}; +use crate::dom::bluetoothdevice::BluetoothDevice; +use crate::dom::globalscope::GlobalScope; +use crate::dom::permissionstatus::PermissionStatus; +use crate::dom::promise::Promise; use bluetooth_traits::{BluetoothRequest, BluetoothResponse}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BluetoothPermissionResultBinding::{self, BluetoothPermissionResultMethods}; -use dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods; -use dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionName, PermissionState}; -use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatusBinding::PermissionStatusMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::error::Error; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::bluetooth::{AsyncBluetoothListener, Bluetooth, AllowedBluetoothDevice}; -use dom::bluetoothdevice::BluetoothDevice; -use dom::globalscope::GlobalScope; -use dom::permissionstatus::PermissionStatus; -use dom::promise::Promise; use dom_struct::dom_struct; use ipc_channel::ipc::IpcSender; -use js::jsapi::JSContext; use std::rc::Rc; // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothpermissionresult #[dom_struct] pub struct BluetoothPermissionResult { status: PermissionStatus, - devices: DOMRefCell<Vec<JS<BluetoothDevice>>>, + devices: DomRefCell<Vec<Dom<BluetoothDevice>>>, } impl BluetoothPermissionResult { @@ -35,19 +36,23 @@ impl BluetoothPermissionResult { fn new_inherited(status: &PermissionStatus) -> BluetoothPermissionResult { let result = BluetoothPermissionResult { status: PermissionStatus::new_inherited(status.get_query()), - devices: DOMRefCell::new(Vec::new()), + devices: DomRefCell::new(Vec::new()), }; result.status.set_state(status.State()); result } - pub fn new(global: &GlobalScope, status: &PermissionStatus) -> Root<BluetoothPermissionResult> { - reflect_dom_object(box BluetoothPermissionResult::new_inherited(status), - global, - BluetoothPermissionResultBinding::Wrap) + pub fn new( + global: &GlobalScope, + status: &PermissionStatus, + ) -> DomRoot<BluetoothPermissionResult> { + reflect_dom_object( + Box::new(BluetoothPermissionResult::new_inherited(status)), + global, + ) } - pub fn get_bluetooth(&self) -> Root<Bluetooth> { + pub fn get_bluetooth(&self) -> DomRoot<Bluetooth> { self.global().as_window().Navigator().Bluetooth() } @@ -68,22 +73,26 @@ impl BluetoothPermissionResult { } #[allow(unrooted_must_root)] - pub fn set_devices(&self, devices: Vec<JS<BluetoothDevice>>) { + pub fn set_devices(&self, devices: Vec<Dom<BluetoothDevice>>) { *self.devices.borrow_mut() = devices; } } impl BluetoothPermissionResultMethods for BluetoothPermissionResult { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothpermissionresult-devices - fn Devices(&self) -> Vec<Root<BluetoothDevice>> { - let device_vec: Vec<Root<BluetoothDevice>> = - self.devices.borrow().iter().map(|d| Root::from_ref(&**d)).collect(); + fn Devices(&self) -> Vec<DomRoot<BluetoothDevice>> { + let device_vec: Vec<DomRoot<BluetoothDevice>> = self + .devices + .borrow() + .iter() + .map(|d| DomRoot::from_ref(&**d)) + .collect(); device_vec } } impl AsyncBluetoothListener for BluetoothPermissionResult { - fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) { + fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>) { match response { // https://webbluetoothcg.github.io/web-bluetooth/#request-bluetooth-devices // Step 3, 11, 13 - 14. @@ -94,32 +103,35 @@ impl AsyncBluetoothListener for BluetoothPermissionResult { if let Some(ref existing_device) = device_instance_map.get(&device.id) { // https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission // Step 3. - self.set_devices(vec!(JS::from_ref(&*existing_device))); + self.set_devices(vec![Dom::from_ref(&*existing_device)]); // https://w3c.github.io/permissions/#dom-permissions-request // Step 8. - return promise.resolve_native(promise_cx, self); + return promise.resolve_native(self); } - let bt_device = BluetoothDevice::new(&self.global(), - DOMString::from(device.id.clone()), - device.name.map(DOMString::from), - &bluetooth); - device_instance_map.insert(device.id.clone(), JS::from_ref(&bt_device)); - self.global().as_window().bluetooth_extra_permission_data().add_new_allowed_device( - AllowedBluetoothDevice { + let bt_device = BluetoothDevice::new( + &self.global(), + DOMString::from(device.id.clone()), + device.name.map(DOMString::from), + &bluetooth, + ); + device_instance_map.insert(device.id.clone(), Dom::from_ref(&bt_device)); + self.global() + .as_window() + .bluetooth_extra_permission_data() + .add_new_allowed_device(AllowedBluetoothDevice { deviceId: DOMString::from(device.id), mayUseGATT: true, - } - ); + }); // https://webbluetoothcg.github.io/web-bluetooth/#request-the-bluetooth-permission // Step 3. - self.set_devices(vec!(JS::from_ref(&bt_device))); + self.set_devices(vec![Dom::from_ref(&bt_device)]); // https://w3c.github.io/permissions/#dom-permissions-request // Step 8. - promise.resolve_native(promise_cx, self); + promise.resolve_native(self); }, - _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())), + _ => promise.reject_error(Error::Type("Something went wrong...".to_owned())), } } } diff --git a/components/script/dom/bluetoothremotegattcharacteristic.rs b/components/script/dom/bluetoothremotegattcharacteristic.rs index d6a39c154e3..fd01653c84d 100644 --- a/components/script/dom/bluetoothremotegattcharacteristic.rs +++ b/components/script/dom/bluetoothremotegattcharacteristic.rs @@ -1,33 +1,32 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding::BluetoothCharacteristicPropertiesMethods; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::BluetoothRemoteGATTCharacteristicMethods; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods; +use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer; +use crate::dom::bindings::error::Error::{ + self, InvalidModification, Network, NotSupported, Security, +}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::{ByteString, DOMString}; +use crate::dom::bluetooth::{get_gatt_children, response_async, AsyncBluetoothListener}; +use crate::dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties; +use crate::dom::bluetoothremotegattservice::BluetoothRemoteGATTService; +use crate::dom::bluetoothuuid::{BluetoothDescriptorUUID, BluetoothUUID}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::realms::InRealm; +use bluetooth_traits::blocklist::{uuid_is_blocklisted, Blocklist}; use bluetooth_traits::{BluetoothRequest, BluetoothResponse, GATTType}; -use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BluetoothCharacteristicPropertiesBinding:: - BluetoothCharacteristicPropertiesMethods; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding:: - BluetoothRemoteGATTCharacteristicMethods; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::error::Error::{self, InvalidModification, Network, NotSupported, Security}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::{ByteString, DOMString}; -use dom::bluetooth::{AsyncBluetoothListener, get_gatt_children, response_async}; -use dom::bluetoothcharacteristicproperties::BluetoothCharacteristicProperties; -use dom::bluetoothremotegattservice::BluetoothRemoteGATTService; -use dom::bluetoothuuid::{BluetoothDescriptorUUID, BluetoothUUID}; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; use dom_struct::dom_struct; use ipc_channel::ipc::IpcSender; -use js::jsapi::JSContext; use std::rc::Rc; // Maximum length of an attribute value. @@ -38,41 +37,46 @@ pub const MAXIMUM_ATTRIBUTE_LENGTH: usize = 512; #[dom_struct] pub struct BluetoothRemoteGATTCharacteristic { eventtarget: EventTarget, - service: JS<BluetoothRemoteGATTService>, + service: Dom<BluetoothRemoteGATTService>, uuid: DOMString, - properties: JS<BluetoothCharacteristicProperties>, - value: DOMRefCell<Option<ByteString>>, + properties: Dom<BluetoothCharacteristicProperties>, + value: DomRefCell<Option<ByteString>>, instance_id: String, } impl BluetoothRemoteGATTCharacteristic { - pub fn new_inherited(service: &BluetoothRemoteGATTService, - uuid: DOMString, - properties: &BluetoothCharacteristicProperties, - instance_id: String) - -> BluetoothRemoteGATTCharacteristic { + pub fn new_inherited( + service: &BluetoothRemoteGATTService, + uuid: DOMString, + properties: &BluetoothCharacteristicProperties, + instance_id: String, + ) -> BluetoothRemoteGATTCharacteristic { BluetoothRemoteGATTCharacteristic { eventtarget: EventTarget::new_inherited(), - service: JS::from_ref(service), + service: Dom::from_ref(service), uuid: uuid, - properties: JS::from_ref(properties), - value: DOMRefCell::new(None), + properties: Dom::from_ref(properties), + value: DomRefCell::new(None), instance_id: instance_id, } } - pub fn new(global: &GlobalScope, - service: &BluetoothRemoteGATTService, - uuid: DOMString, - properties: &BluetoothCharacteristicProperties, - instanceID: String) - -> Root<BluetoothRemoteGATTCharacteristic> { - reflect_dom_object(box BluetoothRemoteGATTCharacteristic::new_inherited(service, - uuid, - properties, - instanceID), - global, - BluetoothRemoteGATTCharacteristicBinding::Wrap) + pub fn new( + global: &GlobalScope, + service: &BluetoothRemoteGATTService, + uuid: DOMString, + properties: &BluetoothCharacteristicProperties, + instance_id: String, + ) -> DomRoot<BluetoothRemoteGATTCharacteristic> { + reflect_dom_object( + Box::new(BluetoothRemoteGATTCharacteristic::new_inherited( + service, + uuid, + properties, + instance_id, + )), + global, + ) } fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> { @@ -86,13 +90,13 @@ impl BluetoothRemoteGATTCharacteristic { impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteristic { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-properties - fn Properties(&self) -> Root<BluetoothCharacteristicProperties> { - Root::from_ref(&self.properties) + fn Properties(&self) -> DomRoot<BluetoothCharacteristicProperties> { + DomRoot::from_ref(&self.properties) } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-service - fn Service(&self) -> Root<BluetoothRemoteGATTService> { - Root::from_ref(&self.service) + fn Service(&self) -> DomRoot<BluetoothRemoteGATTService> { + DomRoot::from_ref(&self.service) } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-uuid @@ -100,20 +104,30 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris self.uuid.clone() } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptor fn GetDescriptor(&self, descriptor: BluetoothDescriptorUUID) -> Rc<Promise> { - get_gatt_children(self, true, BluetoothUUID::descriptor, Some(descriptor), self.get_instance_id(), - self.Service().Device().get_gatt().Connected(), GATTType::Descriptor) + get_gatt_children( + self, + true, + BluetoothUUID::descriptor, + Some(descriptor), + self.get_instance_id(), + self.Service().Device().get_gatt().Connected(), + GATTType::Descriptor, + ) } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-getdescriptors - fn GetDescriptors(&self, - descriptor: Option<BluetoothDescriptorUUID>) - -> Rc<Promise> { - get_gatt_children(self, false, BluetoothUUID::descriptor, descriptor, self.get_instance_id(), - self.Service().Device().get_gatt().Connected(), GATTType::Descriptor) + fn GetDescriptors(&self, descriptor: Option<BluetoothDescriptorUUID>) -> Rc<Promise> { + get_gatt_children( + self, + false, + BluetoothUUID::descriptor, + descriptor, + self.get_instance_id(), + self.Service().Device().get_gatt().Connected(), + GATTType::Descriptor, + ) } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-value @@ -121,21 +135,19 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris self.value.borrow().clone() } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue - fn ReadValue(&self) -> Rc<Promise> { - let p = Promise::new(&self.global()); - let p_cx = p.global().get_cx(); + fn ReadValue(&self, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); // Step 1. if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) { - p.reject_error(p_cx, Security); + p.reject_error(Security); return p; } // Step 2. if !self.Service().Device().get_gatt().Connected() { - p.reject_error(p_cx, Network); + p.reject_error(Network); return p; } @@ -143,39 +155,43 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris // Step 5.1. if !self.Properties().Read() { - p.reject_error(p_cx, NotSupported); + p.reject_error(NotSupported); return p; } // Note: Steps 3 - 4 and the remaining substeps of Step 5 are implemented in components/bluetooth/lib.rs // in readValue function and in handle_response function. let sender = response_async(&p, self); - self.get_bluetooth_thread().send( - BluetoothRequest::ReadValue(self.get_instance_id(), sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::ReadValue(self.get_instance_id(), sender)) + .unwrap(); return p; } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue - fn WriteValue(&self, value: Vec<u8>) -> Rc<Promise> { - let p = Promise::new(&self.global()); - let p_cx = p.global().get_cx(); + fn WriteValue(&self, value: ArrayBufferViewOrArrayBuffer, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); // Step 1. if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Writes) { - p.reject_error(p_cx, Security); + p.reject_error(Security); return p; } // Step 2 - 3. - if value.len() > MAXIMUM_ATTRIBUTE_LENGTH { - p.reject_error(p_cx, InvalidModification); + let vec = match value { + ArrayBufferViewOrArrayBuffer::ArrayBufferView(avb) => avb.to_vec(), + ArrayBufferViewOrArrayBuffer::ArrayBuffer(ab) => ab.to_vec(), + }; + + if vec.len() > MAXIMUM_ATTRIBUTE_LENGTH { + p.reject_error(InvalidModification); return p; } // Step 4. if !self.Service().Device().get_gatt().Connected() { - p.reject_error(p_cx, Network); + p.reject_error(Network); return p; } @@ -183,42 +199,45 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris // Step 7.1. if !(self.Properties().Write() || - self.Properties().WriteWithoutResponse() || - self.Properties().AuthenticatedSignedWrites()) { - p.reject_error(p_cx, NotSupported); + self.Properties().WriteWithoutResponse() || + self.Properties().AuthenticatedSignedWrites()) + { + p.reject_error(NotSupported); return p; } // Note: Steps 5 - 6 and the remaining substeps of Step 7 are implemented in components/bluetooth/lib.rs // in writeValue function and in handle_response function. let sender = response_async(&p, self); - self.get_bluetooth_thread().send( - BluetoothRequest::WriteValue(self.get_instance_id(), value, sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::WriteValue( + self.get_instance_id(), + vec, + sender, + )) + .unwrap(); return p; } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications - fn StartNotifications(&self) -> Rc<Promise> { - let p = Promise::new(&self.global()); - let p_cx = p.global().get_cx(); + fn StartNotifications(&self, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); // Step 1. if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) { - p.reject_error(p_cx, Security); + p.reject_error(Security); return p; } // Step 2. if !self.Service().Device().get_gatt().Connected() { - p.reject_error(p_cx, Network); + p.reject_error(Network); return p; } // Step 5. - if !(self.Properties().Notify() || - self.Properties().Indicate()) { - p.reject_error(p_cx, NotSupported); + if !(self.Properties().Notify() || self.Properties().Indicate()) { + p.reject_error(NotSupported); return p; } @@ -227,51 +246,62 @@ impl BluetoothRemoteGATTCharacteristicMethods for BluetoothRemoteGATTCharacteris // Note: Steps 3 - 4, 7 - 11 are implemented in components/bluetooth/lib.rs in enable_notification function // and in handle_response function. let sender = response_async(&p, self); - self.get_bluetooth_thread().send( - BluetoothRequest::EnableNotification(self.get_instance_id(), - true, - sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::EnableNotification( + self.get_instance_id(), + true, + sender, + )) + .unwrap(); return p; } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications - fn StopNotifications(&self) -> Rc<Promise> { - let p = Promise::new(&self.global()); + fn StopNotifications(&self, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); let sender = response_async(&p, self); // TODO: Step 3 - 4: Implement `active notification context set` for BluetoothRemoteGATTCharacteristic, // Note: Steps 1 - 2, and part of Step 4 and Step 5 are implemented in components/bluetooth/lib.rs // in enable_notification function and in handle_response function. - self.get_bluetooth_thread().send( - BluetoothRequest::EnableNotification(self.get_instance_id(), - false, - sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::EnableNotification( + self.get_instance_id(), + false, + sender, + )) + .unwrap(); return p; } // https://webbluetoothcg.github.io/web-bluetooth/#dom-characteristiceventhandlers-oncharacteristicvaluechanged - event_handler!(characteristicvaluechanged, GetOncharacteristicvaluechanged, SetOncharacteristicvaluechanged); + event_handler!( + characteristicvaluechanged, + GetOncharacteristicvaluechanged, + SetOncharacteristicvaluechanged + ); } impl AsyncBluetoothListener for BluetoothRemoteGATTCharacteristic { - fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) { + fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>) { let device = self.Service().Device(); match response { // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren // Step 7. BluetoothResponse::GetDescriptors(descriptors_vec, single) => { if single { - promise.resolve_native(promise_cx, &device.get_or_create_descriptor(&descriptors_vec[0], &self)); + promise.resolve_native( + &device.get_or_create_descriptor(&descriptors_vec[0], &self), + ); return; } - let mut descriptors = vec!(); + let mut descriptors = vec![]; for descriptor in descriptors_vec { let bt_descriptor = device.get_or_create_descriptor(&descriptor, &self); descriptors.push(bt_descriptor); } - promise.resolve_native(promise_cx, &descriptors); + promise.resolve_native(&descriptors); }, // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-readvalue BluetoothResponse::ReadValue(result) => { @@ -283,10 +313,11 @@ impl AsyncBluetoothListener for BluetoothRemoteGATTCharacteristic { *self.value.borrow_mut() = Some(value.clone()); // Step 5.5.3. - self.upcast::<EventTarget>().fire_bubbling_event(atom!("characteristicvaluechanged")); + self.upcast::<EventTarget>() + .fire_bubbling_event(atom!("characteristicvaluechanged")); // Step 5.5.4. - promise.resolve_native(promise_cx, &value); + promise.resolve_native(&value); }, // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-writevalue BluetoothResponse::WriteValue(result) => { @@ -297,7 +328,7 @@ impl AsyncBluetoothListener for BluetoothRemoteGATTCharacteristic { *self.value.borrow_mut() = Some(ByteString::new(result)); // Step 7.5.3. - promise.resolve_native(promise_cx, &()); + promise.resolve_native(&()); }, // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-startnotifications // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattcharacteristic-stopnotifications @@ -307,9 +338,9 @@ impl AsyncBluetoothListener for BluetoothRemoteGATTCharacteristic { // (StartNotification) Step 11. // (StopNotification) Step 5. - promise.resolve_native(promise_cx, self); + promise.resolve_native(self); }, - _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())), + _ => promise.reject_error(Error::Type("Something went wrong...".to_owned())), } } } diff --git a/components/script/dom/bluetoothremotegattdescriptor.rs b/components/script/dom/bluetoothremotegattdescriptor.rs index c9e24c499be..39e298f923c 100644 --- a/components/script/dom/bluetoothremotegattdescriptor.rs +++ b/components/script/dom/bluetoothremotegattdescriptor.rs @@ -1,63 +1,69 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding::BluetoothRemoteGATTCharacteristicMethods; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTDescriptorBinding::BluetoothRemoteGATTDescriptorMethods; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods; +use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer; +use crate::dom::bindings::error::Error::{self, InvalidModification, Network, Security}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::{ByteString, DOMString}; +use crate::dom::bluetooth::{response_async, AsyncBluetoothListener}; +use crate::dom::bluetoothremotegattcharacteristic::{ + BluetoothRemoteGATTCharacteristic, MAXIMUM_ATTRIBUTE_LENGTH, +}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::realms::InRealm; +use bluetooth_traits::blocklist::{uuid_is_blocklisted, Blocklist}; use bluetooth_traits::{BluetoothRequest, BluetoothResponse}; -use bluetooth_traits::blocklist::{Blocklist, uuid_is_blocklisted}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTCharacteristicBinding:: - BluetoothRemoteGATTCharacteristicMethods; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTDescriptorBinding; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTDescriptorBinding::BluetoothRemoteGATTDescriptorMethods; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods; -use dom::bindings::error::Error::{self, InvalidModification, Network, Security}; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::{ByteString, DOMString}; -use dom::bluetooth::{AsyncBluetoothListener, response_async}; -use dom::bluetoothremotegattcharacteristic::{BluetoothRemoteGATTCharacteristic, MAXIMUM_ATTRIBUTE_LENGTH}; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; use dom_struct::dom_struct; use ipc_channel::ipc::IpcSender; -use js::jsapi::JSContext; use std::rc::Rc; // http://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattdescriptor #[dom_struct] pub struct BluetoothRemoteGATTDescriptor { reflector_: Reflector, - characteristic: JS<BluetoothRemoteGATTCharacteristic>, + characteristic: Dom<BluetoothRemoteGATTCharacteristic>, uuid: DOMString, - value: DOMRefCell<Option<ByteString>>, + value: DomRefCell<Option<ByteString>>, instance_id: String, } impl BluetoothRemoteGATTDescriptor { - pub fn new_inherited(characteristic: &BluetoothRemoteGATTCharacteristic, - uuid: DOMString, - instance_id: String) - -> BluetoothRemoteGATTDescriptor { + pub fn new_inherited( + characteristic: &BluetoothRemoteGATTCharacteristic, + uuid: DOMString, + instance_id: String, + ) -> BluetoothRemoteGATTDescriptor { BluetoothRemoteGATTDescriptor { reflector_: Reflector::new(), - characteristic: JS::from_ref(characteristic), + characteristic: Dom::from_ref(characteristic), uuid: uuid, - value: DOMRefCell::new(None), + value: DomRefCell::new(None), instance_id: instance_id, } } - pub fn new(global: &GlobalScope, - characteristic: &BluetoothRemoteGATTCharacteristic, - uuid: DOMString, - instanceID: String) - -> Root<BluetoothRemoteGATTDescriptor>{ - reflect_dom_object(box BluetoothRemoteGATTDescriptor::new_inherited(characteristic, - uuid, - instanceID), - global, - BluetoothRemoteGATTDescriptorBinding::Wrap) + pub fn new( + global: &GlobalScope, + characteristic: &BluetoothRemoteGATTCharacteristic, + uuid: DOMString, + instance_id: String, + ) -> DomRoot<BluetoothRemoteGATTDescriptor> { + reflect_dom_object( + Box::new(BluetoothRemoteGATTDescriptor::new_inherited( + characteristic, + uuid, + instance_id, + )), + global, + ) } fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> { @@ -71,8 +77,8 @@ impl BluetoothRemoteGATTDescriptor { impl BluetoothRemoteGATTDescriptorMethods for BluetoothRemoteGATTDescriptor { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-characteristic - fn Characteristic(&self) -> Root<BluetoothRemoteGATTCharacteristic> { - Root::from_ref(&self.characteristic) + fn Characteristic(&self) -> DomRoot<BluetoothRemoteGATTCharacteristic> { + DomRoot::from_ref(&self.characteristic) } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-uuid @@ -80,26 +86,30 @@ impl BluetoothRemoteGATTDescriptorMethods for BluetoothRemoteGATTDescriptor { self.uuid.clone() } - // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-value + // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-value fn GetValue(&self) -> Option<ByteString> { self.value.borrow().clone() } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-readvalue - fn ReadValue(&self) -> Rc<Promise> { - let p = Promise::new(&self.global()); - let p_cx = p.global().get_cx(); + fn ReadValue(&self, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); // Step 1. if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Reads) { - p.reject_error(p_cx, Security); + p.reject_error(Security); return p; } // Step 2. - if !self.Characteristic().Service().Device().get_gatt().Connected() { - p.reject_error(p_cx, Network); + if !self + .Characteristic() + .Service() + .Device() + .get_gatt() + .Connected() + { + p.reject_error(Network); return p; } @@ -107,32 +117,41 @@ impl BluetoothRemoteGATTDescriptorMethods for BluetoothRemoteGATTDescriptor { // Note: Steps 3 - 4 and substeps of Step 5 are implemented in components/bluetooth/lib.rs // in readValue function and in handle_response function. let sender = response_async(&p, self); - self.get_bluetooth_thread().send( - BluetoothRequest::ReadValue(self.get_instance_id(), sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::ReadValue(self.get_instance_id(), sender)) + .unwrap(); return p; } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-writevalue - fn WriteValue(&self, value: Vec<u8>) -> Rc<Promise> { - let p = Promise::new(&self.global()); - let p_cx = p.global().get_cx(); + fn WriteValue(&self, value: ArrayBufferViewOrArrayBuffer, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); // Step 1. if uuid_is_blocklisted(self.uuid.as_ref(), Blocklist::Writes) { - p.reject_error(p_cx, Security); + p.reject_error(Security); return p; } // Step 2 - 3. - if value.len() > MAXIMUM_ATTRIBUTE_LENGTH { - p.reject_error(p_cx, InvalidModification); + let vec = match value { + ArrayBufferViewOrArrayBuffer::ArrayBufferView(avb) => avb.to_vec(), + ArrayBufferViewOrArrayBuffer::ArrayBuffer(ab) => ab.to_vec(), + }; + if vec.len() > MAXIMUM_ATTRIBUTE_LENGTH { + p.reject_error(InvalidModification); return p; } // Step 4. - if !self.Characteristic().Service().Device().get_gatt().Connected() { - p.reject_error(p_cx, Network); + if !self + .Characteristic() + .Service() + .Device() + .get_gatt() + .Connected() + { + p.reject_error(Network); return p; } @@ -140,14 +159,19 @@ impl BluetoothRemoteGATTDescriptorMethods for BluetoothRemoteGATTDescriptor { // Note: Steps 5 - 6 and substeps of Step 7 are implemented in components/bluetooth/lib.rs // in writeValue function and in handle_response function. let sender = response_async(&p, self); - self.get_bluetooth_thread().send( - BluetoothRequest::WriteValue(self.get_instance_id(), value, sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::WriteValue( + self.get_instance_id(), + vec, + sender, + )) + .unwrap(); return p; } } impl AsyncBluetoothListener for BluetoothRemoteGATTDescriptor { - fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) { + fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>) { match response { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-readvalue BluetoothResponse::ReadValue(result) => { @@ -159,7 +183,7 @@ impl AsyncBluetoothListener for BluetoothRemoteGATTDescriptor { *self.value.borrow_mut() = Some(value.clone()); // Step 5.4.3. - promise.resolve_native(promise_cx, &value); + promise.resolve_native(&value); }, // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-writevalue BluetoothResponse::WriteValue(result) => { @@ -171,9 +195,9 @@ impl AsyncBluetoothListener for BluetoothRemoteGATTDescriptor { // Step 7.4.3. // TODO: Resolve promise with undefined instead of a value. - promise.resolve_native(promise_cx, &()); + promise.resolve_native(&()); }, - _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())), + _ => promise.reject_error(Error::Type("Something went wrong...".to_owned())), } } } diff --git a/components/script/dom/bluetoothremotegattserver.rs b/components/script/dom/bluetoothremotegattserver.rs index 4a67e03e984..8a4f94fe35c 100644 --- a/components/script/dom/bluetoothremotegattserver.rs +++ b/components/script/dom/bluetoothremotegattserver.rs @@ -1,23 +1,22 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bluetooth::{get_gatt_children, response_async, AsyncBluetoothListener}; +use crate::dom::bluetoothdevice::BluetoothDevice; +use crate::dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::realms::InRealm; use bluetooth_traits::{BluetoothRequest, BluetoothResponse, GATTType}; -use dom::bindings::codegen::Bindings::BluetoothDeviceBinding::BluetoothDeviceMethods; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; -use dom::bindings::error::Error; -use dom::bindings::error::ErrorResult; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bluetooth::{AsyncBluetoothListener, get_gatt_children, response_async}; -use dom::bluetoothdevice::BluetoothDevice; -use dom::bluetoothuuid::{BluetoothServiceUUID, BluetoothUUID}; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; use dom_struct::dom_struct; use ipc_channel::ipc::IpcSender; -use js::jsapi::JSContext; use std::cell::Cell; use std::rc::Rc; @@ -25,7 +24,7 @@ use std::rc::Rc; #[dom_struct] pub struct BluetoothRemoteGATTServer { reflector_: Reflector, - device: JS<BluetoothDevice>, + device: Dom<BluetoothDevice>, connected: Cell<bool>, } @@ -33,15 +32,19 @@ impl BluetoothRemoteGATTServer { pub fn new_inherited(device: &BluetoothDevice) -> BluetoothRemoteGATTServer { BluetoothRemoteGATTServer { reflector_: Reflector::new(), - device: JS::from_ref(device), + device: Dom::from_ref(device), connected: Cell::new(false), } } - pub fn new(global: &GlobalScope, device: &BluetoothDevice) -> Root<BluetoothRemoteGATTServer> { - reflect_dom_object(box BluetoothRemoteGATTServer::new_inherited(device), - global, - BluetoothRemoteGATTServerBinding::Wrap) + pub fn new( + global: &GlobalScope, + device: &BluetoothDevice, + ) -> DomRoot<BluetoothRemoteGATTServer> { + reflect_dom_object( + Box::new(BluetoothRemoteGATTServer::new_inherited(device)), + global, + ) } fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> { @@ -55,8 +58,8 @@ impl BluetoothRemoteGATTServer { impl BluetoothRemoteGATTServerMethods for BluetoothRemoteGATTServer { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-device - fn Device(&self) -> Root<BluetoothDevice> { - Root::from_ref(&self.device) + fn Device(&self) -> DomRoot<BluetoothDevice> { + DomRoot::from_ref(&self.device) } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connected @@ -64,11 +67,11 @@ impl BluetoothRemoteGATTServerMethods for BluetoothRemoteGATTServer { self.connected.get() } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect - fn Connect(&self) -> Rc<Promise> { + #[allow(unsafe_code)] + fn Connect(&self, comp: InRealm) -> Rc<Promise> { // Step 1. - let p = Promise::new(&self.global()); + let p = Promise::new_in_current_realm(&self.global(), comp); let sender = response_async(&p, self); // TODO: Step 3: Check if the UA is currently using the Bluetooth system. @@ -79,8 +82,12 @@ impl BluetoothRemoteGATTServerMethods for BluetoothRemoteGATTServer { // Note: Steps 2, 5.1.1 and 5.1.3 are in components/bluetooth/lib.rs in the gatt_server_connect function. // Steps 5.2.3 - 5.2.5 are in response function. - self.get_bluetooth_thread().send( - BluetoothRequest::GATTServerConnect(String::from(self.Device().Id()), sender)).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::GATTServerConnect( + String::from(self.Device().Id()), + sender, + )) + .unwrap(); // Step 5: return promise. return p; } @@ -91,7 +98,7 @@ impl BluetoothRemoteGATTServerMethods for BluetoothRemoteGATTServer { // Step 2. if !self.Connected() { - return Ok(()) + return Ok(()); } // Step 3. @@ -101,59 +108,70 @@ impl BluetoothRemoteGATTServerMethods for BluetoothRemoteGATTServer { self.Device().garbage_collect_the_connection() } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservice fn GetPrimaryService(&self, service: BluetoothServiceUUID) -> Rc<Promise> { // Step 1 - 2. - get_gatt_children(self, true, BluetoothUUID::service, Some(service), String::from(self.Device().Id()), - self.Device().get_gatt().Connected(), GATTType::PrimaryService) + get_gatt_children( + self, + true, + BluetoothUUID::service, + Some(service), + String::from(self.Device().Id()), + self.Device().get_gatt().Connected(), + GATTType::PrimaryService, + ) } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-getprimaryservices fn GetPrimaryServices(&self, service: Option<BluetoothServiceUUID>) -> Rc<Promise> { // Step 1 - 2. - get_gatt_children(self, false, BluetoothUUID::service, service, String::from(self.Device().Id()), - self.Connected(), GATTType::PrimaryService) - + get_gatt_children( + self, + false, + BluetoothUUID::service, + service, + String::from(self.Device().Id()), + self.Connected(), + GATTType::PrimaryService, + ) } } impl AsyncBluetoothListener for BluetoothRemoteGATTServer { - fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) { + fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>) { match response { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect BluetoothResponse::GATTServerConnect(connected) => { // Step 5.2.3 if self.Device().is_represented_device_null() { if let Err(e) = self.Device().garbage_collect_the_connection() { - return promise.reject_error(promise_cx, Error::from(e)); + return promise.reject_error(Error::from(e)); } - return promise.reject_error(promise_cx, Error::Network); + return promise.reject_error(Error::Network); } // Step 5.2.4. self.connected.set(connected); // Step 5.2.5. - promise.resolve_native(promise_cx, self); + promise.resolve_native(self); }, // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren // Step 7. BluetoothResponse::GetPrimaryServices(services_vec, single) => { let device = self.Device(); if single { - promise.resolve_native(promise_cx, &device.get_or_create_service(&services_vec[0], &self)); + promise.resolve_native(&device.get_or_create_service(&services_vec[0], &self)); return; } - let mut services = vec!(); + let mut services = vec![]; for service in services_vec { let bt_service = device.get_or_create_service(&service, &self); services.push(bt_service); } - promise.resolve_native(promise_cx, &services); + promise.resolve_native(&services); }, - _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())), + _ => promise.reject_error(Error::Type("Something went wrong...".to_owned())), } } } diff --git a/components/script/dom/bluetoothremotegattservice.rs b/components/script/dom/bluetoothremotegattservice.rs index 347604df311..62b46a8b9bd 100644 --- a/components/script/dom/bluetoothremotegattservice.rs +++ b/components/script/dom/bluetoothremotegattservice.rs @@ -1,63 +1,63 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; +use crate::dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bluetooth::{get_gatt_children, AsyncBluetoothListener}; +use crate::dom::bluetoothdevice::BluetoothDevice; +use crate::dom::bluetoothuuid::{BluetoothCharacteristicUUID, BluetoothServiceUUID, BluetoothUUID}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; use bluetooth_traits::{BluetoothResponse, GATTType}; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerMethods; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding; -use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServiceBinding::BluetoothRemoteGATTServiceMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::error::Error; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::bluetooth::{AsyncBluetoothListener, get_gatt_children}; -use dom::bluetoothdevice::BluetoothDevice; -use dom::bluetoothuuid::{BluetoothCharacteristicUUID, BluetoothServiceUUID, BluetoothUUID}; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; use dom_struct::dom_struct; -use js::jsapi::JSContext; use std::rc::Rc; // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattservice #[dom_struct] pub struct BluetoothRemoteGATTService { eventtarget: EventTarget, - device: JS<BluetoothDevice>, + device: Dom<BluetoothDevice>, uuid: DOMString, is_primary: bool, instance_id: String, } impl BluetoothRemoteGATTService { - pub fn new_inherited(device: &BluetoothDevice, - uuid: DOMString, - is_primary: bool, - instance_id: String) - -> BluetoothRemoteGATTService { + pub fn new_inherited( + device: &BluetoothDevice, + uuid: DOMString, + is_primary: bool, + instance_id: String, + ) -> BluetoothRemoteGATTService { BluetoothRemoteGATTService { eventtarget: EventTarget::new_inherited(), - device: JS::from_ref(device), + device: Dom::from_ref(device), uuid: uuid, is_primary: is_primary, instance_id: instance_id, } } - pub fn new(global: &GlobalScope, - device: &BluetoothDevice, - uuid: DOMString, - isPrimary: bool, - instanceID: String) - -> Root<BluetoothRemoteGATTService> { - reflect_dom_object(box BluetoothRemoteGATTService::new_inherited(device, - uuid, - isPrimary, - instanceID), - global, - BluetoothRemoteGATTServiceBinding::Wrap) + #[allow(non_snake_case)] + pub fn new( + global: &GlobalScope, + device: &BluetoothDevice, + uuid: DOMString, + isPrimary: bool, + instanceID: String, + ) -> DomRoot<BluetoothRemoteGATTService> { + reflect_dom_object( + Box::new(BluetoothRemoteGATTService::new_inherited( + device, uuid, isPrimary, instanceID, + )), + global, + ) } fn get_instance_id(&self) -> String { @@ -67,8 +67,8 @@ impl BluetoothRemoteGATTService { impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-device - fn Device(&self) -> Root<BluetoothDevice> { - Root::from_ref(&self.device) + fn Device(&self) -> DomRoot<BluetoothDevice> { + DomRoot::from_ref(&self.device) } // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-isprimary @@ -81,41 +81,59 @@ impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService { self.uuid.clone() } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getcharacteristic - fn GetCharacteristic(&self, - characteristic: BluetoothCharacteristicUUID) - -> Rc<Promise> { - get_gatt_children(self, true, BluetoothUUID::characteristic, Some(characteristic), self.get_instance_id(), - self.Device().get_gatt().Connected(), GATTType::Characteristic) + fn GetCharacteristic(&self, characteristic: BluetoothCharacteristicUUID) -> Rc<Promise> { + get_gatt_children( + self, + true, + BluetoothUUID::characteristic, + Some(characteristic), + self.get_instance_id(), + self.Device().get_gatt().Connected(), + GATTType::Characteristic, + ) } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getcharacteristics - fn GetCharacteristics(&self, - characteristic: Option<BluetoothCharacteristicUUID>) - -> Rc<Promise> { - get_gatt_children(self, false, BluetoothUUID::characteristic, characteristic, self.get_instance_id(), - self.Device().get_gatt().Connected(), GATTType::Characteristic) + fn GetCharacteristics( + &self, + characteristic: Option<BluetoothCharacteristicUUID>, + ) -> Rc<Promise> { + get_gatt_children( + self, + false, + BluetoothUUID::characteristic, + characteristic, + self.get_instance_id(), + self.Device().get_gatt().Connected(), + GATTType::Characteristic, + ) } - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getincludedservice - fn GetIncludedService(&self, - service: BluetoothServiceUUID) - -> Rc<Promise> { - get_gatt_children(self, false, BluetoothUUID::service, Some(service), self.get_instance_id(), - self.Device().get_gatt().Connected(), GATTType::IncludedService) + fn GetIncludedService(&self, service: BluetoothServiceUUID) -> Rc<Promise> { + get_gatt_children( + self, + false, + BluetoothUUID::service, + Some(service), + self.get_instance_id(), + self.Device().get_gatt().Connected(), + GATTType::IncludedService, + ) } - - #[allow(unrooted_must_root)] // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattservice-getincludedservices - fn GetIncludedServices(&self, - service: Option<BluetoothServiceUUID>) - -> Rc<Promise> { - get_gatt_children(self, false, BluetoothUUID::service, service, self.get_instance_id(), - self.Device().get_gatt().Connected(), GATTType::IncludedService) + fn GetIncludedServices(&self, service: Option<BluetoothServiceUUID>) -> Rc<Promise> { + get_gatt_children( + self, + false, + BluetoothUUID::service, + service, + self.get_instance_id(), + self.Device().get_gatt().Connected(), + GATTType::IncludedService, + ) } // https://webbluetoothcg.github.io/web-bluetooth/#dom-serviceeventhandlers-onserviceadded @@ -129,39 +147,42 @@ impl BluetoothRemoteGATTServiceMethods for BluetoothRemoteGATTService { } impl AsyncBluetoothListener for BluetoothRemoteGATTService { - fn handle_response(&self, response: BluetoothResponse, promise_cx: *mut JSContext, promise: &Rc<Promise>) { + fn handle_response(&self, response: BluetoothResponse, promise: &Rc<Promise>) { let device = self.Device(); match response { // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren // Step 7. BluetoothResponse::GetCharacteristics(characteristics_vec, single) => { if single { - promise.resolve_native(promise_cx, - &device.get_or_create_characteristic(&characteristics_vec[0], &self)); + promise.resolve_native( + &device.get_or_create_characteristic(&characteristics_vec[0], &self), + ); return; } - let mut characteristics = vec!(); + let mut characteristics = vec![]; for characteristic in characteristics_vec { - let bt_characteristic = device.get_or_create_characteristic(&characteristic, &self); + let bt_characteristic = + device.get_or_create_characteristic(&characteristic, &self); characteristics.push(bt_characteristic); } - promise.resolve_native(promise_cx, &characteristics); + promise.resolve_native(&characteristics); }, // https://webbluetoothcg.github.io/web-bluetooth/#getgattchildren // Step 7. BluetoothResponse::GetIncludedServices(services_vec, single) => { if single { - return promise.resolve_native(promise_cx, - &device.get_or_create_service(&services_vec[0], &device.get_gatt())); + return promise.resolve_native( + &device.get_or_create_service(&services_vec[0], &device.get_gatt()), + ); } - let mut services = vec!(); + let mut services = vec![]; for service in services_vec { let bt_service = device.get_or_create_service(&service, &device.get_gatt()); services.push(bt_service); } - promise.resolve_native(promise_cx, &services); + promise.resolve_native(&services); }, - _ => promise.reject_error(promise_cx, Error::Type("Something went wrong...".to_owned())), + _ => promise.reject_error(Error::Type("Something went wrong...".to_owned())), } } } diff --git a/components/script/dom/bluetoothuuid.rs b/components/script/dom/bluetoothuuid.rs index a4ef66568a6..70c0c28550a 100644 --- a/components/script/dom/bluetoothuuid.rs +++ b/components/script/dom/bluetoothuuid.rs @@ -1,13 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::UnionTypes::StringOrUnsignedLong; -use dom::bindings::error::Error::Type; -use dom::bindings::error::Fallible; -use dom::bindings::reflector::Reflector; -use dom::bindings::str::DOMString; -use dom::window::Window; +use crate::dom::bindings::codegen::UnionTypes::StringOrUnsignedLong; +use crate::dom::bindings::error::Error::Type; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::str::DOMString; +use crate::dom::window::Window; use dom_struct::dom_struct; use regex::Regex; @@ -17,7 +17,7 @@ pub type BluetoothCharacteristicUUID = StringOrUnsignedLong; pub type BluetoothDescriptorUUID = StringOrUnsignedLong; // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothuuid - #[dom_struct] +#[dom_struct] pub struct BluetoothUUID { reflector_: Reflector, } @@ -30,10 +30,16 @@ const BLUETOOTH_ASSIGNED_SERVICES: &'static [(&'static str, u32)] = &[ ("org.bluetooth.service.blood_pressure", 0x1810_u32), ("org.bluetooth.service.body_composition", 0x181b_u32), ("org.bluetooth.service.bond_management", 0x181e_u32), - ("org.bluetooth.service.continuous_glucose_monitoring", 0x181f_u32), + ( + "org.bluetooth.service.continuous_glucose_monitoring", + 0x181f_u32, + ), ("org.bluetooth.service.current_time", 0x1805_u32), ("org.bluetooth.service.cycling_power", 0x1818_u32), - ("org.bluetooth.service.cycling_speed_and_cadence", 0x1816_u32), + ( + "org.bluetooth.service.cycling_speed_and_cadence", + 0x1816_u32, + ), ("org.bluetooth.service.device_information", 0x180a_u32), ("org.bluetooth.service.environmental_sensing", 0x181a_u32), ("org.bluetooth.service.generic_access", 0x1800_u32), @@ -45,7 +51,10 @@ const BLUETOOTH_ASSIGNED_SERVICES: &'static [(&'static str, u32)] = &[ ("org.bluetooth.service.human_interface_device", 0x1812_u32), ("org.bluetooth.service.immediate_alert", 0x1802_u32), ("org.bluetooth.service.indoor_positioning", 0x1821_u32), - ("org.bluetooth.service.internet_protocol_support", 0x1820_u32), + ( + "org.bluetooth.service.internet_protocol_support", + 0x1820_u32, + ), ("org.bluetooth.service.link_loss", 0x1803_u32), ("org.bluetooth.service.location_and_navigation", 0x1819_u32), ("org.bluetooth.service.next_dst_change", 0x1807_u32), @@ -53,7 +62,10 @@ const BLUETOOTH_ASSIGNED_SERVICES: &'static [(&'static str, u32)] = &[ ("org.bluetooth.service.phone_alert_status", 0x180e_u32), ("org.bluetooth.service.pulse_oximeter", 0x1822_u32), ("org.bluetooth.service.reference_time_update", 0x1806_u32), - ("org.bluetooth.service.running_speed_and_cadence", 0x1814_u32), + ( + "org.bluetooth.service.running_speed_and_cadence", + 0x1814_u32, + ), ("org.bluetooth.service.scan_parameters", 0x1813_u32), ("org.bluetooth.service.transport_discovery", 0x1824), ("org.bluetooth.service.tx_power", 0x1804_u32), @@ -63,57 +75,150 @@ const BLUETOOTH_ASSIGNED_SERVICES: &'static [(&'static str, u32)] = &[ //https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx const BLUETOOTH_ASSIGNED_CHARCTERISTICS: &'static [(&'static str, u32)] = &[ - ("org.bluetooth.characteristic.aerobic_heart_rate_lower_limit", 0x2a7e_u32), - ("org.bluetooth.characteristic.aerobic_heart_rate_upper_limit", 0x2a84_u32), + ( + "org.bluetooth.characteristic.aerobic_heart_rate_lower_limit", + 0x2a7e_u32, + ), + ( + "org.bluetooth.characteristic.aerobic_heart_rate_upper_limit", + 0x2a84_u32, + ), ("org.bluetooth.characteristic.aerobic_threshold", 0x2a7f_u32), ("org.bluetooth.characteristic.age", 0x2a80_u32), ("org.bluetooth.characteristic.aggregate", 0x2a5a_u32), ("org.bluetooth.characteristic.alert_category_id", 0x2a43_u32), - ("org.bluetooth.characteristic.alert_category_id_bit_mask", 0x2a42_u32), + ( + "org.bluetooth.characteristic.alert_category_id_bit_mask", + 0x2a42_u32, + ), ("org.bluetooth.characteristic.alert_level", 0x2a06_u32), - ("org.bluetooth.characteristic.alert_notification_control_point", 0x2a44_u32), + ( + "org.bluetooth.characteristic.alert_notification_control_point", + 0x2a44_u32, + ), ("org.bluetooth.characteristic.alert_status", 0x2a3f_u32), ("org.bluetooth.characteristic.altitude", 0x2ab3_u32), - ("org.bluetooth.characteristic.anaerobic_heart_rate_lower_limit", 0x2a81_u32), - ("org.bluetooth.characteristic.anaerobic_heart_rate_upper_limit", 0x2a82_u32), - ("org.bluetooth.characteristic.anaerobic_threshold", 0x2a83_u32), + ( + "org.bluetooth.characteristic.anaerobic_heart_rate_lower_limit", + 0x2a81_u32, + ), + ( + "org.bluetooth.characteristic.anaerobic_heart_rate_upper_limit", + 0x2a82_u32, + ), + ( + "org.bluetooth.characteristic.anaerobic_threshold", + 0x2a83_u32, + ), ("org.bluetooth.characteristic.analog", 0x2a58_u32), - ("org.bluetooth.characteristic.apparent_wind_direction", 0x2a73_u32), - ("org.bluetooth.characteristic.apparent_wind_speed", 0x2a72_u32), + ( + "org.bluetooth.characteristic.apparent_wind_direction", + 0x2a73_u32, + ), + ( + "org.bluetooth.characteristic.apparent_wind_speed", + 0x2a72_u32, + ), ("org.bluetooth.characteristic.gap.appearance", 0x2a01_u32), - ("org.bluetooth.characteristic.barometric_pressure_trend", 0x2aa3_u32), + ( + "org.bluetooth.characteristic.barometric_pressure_trend", + 0x2aa3_u32, + ), ("org.bluetooth.characteristic.battery_level", 0x2a19_u32), - ("org.bluetooth.characteristic.blood_pressure_feature", 0x2a49_u32), - ("org.bluetooth.characteristic.blood_pressure_measurement", 0x2a35_u32), - ("org.bluetooth.characteristic.body_composition_feature", 0x2a9b_u32), - ("org.bluetooth.characteristic.body_composition_measurement", 0x2a9c_u32), - ("org.bluetooth.characteristic.body_sensor_location", 0x2a38_u32), - ("org.bluetooth.characteristic.bond_management_control_point", 0x2aa4_u32), - ("org.bluetooth.characteristic.bond_management_feature", 0x2aa5_u32), - ("org.bluetooth.characteristic.boot_keyboard_input_report", 0x2a22_u32), - ("org.bluetooth.characteristic.boot_keyboard_output_report", 0x2a32_u32), - ("org.bluetooth.characteristic.boot_mouse_input_report", 0x2a33_u32), - ("org.bluetooth.characteristic.gap.central_address_resolution_support", 0x2aa6_u32), + ( + "org.bluetooth.characteristic.blood_pressure_feature", + 0x2a49_u32, + ), + ( + "org.bluetooth.characteristic.blood_pressure_measurement", + 0x2a35_u32, + ), + ( + "org.bluetooth.characteristic.body_composition_feature", + 0x2a9b_u32, + ), + ( + "org.bluetooth.characteristic.body_composition_measurement", + 0x2a9c_u32, + ), + ( + "org.bluetooth.characteristic.body_sensor_location", + 0x2a38_u32, + ), + ( + "org.bluetooth.characteristic.bond_management_control_point", + 0x2aa4_u32, + ), + ( + "org.bluetooth.characteristic.bond_management_feature", + 0x2aa5_u32, + ), + ( + "org.bluetooth.characteristic.boot_keyboard_input_report", + 0x2a22_u32, + ), + ( + "org.bluetooth.characteristic.boot_keyboard_output_report", + 0x2a32_u32, + ), + ( + "org.bluetooth.characteristic.boot_mouse_input_report", + 0x2a33_u32, + ), + ( + "org.bluetooth.characteristic.gap.central_address_resolution_support", + 0x2aa6_u32, + ), ("org.bluetooth.characteristic.cgm_feature", 0x2aa8_u32), ("org.bluetooth.characteristic.cgm_measurement", 0x2aa7_u32), - ("org.bluetooth.characteristic.cgm_session_run_time", 0x2aab_u32), - ("org.bluetooth.characteristic.cgm_session_start_time", 0x2aaa_u32), - ("org.bluetooth.characteristic.cgm_specific_ops_control_point", 0x2aac_u32), + ( + "org.bluetooth.characteristic.cgm_session_run_time", + 0x2aab_u32, + ), + ( + "org.bluetooth.characteristic.cgm_session_start_time", + 0x2aaa_u32, + ), + ( + "org.bluetooth.characteristic.cgm_specific_ops_control_point", + 0x2aac_u32, + ), ("org.bluetooth.characteristic.cgm_status", 0x2aa9_u32), ("org.bluetooth.characteristic.csc_feature", 0x2a5c_u32), ("org.bluetooth.characteristic.csc_measurement", 0x2a5b_u32), ("org.bluetooth.characteristic.current_time", 0x2a2b_u32), - ("org.bluetooth.characteristic.cycling_power_control_point", 0x2a66_u32), - ("org.bluetooth.characteristic.cycling_power_feature", 0x2a65_u32), - ("org.bluetooth.characteristic.cycling_power_measurement", 0x2a63_u32), - ("org.bluetooth.characteristic.cycling_power_vector", 0x2a64_u32), - ("org.bluetooth.characteristic.database_change_increment", 0x2a99_u32), + ( + "org.bluetooth.characteristic.cycling_power_control_point", + 0x2a66_u32, + ), + ( + "org.bluetooth.characteristic.cycling_power_feature", + 0x2a65_u32, + ), + ( + "org.bluetooth.characteristic.cycling_power_measurement", + 0x2a63_u32, + ), + ( + "org.bluetooth.characteristic.cycling_power_vector", + 0x2a64_u32, + ), + ( + "org.bluetooth.characteristic.database_change_increment", + 0x2a99_u32, + ), ("org.bluetooth.characteristic.date_of_birth", 0x2a85_u32), - ("org.bluetooth.characteristic.date_of_threshold_assessment", 0x2a86_u32), + ( + "org.bluetooth.characteristic.date_of_threshold_assessment", + 0x2a86_u32, + ), ("org.bluetooth.characteristic.date_time", 0x2a08_u32), ("org.bluetooth.characteristic.day_date_time", 0x2a0a_u32), ("org.bluetooth.characteristic.day_of_week", 0x2a09_u32), - ("org.bluetooth.characteristic.descriptor_value_changed", 0x2a7d_u32), + ( + "org.bluetooth.characteristic.descriptor_value_changed", + 0x2a7d_u32, + ), ("org.bluetooth.characteristic.gap.device_name", 0x2a00_u32), ("org.bluetooth.characteristic.dew_point", 0x2a7b_u32), ("org.bluetooth.characteristic.digital", 0x2a56_u32), @@ -121,140 +226,332 @@ const BLUETOOTH_ASSIGNED_CHARCTERISTICS: &'static [(&'static str, u32)] = &[ ("org.bluetooth.characteristic.elevation", 0x2a6c_u32), ("org.bluetooth.characteristic.email_address", 0x2a87_u32), ("org.bluetooth.characteristic.exact_time_256", 0x2a0c_u32), - ("org.bluetooth.characteristic.fat_burn_heart_rate_lower_limit", 0x2a88_u32), - ("org.bluetooth.characteristic.fat_burn_heart_rate_upper_limit", 0x2a89_u32), - ("org.bluetooth.characteristic.firmware_revision_string", 0x2a26_u32), + ( + "org.bluetooth.characteristic.fat_burn_heart_rate_lower_limit", + 0x2a88_u32, + ), + ( + "org.bluetooth.characteristic.fat_burn_heart_rate_upper_limit", + 0x2a89_u32, + ), + ( + "org.bluetooth.characteristic.firmware_revision_string", + 0x2a26_u32, + ), ("org.bluetooth.characteristic.first_name", 0x2a8a_u32), - ("org.bluetooth.characteristic.five_zone_heart_rate_limits", 0x2a8b_u32), + ( + "org.bluetooth.characteristic.five_zone_heart_rate_limits", + 0x2a8b_u32, + ), ("org.bluetooth.characteristic.floor_number", 0x2ab2_u32), ("org.bluetooth.characteristic.gender", 0x2a8c_u32), ("org.bluetooth.characteristic.glucose_feature", 0x2a51_u32), - ("org.bluetooth.characteristic.glucose_measurement", 0x2a18_u32), - ("org.bluetooth.characteristic.glucose_measurement_context", 0x2a34_u32), + ( + "org.bluetooth.characteristic.glucose_measurement", + 0x2a18_u32, + ), + ( + "org.bluetooth.characteristic.glucose_measurement_context", + 0x2a34_u32, + ), ("org.bluetooth.characteristic.gust_factor", 0x2a74_u32), - ("org.bluetooth.characteristic.hardware_revision_string", 0x2a27_u32), - ("org.bluetooth.characteristic.heart_rate_control_point", 0x2a39_u32), + ( + "org.bluetooth.characteristic.hardware_revision_string", + 0x2a27_u32, + ), + ( + "org.bluetooth.characteristic.heart_rate_control_point", + 0x2a39_u32, + ), ("org.bluetooth.characteristic.heart_rate_max", 0x2a8d_u32), - ("org.bluetooth.characteristic.heart_rate_measurement", 0x2a37_u32), + ( + "org.bluetooth.characteristic.heart_rate_measurement", + 0x2a37_u32, + ), ("org.bluetooth.characteristic.heat_index", 0x2a7a_u32), ("org.bluetooth.characteristic.height", 0x2a8e_u32), ("org.bluetooth.characteristic.hid_control_point", 0x2a4c_u32), ("org.bluetooth.characteristic.hid_information", 0x2a4a_u32), ("org.bluetooth.characteristic.hip_circumference", 0x2a8f_u32), - ("org.bluetooth.characteristic.http_control_point", 0x2aba_u32), + ( + "org.bluetooth.characteristic.http_control_point", + 0x2aba_u32, + ), ("org.bluetooth.characteristic.http_entity_body", 0x2ab9_u32), ("org.bluetooth.characteristic.http_headers", 0x2ab7_u32), ("org.bluetooth.characteristic.http_status_code", 0x2ab8_u32), ("org.bluetooth.characteristic.https_security", 0x2abb_u32), ("org.bluetooth.characteristic.humidity", 0x2a6f_u32), - ("org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list", 0x2a2a_u32), - ("org.bluetooth.characteristic.indoor_positioning_configuration", 0x2aad_u32), - ("org.bluetooth.characteristic.intermediate_cuff_pressure", 0x2a36_u32), - ("org.bluetooth.characteristic.intermediate_temperature", 0x2a1e_u32), + ( + "org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list", + 0x2a2a_u32, + ), + ( + "org.bluetooth.characteristic.indoor_positioning_configuration", + 0x2aad_u32, + ), + ( + "org.bluetooth.characteristic.intermediate_cuff_pressure", + 0x2a36_u32, + ), + ( + "org.bluetooth.characteristic.intermediate_temperature", + 0x2a1e_u32, + ), ("org.bluetooth.characteristic.irradiance", 0x2a77_u32), ("org.bluetooth.characteristic.language", 0x2aa2_u32), ("org.bluetooth.characteristic.last_name", 0x2a90_u32), ("org.bluetooth.characteristic.latitude", 0x2aae_u32), ("org.bluetooth.characteristic.ln_control_point", 0x2a6b_u32), ("org.bluetooth.characteristic.ln_feature", 0x2a6a_u32), - ("org.bluetooth.characteristic.local_east_coordinate.xml", 0x2ab1_u32), - ("org.bluetooth.characteristic.local_north_coordinate", 0x2ab0_u32), - ("org.bluetooth.characteristic.local_time_information", 0x2a0f_u32), - ("org.bluetooth.characteristic.location_and_speed", 0x2a67_u32), + ( + "org.bluetooth.characteristic.local_east_coordinate.xml", + 0x2ab1_u32, + ), + ( + "org.bluetooth.characteristic.local_north_coordinate", + 0x2ab0_u32, + ), + ( + "org.bluetooth.characteristic.local_time_information", + 0x2a0f_u32, + ), + ( + "org.bluetooth.characteristic.location_and_speed", + 0x2a67_u32, + ), ("org.bluetooth.characteristic.location_name", 0x2ab5_u32), ("org.bluetooth.characteristic.longitude", 0x2aaf_u32), - ("org.bluetooth.characteristic.magnetic_declination", 0x2a2c_u32), - ("org.bluetooth.characteristic.magnetic_flux_density_2d", 0x2aa0_u32), - ("org.bluetooth.characteristic.magnetic_flux_density_3d", 0x2aa1_u32), - ("org.bluetooth.characteristic.manufacturer_name_string", 0x2a29_u32), - ("org.bluetooth.characteristic.maximum_recommended_heart_rate", 0x2a91_u32), - ("org.bluetooth.characteristic.measurement_interval", 0x2a21_u32), - ("org.bluetooth.characteristic.model_number_string", 0x2a24_u32), + ( + "org.bluetooth.characteristic.magnetic_declination", + 0x2a2c_u32, + ), + ( + "org.bluetooth.characteristic.magnetic_flux_density_2d", + 0x2aa0_u32, + ), + ( + "org.bluetooth.characteristic.magnetic_flux_density_3d", + 0x2aa1_u32, + ), + ( + "org.bluetooth.characteristic.manufacturer_name_string", + 0x2a29_u32, + ), + ( + "org.bluetooth.characteristic.maximum_recommended_heart_rate", + 0x2a91_u32, + ), + ( + "org.bluetooth.characteristic.measurement_interval", + 0x2a21_u32, + ), + ( + "org.bluetooth.characteristic.model_number_string", + 0x2a24_u32, + ), ("org.bluetooth.characteristic.navigation", 0x2a68_u32), ("org.bluetooth.characteristic.new_alert", 0x2a46_u32), - ("org.bluetooth.characteristic.object_action_control_point", 0x2ac5_u32), + ( + "org.bluetooth.characteristic.object_action_control_point", + 0x2ac5_u32, + ), ("org.bluetooth.characteristic.object_changed", 0x2ac8_u32), - ("org.bluetooth.characteristic.object_first_created", 0x2ac1_u32), + ( + "org.bluetooth.characteristic.object_first_created", + 0x2ac1_u32, + ), ("org.bluetooth.characteristic.object_id", 0x2ac3_u32), - ("org.bluetooth.characteristic.object_last_modified", 0x2ac2_u32), - ("org.bluetooth.characteristic.object_list_control_point", 0x2ac6_u32), - ("org.bluetooth.characteristic.object_list_filter", 0x2ac7_u32), + ( + "org.bluetooth.characteristic.object_last_modified", + 0x2ac2_u32, + ), + ( + "org.bluetooth.characteristic.object_list_control_point", + 0x2ac6_u32, + ), + ( + "org.bluetooth.characteristic.object_list_filter", + 0x2ac7_u32, + ), ("org.bluetooth.characteristic.object_name", 0x2abe_u32), ("org.bluetooth.characteristic.object_properties", 0x2ac4_u32), ("org.bluetooth.characteristic.object_size", 0x2ac0_u32), ("org.bluetooth.characteristic.object_type", 0x2abf_u32), ("org.bluetooth.characteristic.ots_feature", 0x2abd_u32), - ("org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters", 0x2a04_u32), - ("org.bluetooth.characteristic.gap.peripheral_privacy_flag", 0x2a02_u32), - ("org.bluetooth.characteristic.plx_continuous_measurement", 0x2a5f_u32), + ( + "org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters", + 0x2a04_u32, + ), + ( + "org.bluetooth.characteristic.gap.peripheral_privacy_flag", + 0x2a02_u32, + ), + ( + "org.bluetooth.characteristic.plx_continuous_measurement", + 0x2a5f_u32, + ), ("org.bluetooth.characteristic.plx_features", 0x2a60_u32), - ("org.bluetooth.characteristic.plx_spot_check_measurement", 0x2a5e_u32), + ( + "org.bluetooth.characteristic.plx_spot_check_measurement", + 0x2a5e_u32, + ), ("org.bluetooth.characteristic.pnp_id", 0x2a50_u32), - ("org.bluetooth.characteristic.pollen_concentration", 0x2a75_u32), + ( + "org.bluetooth.characteristic.pollen_concentration", + 0x2a75_u32, + ), ("org.bluetooth.characteristic.position_quality", 0x2a69_u32), ("org.bluetooth.characteristic.pressure", 0x2a6d_u32), ("org.bluetooth.characteristic.protocol_mode", 0x2a4e_u32), ("org.bluetooth.characteristic.rainfall", 0x2a78_u32), - ("org.bluetooth.characteristic.gap.reconnection_address", 0x2a03_u32), - ("org.bluetooth.characteristic.record_access_control_point", 0x2a52_u32), - ("org.bluetooth.characteristic.reference_time_information", 0x2a14_u32), + ( + "org.bluetooth.characteristic.gap.reconnection_address", + 0x2a03_u32, + ), + ( + "org.bluetooth.characteristic.record_access_control_point", + 0x2a52_u32, + ), + ( + "org.bluetooth.characteristic.reference_time_information", + 0x2a14_u32, + ), ("org.bluetooth.characteristic.report", 0x2a4d_u32), ("org.bluetooth.characteristic.report_map", 0x2a4b_u32), - ("org.bluetooth.characteristic.resting_heart_rate", 0x2a92_u32), - ("org.bluetooth.characteristic.ringer_control_point", 0x2a40_u32), + ( + "org.bluetooth.characteristic.resting_heart_rate", + 0x2a92_u32, + ), + ( + "org.bluetooth.characteristic.ringer_control_point", + 0x2a40_u32, + ), ("org.bluetooth.characteristic.ringer_setting", 0x2a41_u32), ("org.bluetooth.characteristic.rsc_feature", 0x2a54_u32), ("org.bluetooth.characteristic.rsc_measurement", 0x2a53_u32), ("org.bluetooth.characteristic.sc_control_point", 0x2a55_u32), - ("org.bluetooth.characteristic.scan_interval_window", 0x2a4f_u32), + ( + "org.bluetooth.characteristic.scan_interval_window", + 0x2a4f_u32, + ), ("org.bluetooth.characteristic.scan_refresh", 0x2a31_u32), ("org.bluetooth.characteristic.sensor_location", 0x2a5d_u32), - ("org.bluetooth.characteristic.serial_number_string", 0x2a25_u32), - ("org.bluetooth.characteristic.gatt.service_changed", 0x2a05_u32), - ("org.bluetooth.characteristic.software_revision_string", 0x2a28_u32), - ("org.bluetooth.characteristic.sport_type_for_aerobic_and_anaerobic_thresholds", 0x2a93_u32), - ("org.bluetooth.characteristic.supported_new_alert_category", 0x2a47_u32), - ("org.bluetooth.characteristic.supported_unread_alert_category", 0x2a48_u32), + ( + "org.bluetooth.characteristic.serial_number_string", + 0x2a25_u32, + ), + ( + "org.bluetooth.characteristic.gatt.service_changed", + 0x2a05_u32, + ), + ( + "org.bluetooth.characteristic.software_revision_string", + 0x2a28_u32, + ), + ( + "org.bluetooth.characteristic.sport_type_for_aerobic_and_anaerobic_thresholds", + 0x2a93_u32, + ), + ( + "org.bluetooth.characteristic.supported_new_alert_category", + 0x2a47_u32, + ), + ( + "org.bluetooth.characteristic.supported_unread_alert_category", + 0x2a48_u32, + ), ("org.bluetooth.characteristic.system_id", 0x2a23_u32), ("org.bluetooth.characteristic.tds_control_point", 0x2abc_u32), ("org.bluetooth.characteristic.temperature", 0x2a6e_u32), - ("org.bluetooth.characteristic.temperature_measurement", 0x2a1c_u32), + ( + "org.bluetooth.characteristic.temperature_measurement", + 0x2a1c_u32, + ), ("org.bluetooth.characteristic.temperature_type", 0x2a1d_u32), - ("org.bluetooth.characteristic.three_zone_heart_rate_limits", 0x2a94_u32), + ( + "org.bluetooth.characteristic.three_zone_heart_rate_limits", + 0x2a94_u32, + ), ("org.bluetooth.characteristic.time_accuracy", 0x2a12_u32), ("org.bluetooth.characteristic.time_source", 0x2a13_u32), - ("org.bluetooth.characteristic.time_update_control_point", 0x2a16_u32), + ( + "org.bluetooth.characteristic.time_update_control_point", + 0x2a16_u32, + ), ("org.bluetooth.characteristic.time_update_state", 0x2a17_u32), ("org.bluetooth.characteristic.time_with_dst", 0x2a11_u32), ("org.bluetooth.characteristic.time_zone", 0x2a0e_u32), - ("org.bluetooth.characteristic.true_wind_direction", 0x2a71_u32), + ( + "org.bluetooth.characteristic.true_wind_direction", + 0x2a71_u32, + ), ("org.bluetooth.characteristic.true_wind_speed", 0x2a70_u32), - ("org.bluetooth.characteristic.two_zone_heart_rate_limit", 0x2a95_u32), + ( + "org.bluetooth.characteristic.two_zone_heart_rate_limit", + 0x2a95_u32, + ), ("org.bluetooth.characteristic.tx_power_level", 0x2a07_u32), ("org.bluetooth.characteristic.uncertainty", 0x2ab4_u32), - ("org.bluetooth.characteristic.unread_alert_status", 0x2a45_u32), + ( + "org.bluetooth.characteristic.unread_alert_status", + 0x2a45_u32, + ), ("org.bluetooth.characteristic.uri", 0x2ab6_u32), - ("org.bluetooth.characteristic.user_control_point", 0x2a9f_u32), + ( + "org.bluetooth.characteristic.user_control_point", + 0x2a9f_u32, + ), ("org.bluetooth.characteristic.user_index", 0x2a9a_u32), ("org.bluetooth.characteristic.uv_index", 0x2a76_u32), ("org.bluetooth.characteristic.vo2_max", 0x2a96_u32), - ("org.bluetooth.characteristic.waist_circumference", 0x2a97_u32), + ( + "org.bluetooth.characteristic.waist_circumference", + 0x2a97_u32, + ), ("org.bluetooth.characteristic.weight", 0x2a98_u32), - ("org.bluetooth.characteristic.weight_measurement", 0x2a9d_u32), - ("org.bluetooth.characteristic.weight_scale_feature", 0x2a9e_u32), + ( + "org.bluetooth.characteristic.weight_measurement", + 0x2a9d_u32, + ), + ( + "org.bluetooth.characteristic.weight_scale_feature", + 0x2a9e_u32, + ), ("org.bluetooth.characteristic.wind_chill", 0x2a79_u32), ]; //https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx const BLUETOOTH_ASSIGNED_DESCRIPTORS: &'static [(&'static str, u32)] = &[ - ("org.bluetooth.descriptor.gatt.characteristic_extended_properties", 0x2900_u32), - ("org.bluetooth.descriptor.gatt.characteristic_user_description", 0x2901_u32), - ("org.bluetooth.descriptor.gatt.client_characteristic_configuration", 0x2902_u32), - ("org.bluetooth.descriptor.gatt.server_characteristic_configuration", 0x2903_u32), - ("org.bluetooth.descriptor.gatt.characteristic_presentation_format", 0x2904_u32), - ("org.bluetooth.descriptor.gatt.characteristic_aggregate_format", 0x2905_u32), + ( + "org.bluetooth.descriptor.gatt.characteristic_extended_properties", + 0x2900_u32, + ), + ( + "org.bluetooth.descriptor.gatt.characteristic_user_description", + 0x2901_u32, + ), + ( + "org.bluetooth.descriptor.gatt.client_characteristic_configuration", + 0x2902_u32, + ), + ( + "org.bluetooth.descriptor.gatt.server_characteristic_configuration", + 0x2903_u32, + ), + ( + "org.bluetooth.descriptor.gatt.characteristic_presentation_format", + 0x2904_u32, + ), + ( + "org.bluetooth.descriptor.gatt.characteristic_aggregate_format", + 0x2905_u32, + ), ("org.bluetooth.descriptor.valid_range", 0x2906_u32), - ("org.bluetooth.descriptor.external_report_reference", 0x2907_u32), + ( + "org.bluetooth.descriptor.external_report_reference", + 0x2907_u32, + ), ("org.bluetooth.descriptor.report_reference", 0x2908_u32), ("org.bluetooth.descriptor.number_of_digitals", 0x2909_u32), ("org.bluetooth.descriptor.value_trigger_setting", 0x290a_u32), @@ -268,20 +565,25 @@ const BASE_UUID: &'static str = "-0000-1000-8000-00805f9b34fb"; const SERVICE_PREFIX: &'static str = "org.bluetooth.service"; const CHARACTERISTIC_PREFIX: &'static str = "org.bluetooth.characteristic"; const DESCRIPTOR_PREFIX: &'static str = "org.bluetooth.descriptor"; -const VALID_UUID_REGEX: &'static str = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"; +const VALID_UUID_REGEX: &'static str = + "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"; // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=314 const UUID_ERROR_MESSAGE: &'static str = "It must be a valid UUID alias (e.g. 0x1234), \ UUID (lowercase hex characters e.g. '00001234-0000-1000-8000-00805f9b34fb'),\nor recognized standard name from"; // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=321 -const SERVICES_ERROR_MESSAGE: &'static str = "https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx\ - \ne.g. 'alert_notification'."; +const SERVICES_ERROR_MESSAGE: &'static str = + "https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx\ + \ne.g. 'alert_notification'."; // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=327 -const CHARACTERISTIC_ERROR_MESSAGE: &'static str = "https://developer.bluetooth.org/gatt/characteristics/Pages/\ - CharacteristicsHome.aspx\ne.g. 'aerobic_heart_rate_lower_limit'."; +const CHARACTERISTIC_ERROR_MESSAGE: &'static str = + "https://developer.bluetooth.org/gatt/characteristics/Pages/\ + CharacteristicsHome.aspx\ne.g. 'aerobic_heart_rate_lower_limit'."; // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/bluetooth/BluetoothUUID.cpp?l=333 -const DESCRIPTOR_ERROR_MESSAGE: &'static str = "https://developer.bluetooth.org/gatt/descriptors/Pages/\ - DescriptorsHomePage.aspx\ne.g. 'gatt.characteristic_presentation_format'."; +const DESCRIPTOR_ERROR_MESSAGE: &'static str = + "https://developer.bluetooth.org/gatt/descriptors/Pages/\ + DescriptorsHomePage.aspx\ne.g. 'gatt.characteristic_presentation_format'."; +#[allow(non_snake_case)] impl BluetoothUUID { // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-canonicaluuid pub fn CanonicalUUID(_: &Window, alias: u32) -> UUID { @@ -310,7 +612,11 @@ impl BluetoothUUID { } pub fn characteristic(name: BluetoothCharacteristicUUID) -> Fallible<UUID> { - resolve_uuid_name(name, BLUETOOTH_ASSIGNED_CHARCTERISTICS, CHARACTERISTIC_PREFIX) + resolve_uuid_name( + name, + BLUETOOTH_ASSIGNED_CHARCTERISTICS, + CHARACTERISTIC_PREFIX, + ) } pub fn descriptor(name: BluetoothDescriptorUUID) -> Fallible<UUID> { @@ -333,22 +639,20 @@ fn canonical_uuid(alias: u32) -> UUID { // https://webbluetoothcg.github.io/web-bluetooth/#resolveuuidname fn resolve_uuid_name( - name: StringOrUnsignedLong, - assigned_numbers_table: &'static [(&'static str, u32)], - prefix: &str) - -> Fallible<DOMString> { + name: StringOrUnsignedLong, + assigned_numbers_table: &'static [(&'static str, u32)], + prefix: &str, +) -> Fallible<DOMString> { match name { // Step 1. - StringOrUnsignedLong::UnsignedLong(unsigned32) => { - Ok(canonical_uuid(unsigned32)) - }, + StringOrUnsignedLong::UnsignedLong(unsigned32) => Ok(canonical_uuid(unsigned32)), StringOrUnsignedLong::String(dstring) => { - // Step 2. + // Step 2. let regex = Regex::new(VALID_UUID_REGEX).unwrap(); if regex.is_match(&*dstring) { Ok(dstring) } else { - // Step 3. + // Step 3. let concatenated = format!("{}.{}", prefix, dstring); let is_in_table = assigned_numbers_table.iter().find(|p| p.0 == concatenated); match is_in_table { @@ -356,16 +660,17 @@ fn resolve_uuid_name( None => { let (attribute_type, error_url_message) = match prefix { SERVICE_PREFIX => ("Service", SERVICES_ERROR_MESSAGE), - CHARACTERISTIC_PREFIX => ("Characteristic", CHARACTERISTIC_ERROR_MESSAGE), + CHARACTERISTIC_PREFIX => { + ("Characteristic", CHARACTERISTIC_ERROR_MESSAGE) + }, DESCRIPTOR_PREFIX => ("Descriptor", DESCRIPTOR_ERROR_MESSAGE), _ => unreachable!(), }; // Step 4. - return Err(Type(format!("Invalid {} name : '{}'.\n{} {}", - attribute_type, - dstring, - UUID_ERROR_MESSAGE, - error_url_message))); + return Err(Type(format!( + "Invalid {} name : '{}'.\n{} {}", + attribute_type, dstring, UUID_ERROR_MESSAGE, error_url_message + ))); }, } } diff --git a/components/script/dom/broadcastchannel.rs b/components/script/dom/broadcastchannel.rs new file mode 100644 index 00000000000..701f71b27cd --- /dev/null +++ b/components/script/dom/broadcastchannel.rs @@ -0,0 +1,100 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::structuredclone; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext as SafeJSContext; +use dom_struct::dom_struct; +use js::rust::HandleValue; +use script_traits::BroadcastMsg; +use std::cell::Cell; +use uuid::Uuid; + +#[dom_struct] +pub struct BroadcastChannel { + eventtarget: EventTarget, + name: DOMString, + closed: Cell<bool>, + id: Uuid, +} + +impl BroadcastChannel { + /// <https://html.spec.whatwg.org/multipage/#broadcastchannel> + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope, name: DOMString) -> DomRoot<BroadcastChannel> { + BroadcastChannel::new(global, name) + } + + pub fn new(global: &GlobalScope, name: DOMString) -> DomRoot<BroadcastChannel> { + let channel = reflect_dom_object(Box::new(BroadcastChannel::new_inherited(name)), global); + global.track_broadcast_channel(&*channel); + channel + } + + pub fn new_inherited(name: DOMString) -> BroadcastChannel { + BroadcastChannel { + eventtarget: EventTarget::new_inherited(), + name, + closed: Default::default(), + id: Uuid::new_v4(), + } + } + + /// The unique Id of this channel. + /// Used for filtering out the sender from the local broadcast. + pub fn id(&self) -> &Uuid { + &self.id + } + + /// Is this channel closed? + pub fn closed(&self) -> bool { + self.closed.get() + } +} + +impl BroadcastChannelMethods for BroadcastChannel { + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + fn PostMessage(&self, cx: SafeJSContext, message: HandleValue) -> ErrorResult { + // Step 3, if closed. + if self.closed.get() { + return Err(Error::InvalidState); + } + + // Step 6, StructuredSerialize(message). + let data = structuredclone::write(cx, message, None)?; + + let global = self.global(); + + let msg = BroadcastMsg { + origin: global.origin().immutable().clone(), + channel_name: self.Name().to_string(), + data, + }; + + global.schedule_broadcast(msg, &self.id); + Ok(()) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-name> + fn Name(&self) -> DOMString { + self.name.clone() + } + + /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-close> + fn Close(&self) { + self.closed.set(true); + } + + // <https://html.spec.whatwg.org/multipage/#handler-broadcastchannel-onmessageerror> + event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); + + // <https://html.spec.whatwg.org/multipage/#handler-broadcastchannel-onmessage> + event_handler!(message, GetOnmessage, SetOnmessage); +} diff --git a/components/script/dom/browsingcontext.rs b/components/script/dom/browsingcontext.rs deleted file mode 100644 index 5a1a82ff9b0..00000000000 --- a/components/script/dom/browsingcontext.rs +++ /dev/null @@ -1,602 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject}; -use dom::bindings::error::{Error, throw_dom_exception}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root, RootedReference}; -use dom::bindings::proxyhandler::{fill_property_descriptor, get_property_descriptor}; -use dom::bindings::reflector::{DomObject, Reflector}; -use dom::bindings::trace::JSTraceable; -use dom::bindings::utils::{WindowProxyHandler, get_array_index_from_id, AsVoidPtr}; -use dom::dissimilaroriginwindow::DissimilarOriginWindow; -use dom::element::Element; -use dom::globalscope::GlobalScope; -use dom::window::Window; -use dom_struct::dom_struct; -use js::JSCLASS_IS_GLOBAL; -use js::glue::{CreateWrapperProxyHandler, ProxyTraps, NewWindowProxy}; -use js::glue::{GetProxyPrivate, SetProxyExtra, GetProxyExtra}; -use js::jsapi::{Handle, HandleId, HandleObject, HandleValue}; -use js::jsapi::{JSAutoCompartment, JSContext, JSErrNum, JSFreeOp, JSObject}; -use js::jsapi::{JSPROP_READONLY, JSTracer, JS_DefinePropertyById}; -use js::jsapi::{JS_ForwardGetPropertyTo, JS_ForwardSetPropertyTo}; -use js::jsapi::{JS_GetOwnPropertyDescriptorById, JS_HasPropertyById, JS_HasOwnPropertyById}; -use js::jsapi::{JS_IsExceptionPending, JS_TransplantObject, SetWindowProxy}; -use js::jsapi::{MutableHandle, MutableHandleObject, MutableHandleValue}; -use js::jsapi::{ObjectOpResult, PropertyDescriptor}; -use js::jsval::{UndefinedValue, PrivateValue}; -use js::rust::get_object_class; -use msg::constellation_msg::FrameId; -use msg::constellation_msg::PipelineId; -use std::cell::Cell; -use std::ptr; - -#[dom_struct] -// NOTE: the browsing context for a window is managed in two places: -// here, in script, but also in the constellation. The constellation -// manages the session history, which in script is accessed through -// History objects, messaging the constellation. -pub struct BrowsingContext { - /// The WindowProxy object. - /// Unlike other reflectors, we mutate this field because - /// we have to brain-transplant the reflector when the WindowProxy - /// changes Window. - reflector: Reflector, - - /// The frame id of the browsing context. - /// In the case that this is a nested browsing context, this is the frame id - /// of the container. - frame_id: FrameId, - - /// The pipeline id of the currently active document. - /// May be None, when the currently active document is in another script thread. - /// We do not try to keep the pipeline id for documents in other threads, - /// as this would require the constellation notifying many script threads about - /// the change, which could be expensive. - currently_active: Cell<Option<PipelineId>>, - - /// Has this browsing context been discarded? - discarded: Cell<bool>, - - /// The containing iframe element, if this is a same-origin iframe - frame_element: Option<JS<Element>>, - - /// The parent browsing context, if this is a nested browsing context - parent: Option<JS<BrowsingContext>>, -} - -impl BrowsingContext { - pub fn new_inherited(frame_id: FrameId, - currently_active: Option<PipelineId>, - frame_element: Option<&Element>, - parent: Option<&BrowsingContext>) - -> BrowsingContext - { - BrowsingContext { - reflector: Reflector::new(), - frame_id: frame_id, - currently_active: Cell::new(currently_active), - discarded: Cell::new(false), - frame_element: frame_element.map(JS::from_ref), - parent: parent.map(JS::from_ref), - } - } - - #[allow(unsafe_code)] - pub fn new(window: &Window, - frame_id: FrameId, - frame_element: Option<&Element>, - parent: Option<&BrowsingContext>) - -> Root<BrowsingContext> - { - unsafe { - let WindowProxyHandler(handler) = window.windowproxy_handler(); - assert!(!handler.is_null()); - - let cx = window.get_cx(); - let window_jsobject = window.reflector().get_jsobject(); - assert!(!window_jsobject.get().is_null()); - assert!(((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL) != 0); - let _ac = JSAutoCompartment::new(cx, window_jsobject.get()); - - // Create a new window proxy. - rooted!(in(cx) let window_proxy = NewWindowProxy(cx, window_jsobject, handler)); - assert!(!window_proxy.is_null()); - - // Create a new browsing context. - let current = Some(window.global().pipeline_id()); - let mut browsing_context = box BrowsingContext::new_inherited(frame_id, current, frame_element, parent); - - // The window proxy owns the browsing context. - // When we finalize the window proxy, it drops the browsing context it owns. - SetProxyExtra(window_proxy.get(), 0, &PrivateValue((&*browsing_context).as_void_ptr())); - - // Notify the JS engine about the new window proxy binding. - SetWindowProxy(cx, window_jsobject, window_proxy.handle()); - - // Set the reflector. - debug!("Initializing reflector of {:p} to {:p}.", browsing_context, window_proxy.get()); - browsing_context.reflector.set_jsobject(window_proxy.get()); - Root::from_ref(&*Box::into_raw(browsing_context)) - } - } - - #[allow(unsafe_code)] - pub fn new_dissimilar_origin(global_to_clone_from: &GlobalScope, - frame_id: FrameId, - parent: Option<&BrowsingContext>) - -> Root<BrowsingContext> - { - unsafe { - let handler = CreateWrapperProxyHandler(&XORIGIN_PROXY_HANDLER); - assert!(!handler.is_null()); - - let cx = global_to_clone_from.get_cx(); - - // Create a new browsing context. - let mut browsing_context = box BrowsingContext::new_inherited(frame_id, None, None, parent); - - // Create a new dissimilar-origin window. - let window = DissimilarOriginWindow::new(global_to_clone_from, &*browsing_context); - let window_jsobject = window.reflector().get_jsobject(); - assert!(!window_jsobject.get().is_null()); - assert!(((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL) != 0); - let _ac = JSAutoCompartment::new(cx, window_jsobject.get()); - - // Create a new window proxy. - rooted!(in(cx) let window_proxy = NewWindowProxy(cx, window_jsobject, handler)); - assert!(!window_proxy.is_null()); - - // The window proxy owns the browsing context. - // When we finalize the window proxy, it drops the browsing context it owns. - SetProxyExtra(window_proxy.get(), 0, &PrivateValue((&*browsing_context).as_void_ptr())); - - // Notify the JS engine about the new window proxy binding. - SetWindowProxy(cx, window_jsobject, window_proxy.handle()); - - // Set the reflector. - debug!("Initializing reflector of {:p} to {:p}.", browsing_context, window_proxy.get()); - browsing_context.reflector.set_jsobject(window_proxy.get()); - Root::from_ref(&*Box::into_raw(browsing_context)) - } - } - - pub fn discard(&self) { - self.discarded.set(true); - } - - pub fn is_discarded(&self) -> bool { - self.discarded.get() - } - - pub fn frame_id(&self) -> FrameId { - self.frame_id - } - - pub fn frame_element(&self) -> Option<&Element> { - self.frame_element.r() - } - - pub fn parent(&self) -> Option<&BrowsingContext> { - self.parent.r() - } - - pub fn top(&self) -> &BrowsingContext { - let mut result = self; - while let Some(parent) = result.parent() { - result = parent; - } - result - } - - #[allow(unsafe_code)] - /// Change the Window that this browsing context's WindowProxy resolves to. - // TODO: support setting the window proxy to a dummy value, - // to handle the case when the active document is in another script thread. - fn set_window_proxy(&self, window: &GlobalScope, traps: &ProxyTraps) { - unsafe { - debug!("Setting window proxy of {:p}.", self); - let handler = CreateWrapperProxyHandler(traps); - assert!(!handler.is_null()); - - let cx = window.get_cx(); - let window_jsobject = window.reflector().get_jsobject(); - let old_window_proxy = self.reflector.get_jsobject(); - assert!(!window_jsobject.get().is_null()); - assert!(((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL) != 0); - let _ac = JSAutoCompartment::new(cx, window_jsobject.get()); - - // The old window proxy no longer owns this browsing context. - SetProxyExtra(old_window_proxy.get(), 0, &PrivateValue(ptr::null_mut())); - - // Brain transpant the window proxy. - // We need to do this, because the Window and WindowProxy - // objects need to be in the same compartment. - // JS_TransplantObject does this by copying the contents - // of the old window proxy to the new window proxy, then - // making the old window proxy a cross-compartment wrapper - // pointing to the new window proxy. - rooted!(in(cx) let new_window_proxy = NewWindowProxy(cx, window_jsobject, handler)); - debug!("Transplanting window proxy from {:p} to {:p}.", old_window_proxy.get(), new_window_proxy.get()); - rooted!(in(cx) let new_window_proxy = JS_TransplantObject(cx, old_window_proxy, new_window_proxy.handle())); - debug!("Transplanted window proxy is {:p}.", new_window_proxy.get()); - - // Transfer ownership of this browsing context from the old window proxy to the new one. - SetProxyExtra(new_window_proxy.get(), 0, &PrivateValue(self.as_void_ptr())); - - // Notify the JS engine about the new window proxy binding. - SetWindowProxy(cx, window_jsobject, new_window_proxy.handle()); - - // Update the reflector. - debug!("Setting reflector of {:p} to {:p}.", self, new_window_proxy.get()); - self.reflector.rootable().set(new_window_proxy.get()); - } - } - - pub fn set_currently_active(&self, window: &Window) { - let globalscope = window.upcast(); - self.set_window_proxy(&*globalscope, &PROXY_HANDLER); - self.currently_active.set(Some(globalscope.pipeline_id())); - } - - pub fn unset_currently_active(&self) { - let globalscope = self.global(); - let window = DissimilarOriginWindow::new(&*globalscope, self); - self.set_window_proxy(&*window.upcast(), &XORIGIN_PROXY_HANDLER); - self.currently_active.set(None); - } - - pub fn currently_active(&self) -> Option<PipelineId> { - self.currently_active.get() - } - - pub fn window_proxy(&self) -> *mut JSObject { - let window_proxy = self.reflector.get_jsobject(); - assert!(!window_proxy.get().is_null()); - window_proxy.get() - } -} - -#[allow(unsafe_code)] -unsafe fn GetSubframeWindow(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId) - -> Option<Root<Window>> { - let index = get_array_index_from_id(cx, id); - if let Some(index) = index { - rooted!(in(cx) let target = GetProxyPrivate(*proxy.ptr).to_object()); - let win = root_from_handleobject::<Window>(target.handle()).unwrap(); - let mut found = false; - return win.IndexedGetter(index, &mut found); - } - - None -} - -#[allow(unsafe_code)] -unsafe extern "C" fn getOwnPropertyDescriptor(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - mut desc: MutableHandle<PropertyDescriptor>) - -> bool { - let window = GetSubframeWindow(cx, proxy, id); - if let Some(window) = window { - rooted!(in(cx) let mut val = UndefinedValue()); - window.to_jsval(cx, val.handle_mut()); - desc.value = val.get(); - fill_property_descriptor(desc, proxy.get(), JSPROP_READONLY); - return true; - } - - rooted!(in(cx) let target = GetProxyPrivate(proxy.get()).to_object()); - if !JS_GetOwnPropertyDescriptorById(cx, target.handle(), id, desc) { - return false; - } - - assert!(desc.obj.is_null() || desc.obj == target.get()); - if desc.obj == target.get() { - // FIXME(#11868) Should assign to desc.obj, desc.get() is a copy. - desc.get().obj = proxy.get(); - } - - true -} - -#[allow(unsafe_code)] -unsafe extern "C" fn defineProperty(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - desc: Handle<PropertyDescriptor>, - res: *mut ObjectOpResult) - -> bool { - if get_array_index_from_id(cx, id).is_some() { - // Spec says to Reject whether this is a supported index or not, - // since we have no indexed setter or indexed creator. That means - // throwing in strict mode (FIXME: Bug 828137), doing nothing in - // non-strict mode. - (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t; - return true; - } - - rooted!(in(cx) let target = GetProxyPrivate(*proxy.ptr).to_object()); - JS_DefinePropertyById(cx, target.handle(), id, desc, res) -} - -#[allow(unsafe_code)] -unsafe extern "C" fn has(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - bp: *mut bool) - -> bool { - let window = GetSubframeWindow(cx, proxy, id); - if window.is_some() { - *bp = true; - return true; - } - - rooted!(in(cx) let target = GetProxyPrivate(*proxy.ptr).to_object()); - let mut found = false; - if !JS_HasPropertyById(cx, target.handle(), id, &mut found) { - return false; - } - - *bp = found; - true -} - -#[allow(unsafe_code)] -unsafe extern "C" fn get(cx: *mut JSContext, - proxy: HandleObject, - receiver: HandleValue, - id: HandleId, - vp: MutableHandleValue) - -> bool { - let window = GetSubframeWindow(cx, proxy, id); - if let Some(window) = window { - window.to_jsval(cx, vp); - return true; - } - - rooted!(in(cx) let target = GetProxyPrivate(*proxy.ptr).to_object()); - JS_ForwardGetPropertyTo(cx, target.handle(), id, receiver, vp) -} - -#[allow(unsafe_code)] -unsafe extern "C" fn set(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - v: HandleValue, - receiver: HandleValue, - res: *mut ObjectOpResult) - -> bool { - if get_array_index_from_id(cx, id).is_some() { - // Reject (which means throw if and only if strict) the set. - (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t; - return true; - } - - rooted!(in(cx) let target = GetProxyPrivate(*proxy.ptr).to_object()); - JS_ForwardSetPropertyTo(cx, - target.handle(), - id, - v, - receiver, - res) -} - -#[allow(unsafe_code)] -unsafe extern "C" fn get_prototype_if_ordinary(_: *mut JSContext, - _: HandleObject, - is_ordinary: *mut bool, - _: MutableHandleObject) - -> bool { - // Window's [[GetPrototypeOf]] trap isn't the ordinary definition: - // - // https://html.spec.whatwg.org/multipage/#windowproxy-getprototypeof - // - // We nonetheless can implement it with a static [[Prototype]], because - // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply - // all non-ordinary behavior. - // - // But from a spec point of view, it's the exact same object in both cases -- - // only the observer's changed. So this getPrototypeIfOrdinary trap on the - // non-wrapper object *must* report non-ordinary, even if static [[Prototype]] - // usually means ordinary. - *is_ordinary = false; - return true; -} - -static PROXY_HANDLER: ProxyTraps = ProxyTraps { - enter: None, - getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor), - defineProperty: Some(defineProperty), - ownPropertyKeys: None, - delete_: None, - enumerate: None, - getPrototypeIfOrdinary: Some(get_prototype_if_ordinary), - preventExtensions: None, - isExtensible: None, - has: Some(has), - get: Some(get), - set: Some(set), - call: None, - construct: None, - getPropertyDescriptor: Some(get_property_descriptor), - hasOwn: None, - getOwnEnumerablePropertyKeys: None, - nativeCall: None, - hasInstance: None, - objectClassIs: None, - className: None, - fun_toString: None, - boxedValue_unbox: None, - defaultValue: None, - trace: Some(trace), - finalize: Some(finalize), - objectMoved: None, - isCallable: None, - isConstructor: None, -}; - -#[allow(unsafe_code)] -pub fn new_window_proxy_handler() -> WindowProxyHandler { - unsafe { - WindowProxyHandler(CreateWrapperProxyHandler(&PROXY_HANDLER)) - } -} - -// The proxy traps for cross-origin windows. -// These traps often throw security errors, and only pass on calls to methods -// defined in the DissimilarOriginWindow IDL. - -#[allow(unsafe_code)] -unsafe fn throw_security_error(cx: *mut JSContext) -> bool { - if !JS_IsExceptionPending(cx) { - let global = GlobalScope::from_context(cx); - throw_dom_exception(cx, &*global, Error::Security); - } - false -} - -#[allow(unsafe_code)] -unsafe extern "C" fn has_xorigin(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - bp: *mut bool) - -> bool -{ - rooted!(in(cx) let target = GetProxyPrivate(*proxy.ptr).to_object()); - let mut found = false; - JS_HasOwnPropertyById(cx, target.handle(), id, &mut found); - if found { - *bp = true; - true - } else { - throw_security_error(cx) - } -} - -#[allow(unsafe_code)] -unsafe extern "C" fn get_xorigin(cx: *mut JSContext, - proxy: HandleObject, - receiver: HandleValue, - id: HandleId, - vp: MutableHandleValue) - -> bool -{ - let mut found = false; - has_xorigin(cx, proxy, id, &mut found); - found && get(cx, proxy, receiver, id, vp) -} - -#[allow(unsafe_code)] -unsafe extern "C" fn set_xorigin(cx: *mut JSContext, - _: HandleObject, - _: HandleId, - _: HandleValue, - _: HandleValue, - _: *mut ObjectOpResult) - -> bool -{ - throw_security_error(cx) -} - -#[allow(unsafe_code)] -unsafe extern "C" fn delete_xorigin(cx: *mut JSContext, - _: HandleObject, - _: HandleId, - _: *mut ObjectOpResult) - -> bool -{ - throw_security_error(cx) -} - -#[allow(unsafe_code)] -unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - desc: MutableHandle<PropertyDescriptor>) - -> bool -{ - let mut found = false; - has_xorigin(cx, proxy, id, &mut found); - found && getOwnPropertyDescriptor(cx, proxy, id, desc) -} - -#[allow(unsafe_code)] -unsafe extern "C" fn defineProperty_xorigin(cx: *mut JSContext, - _: HandleObject, - _: HandleId, - _: Handle<PropertyDescriptor>, - _: *mut ObjectOpResult) - -> bool -{ - throw_security_error(cx) -} - -#[allow(unsafe_code)] -unsafe extern "C" fn preventExtensions_xorigin(cx: *mut JSContext, - _: HandleObject, - _: *mut ObjectOpResult) - -> bool -{ - throw_security_error(cx) -} - -static XORIGIN_PROXY_HANDLER: ProxyTraps = ProxyTraps { - enter: None, - getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin), - defineProperty: Some(defineProperty_xorigin), - ownPropertyKeys: None, - delete_: Some(delete_xorigin), - enumerate: None, - getPrototypeIfOrdinary: None, - preventExtensions: Some(preventExtensions_xorigin), - isExtensible: None, - has: Some(has_xorigin), - get: Some(get_xorigin), - set: Some(set_xorigin), - call: None, - construct: None, - getPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin), - hasOwn: Some(has_xorigin), - getOwnEnumerablePropertyKeys: None, - nativeCall: None, - hasInstance: None, - objectClassIs: None, - className: None, - fun_toString: None, - boxedValue_unbox: None, - defaultValue: None, - trace: Some(trace), - finalize: Some(finalize), - objectMoved: None, - isCallable: None, - isConstructor: None, -}; - -// How WindowProxy objects are garbage collected. - -#[allow(unsafe_code)] -unsafe extern fn finalize(_fop: *mut JSFreeOp, obj: *mut JSObject) { - let this = GetProxyExtra(obj, 0).to_private() as *mut BrowsingContext; - if this.is_null() { - // GC during obj creation or after transplanting. - return; - } - let jsobject = (*this).reflector.get_jsobject().get(); - debug!("BrowsingContext finalize: {:p}, with reflector {:p} from {:p}.", this, jsobject, obj); - let _ = Box::from_raw(this); -} - -#[allow(unsafe_code)] -unsafe extern fn trace(trc: *mut JSTracer, obj: *mut JSObject) { - let this = GetProxyExtra(obj, 0).to_private() as *const BrowsingContext; - if this.is_null() { - // GC during obj creation or after transplanting. - return; - } - (*this).trace(trc); -} diff --git a/components/script/dom/canvasgradient.rs b/components/script/dom/canvasgradient.rs index 994582f509b..3671df678af 100644 --- a/components/script/dom/canvasgradient.rs +++ b/components/script/dom/canvasgradient.rs @@ -1,19 +1,20 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use canvas_traits::{CanvasGradientStop, FillOrStrokeStyle, LinearGradientStyle, RadialGradientStyle}; -use cssparser::{Parser, RGBA}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::CanvasGradientBinding::CanvasGradientMethods; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use canvas_traits::canvas::{ + CanvasGradientStop, FillOrStrokeStyle, LinearGradientStyle, RadialGradientStyle, +}; use cssparser::Color as CSSColor; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::CanvasGradientBinding; -use dom::bindings::codegen::Bindings::CanvasGradientBinding::CanvasGradientMethods; -use dom::bindings::error::{Error, ErrorResult}; -use dom::bindings::js::Root; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::globalscope::GlobalScope; +use cssparser::{Parser, ParserInput, RGBA}; use dom_struct::dom_struct; // https://html.spec.whatwg.org/multipage/#canvasgradient @@ -21,10 +22,10 @@ use dom_struct::dom_struct; pub struct CanvasGradient { reflector_: Reflector, style: CanvasGradientStyle, - stops: DOMRefCell<Vec<CanvasGradientStop>>, + stops: DomRefCell<Vec<CanvasGradientStop>>, } -#[derive(JSTraceable, Clone, HeapSizeOf)] +#[derive(Clone, JSTraceable, MallocSizeOf)] pub enum CanvasGradientStyle { Linear(LinearGradientStyle), Radial(RadialGradientStyle), @@ -35,14 +36,12 @@ impl CanvasGradient { CanvasGradient { reflector_: Reflector::new(), style: style, - stops: DOMRefCell::new(Vec::new()), + stops: DomRefCell::new(Vec::new()), } } - pub fn new(global: &GlobalScope, style: CanvasGradientStyle) -> Root<CanvasGradient> { - reflect_dom_object(box CanvasGradient::new_inherited(style), - global, - CanvasGradientBinding::Wrap) + pub fn new(global: &GlobalScope, style: CanvasGradientStyle) -> DomRoot<CanvasGradient> { + reflect_dom_object(Box::new(CanvasGradient::new_inherited(style)), global) } } @@ -53,16 +52,17 @@ impl CanvasGradientMethods for CanvasGradient { return Err(Error::IndexSize); } - let mut parser = Parser::new(&color); + let mut input = ParserInput::new(&color); + let mut parser = Parser::new(&mut input); let color = CSSColor::parse(&mut parser); let color = if parser.is_exhausted() { match color { Ok(CSSColor::RGBA(rgba)) => rgba, Ok(CSSColor::CurrentColor) => RGBA::new(0, 0, 0, 255), - _ => return Err(Error::Syntax) + _ => return Err(Error::Syntax), } } else { - return Err(Error::Syntax) + return Err(Error::Syntax); }; self.stops.borrow_mut().push(CanvasGradientStop { @@ -82,21 +82,25 @@ impl<'a> ToFillOrStrokeStyle for &'a CanvasGradient { let gradient_stops = self.stops.borrow().clone(); match self.style { CanvasGradientStyle::Linear(ref gradient) => { - FillOrStrokeStyle::LinearGradient(LinearGradientStyle::new(gradient.x0, - gradient.y0, - gradient.x1, - gradient.y1, - gradient_stops)) - } + FillOrStrokeStyle::LinearGradient(LinearGradientStyle::new( + gradient.x0, + gradient.y0, + gradient.x1, + gradient.y1, + gradient_stops, + )) + }, CanvasGradientStyle::Radial(ref gradient) => { - FillOrStrokeStyle::RadialGradient(RadialGradientStyle::new(gradient.x0, - gradient.y0, - gradient.r0, - gradient.x1, - gradient.y1, - gradient.r1, - gradient_stops)) - } + FillOrStrokeStyle::RadialGradient(RadialGradientStyle::new( + gradient.x0, + gradient.y0, + gradient.r0, + gradient.x1, + gradient.y1, + gradient.r1, + gradient_stops, + )) + }, } } } diff --git a/components/script/dom/canvaspattern.rs b/components/script/dom/canvaspattern.rs index 6a6d6789918..3557f646ab9 100644 --- a/components/script/dom/canvaspattern.rs +++ b/components/script/dom/canvaspattern.rs @@ -1,33 +1,33 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use canvas_traits::{FillOrStrokeStyle, RepetitionStyle, SurfaceStyle}; -use dom::bindings::codegen::Bindings::CanvasPatternBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::canvasgradient::ToFillOrStrokeStyle; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::canvasgradient::ToFillOrStrokeStyle; +use crate::dom::globalscope::GlobalScope; +use canvas_traits::canvas::{FillOrStrokeStyle, RepetitionStyle, SurfaceStyle}; use dom_struct::dom_struct; -use euclid::size::Size2D; +use euclid::default::Size2D; // https://html.spec.whatwg.org/multipage/#canvaspattern #[dom_struct] pub struct CanvasPattern { reflector_: Reflector, surface_data: Vec<u8>, - surface_size: Size2D<i32>, + surface_size: Size2D<u32>, repeat_x: bool, repeat_y: bool, origin_clean: bool, } impl CanvasPattern { - fn new_inherited(surface_data: Vec<u8>, - surface_size: Size2D<i32>, - repeat: RepetitionStyle, - origin_clean: bool) - -> CanvasPattern { + fn new_inherited( + surface_data: Vec<u8>, + surface_size: Size2D<u32>, + repeat: RepetitionStyle, + origin_clean: bool, + ) -> CanvasPattern { let (x, y) = match repeat { RepetitionStyle::Repeat => (true, true), RepetitionStyle::RepeatX => (true, false), @@ -38,33 +38,41 @@ impl CanvasPattern { CanvasPattern { reflector_: Reflector::new(), surface_data: surface_data, - surface_size: surface_size, + surface_size, repeat_x: x, repeat_y: y, origin_clean: origin_clean, } } - pub fn new(global: &GlobalScope, - surface_data: Vec<u8>, - surface_size: Size2D<i32>, - repeat: RepetitionStyle, - origin_clean: bool) - -> Root<CanvasPattern> { - reflect_dom_object(box CanvasPattern::new_inherited(surface_data, surface_size, - repeat, origin_clean), - global, - CanvasPatternBinding::Wrap) + pub fn new( + global: &GlobalScope, + surface_data: Vec<u8>, + surface_size: Size2D<u32>, + repeat: RepetitionStyle, + origin_clean: bool, + ) -> DomRoot<CanvasPattern> { + reflect_dom_object( + Box::new(CanvasPattern::new_inherited( + surface_data, + surface_size, + repeat, + origin_clean, + )), + global, + ) } pub fn origin_is_clean(&self) -> bool { - self.origin_clean + self.origin_clean } } impl<'a> ToFillOrStrokeStyle for &'a CanvasPattern { fn to_fill_or_stroke_style(self) -> FillOrStrokeStyle { - FillOrStrokeStyle::Surface(SurfaceStyle::new(self.surface_data.clone(), - self.surface_size, - self.repeat_x, - self.repeat_y)) + FillOrStrokeStyle::Surface(SurfaceStyle::new( + self.surface_data.clone(), + self.surface_size, + self.repeat_x, + self.repeat_y, + )) } } diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 432dbe800aa..3ebf8f130d9 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -1,527 +1,157 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use canvas_traits::{Canvas2dMsg, CanvasCommonMsg, CanvasMsg}; -use canvas_traits::{CompositionOrBlending, FillOrStrokeStyle, FillRule}; -use canvas_traits::{LineCapStyle, LineJoinStyle, LinearGradientStyle}; -use canvas_traits::{RadialGradientStyle, RepetitionStyle, byte_swap_and_premultiply}; -use cssparser::{Parser, RGBA}; -use cssparser::Color as CSSColor; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods; -use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding; -use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule; -use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; -use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; -use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods; -use dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::UnionTypes::HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D; -use dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, LayoutJS, Root}; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrStrokeStyle}; -use dom::canvaspattern::CanvasPattern; -use dom::globalscope::GlobalScope; -use dom::htmlcanvaselement::HTMLCanvasElement; -use dom::htmlcanvaselement::utils as canvas_utils; -use dom::htmlimageelement::HTMLImageElement; -use dom::imagedata::ImageData; -use dom::node::{Node, NodeDamage, window_from_node}; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::canvas_state::CanvasState; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasDirection; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextAlign; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextBaseline; +use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; +use crate::dom::bindings::error::{ErrorResult, Fallible}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::canvasgradient::CanvasGradient; +use crate::dom::canvaspattern::CanvasPattern; +use crate::dom::dommatrix::DOMMatrix; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlcanvaselement::HTMLCanvasElement; +use crate::dom::imagedata::ImageData; +use crate::dom::textmetrics::TextMetrics; +use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg}; use dom_struct::dom_struct; -use euclid::matrix2d::Matrix2D; -use euclid::point::Point2D; -use euclid::rect::Rect; -use euclid::size::Size2D; -use ipc_channel::ipc::{self, IpcSender}; -use net_traits::image::base::PixelFormat; -use net_traits::image_cache::ImageResponse; -use num_traits::ToPrimitive; -use script_traits::ScriptMsg as ConstellationMsg; +use euclid::default::{Point2D, Rect, Size2D}; +use ipc_channel::ipc::IpcSender; use servo_url::ServoUrl; -use std::{cmp, fmt}; -use std::cell::Cell; -use std::str::FromStr; -use unpremultiplytable::UNPREMULTIPLY_TABLE; - -#[must_root] -#[derive(JSTraceable, Clone, HeapSizeOf)] -#[allow(dead_code)] -enum CanvasFillOrStrokeStyle { - Color(RGBA), - Gradient(JS<CanvasGradient>), - Pattern(JS<CanvasPattern>), -} +use std::mem; // https://html.spec.whatwg.org/multipage/#canvasrenderingcontext2d #[dom_struct] pub struct CanvasRenderingContext2D { reflector_: Reflector, - #[ignore_heap_size_of = "Defined in ipc-channel"] - ipc_renderer: IpcSender<CanvasMsg>, - canvas: JS<HTMLCanvasElement>, - state: DOMRefCell<CanvasContextState>, - saved_states: DOMRefCell<Vec<CanvasContextState>>, - origin_clean: Cell<bool>, -} - -#[must_root] -#[derive(JSTraceable, Clone, HeapSizeOf)] -struct CanvasContextState { - global_alpha: f64, - global_composition: CompositionOrBlending, - image_smoothing_enabled: bool, - fill_style: CanvasFillOrStrokeStyle, - stroke_style: CanvasFillOrStrokeStyle, - line_width: f64, - line_cap: LineCapStyle, - line_join: LineJoinStyle, - miter_limit: f64, - transform: Matrix2D<f32>, - shadow_offset_x: f64, - shadow_offset_y: f64, - shadow_blur: f64, - shadow_color: RGBA, -} - -impl CanvasContextState { - fn new() -> CanvasContextState { - let black = RGBA::new(0, 0, 0, 255); - CanvasContextState { - global_alpha: 1.0, - global_composition: CompositionOrBlending::default(), - image_smoothing_enabled: true, - fill_style: CanvasFillOrStrokeStyle::Color(black), - stroke_style: CanvasFillOrStrokeStyle::Color(black), - line_width: 1.0, - line_cap: LineCapStyle::Butt, - line_join: LineJoinStyle::Miter, - miter_limit: 10.0, - transform: Matrix2D::identity(), - shadow_offset_x: 0.0, - shadow_offset_y: 0.0, - shadow_blur: 0.0, - shadow_color: RGBA::transparent(), - } - } + /// For rendering contexts created by an HTML canvas element, this is Some, + /// for ones created by a paint worklet, this is None. + canvas: Option<Dom<HTMLCanvasElement>>, + canvas_state: CanvasState, } impl CanvasRenderingContext2D { - fn new_inherited(global: &GlobalScope, - canvas: &HTMLCanvasElement, - size: Size2D<i32>) - -> CanvasRenderingContext2D { - let (sender, receiver) = ipc::channel().unwrap(); - let constellation_chan = global.constellation_chan(); - constellation_chan.send(ConstellationMsg::CreateCanvasPaintThread(size, sender)).unwrap(); - let ipc_renderer = receiver.recv().unwrap(); + pub fn new_inherited( + global: &GlobalScope, + canvas: Option<&HTMLCanvasElement>, + size: Size2D<u32>, + ) -> CanvasRenderingContext2D { CanvasRenderingContext2D { reflector_: Reflector::new(), - ipc_renderer: ipc_renderer, - canvas: JS::from_ref(canvas), - state: DOMRefCell::new(CanvasContextState::new()), - saved_states: DOMRefCell::new(Vec::new()), - origin_clean: Cell::new(true), + canvas: canvas.map(Dom::from_ref), + canvas_state: CanvasState::new( + global, + Size2D::new(size.width as u64, size.height as u64), + ), } } - pub fn new(global: &GlobalScope, - canvas: &HTMLCanvasElement, - size: Size2D<i32>) - -> Root<CanvasRenderingContext2D> { - reflect_dom_object(box CanvasRenderingContext2D::new_inherited(global, canvas, size), - global, - CanvasRenderingContext2DBinding::Wrap) + pub fn new( + global: &GlobalScope, + canvas: &HTMLCanvasElement, + size: Size2D<u32>, + ) -> DomRoot<CanvasRenderingContext2D> { + let boxed = Box::new(CanvasRenderingContext2D::new_inherited( + global, + Some(canvas), + size, + )); + reflect_dom_object(boxed, global) } // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions - pub fn set_bitmap_dimensions(&self, size: Size2D<i32>) { + pub fn set_bitmap_dimensions(&self, size: Size2D<u32>) { self.reset_to_initial_state(); - self.ipc_renderer - .send(CanvasMsg::Common(CanvasCommonMsg::Recreate(size))) + self.canvas_state + .get_ipc_renderer() + .send(CanvasMsg::Recreate( + size.to_u64(), + self.canvas_state.get_canvas_id(), + )) .unwrap(); } // https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state fn reset_to_initial_state(&self) { - self.saved_states.borrow_mut().clear(); - *self.state.borrow_mut() = CanvasContextState::new(); - } - - pub fn ipc_renderer(&self) -> IpcSender<CanvasMsg> { - self.ipc_renderer.clone() - } - - fn mark_as_dirty(&self) { - self.canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); - } - - fn update_transform(&self) { - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetTransform(self.state.borrow().transform))) - .unwrap() - } - - // It is used by DrawImage to calculate the size of the source and destination rectangles based - // on the drawImage call arguments - // source rectangle = area of the original image to be copied - // destination rectangle = area of the destination canvas where the source image is going to be drawn - fn adjust_source_dest_rects(&self, - image_size: Size2D<f64>, - sx: f64, - sy: f64, - sw: f64, - sh: f64, - dx: f64, - dy: f64, - dw: f64, - dh: f64) - -> (Rect<f64>, Rect<f64>) { - let image_rect = Rect::new(Point2D::new(0f64, 0f64), - Size2D::new(image_size.width as f64, image_size.height as f64)); - - // The source rectangle is the rectangle whose corners are the four points (sx, sy), - // (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh). - let source_rect = Rect::new(Point2D::new(sx.min(sx + sw), sy.min(sy + sh)), - Size2D::new(sw.abs(), sh.abs())); - - // When the source rectangle is outside the source image, - // the source rectangle must be clipped to the source image - let source_rect_clipped = source_rect.intersection(&image_rect).unwrap_or(Rect::zero()); - - // Width and height ratios between the non clipped and clipped source rectangles - let width_ratio: f64 = source_rect_clipped.size.width / source_rect.size.width; - let height_ratio: f64 = source_rect_clipped.size.height / source_rect.size.height; - - // When the source rectangle is outside the source image, - // the destination rectangle must be clipped in the same proportion. - let dest_rect_width_scaled: f64 = dw * width_ratio; - let dest_rect_height_scaled: f64 = dh * height_ratio; - - // The destination rectangle is the rectangle whose corners are the four points (dx, dy), - // (dx+dw, dy), (dx+dw, dy+dh), (dx, dy+dh). - let dest_rect = Rect::new(Point2D::new(dx.min(dx + dest_rect_width_scaled), - dy.min(dy + dest_rect_height_scaled)), - Size2D::new(dest_rect_width_scaled.abs(), - dest_rect_height_scaled.abs())); - - let source_rect = Rect::new(Point2D::new(source_rect_clipped.origin.x, - source_rect_clipped.origin.y), - Size2D::new(source_rect_clipped.size.width, - source_rect_clipped.size.height)); - - (source_rect, dest_rect) - } - - // https://html.spec.whatwg.org/multipage/#the-image-argument-is-not-origin-clean - fn is_origin_clean(&self, - image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D) - -> bool { - match image { - HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLCanvasElement(canvas) => { - canvas.origin_is_clean() - } - HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::CanvasRenderingContext2D(image) => - image.origin_is_clean(), - HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLImageElement(image) => - match image.get_url() { - None => true, - Some(url) => { - // TODO(zbarsky): we should check the origin of the image against - // the entry settings object, but for now check it against the canvas' doc. - let node: &Node = &*self.canvas.upcast(); - url.origin() == node.owner_doc().url().origin() - } - } - } + self.canvas_state.reset_to_initial_state(); } - // - // drawImage coordinates explained - // - // Source Image Destination Canvas - // +-------------+ +-------------+ - // | | | | - // |(sx,sy) | |(dx,dy) | - // | +----+ | | +----+ | - // | | | | | | | | - // | | |sh |---->| | |dh | - // | | | | | | | | - // | +----+ | | +----+ | - // | sw | | dw | - // | | | | - // +-------------+ +-------------+ - // - // - // The rectangle (sx, sy, sw, sh) from the source image - // is copied on the rectangle (dx, dy, dh, dw) of the destination canvas - // - // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage - fn draw_image(&self, - image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D, - sx: f64, - sy: f64, - sw: Option<f64>, - sh: Option<f64>, - dx: f64, - dy: f64, - dw: Option<f64>, - dh: Option<f64>) - -> ErrorResult { - let result = match image { - HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLCanvasElement(ref canvas) => { - self.draw_html_canvas_element(&canvas, - sx, sy, sw, sh, - dx, dy, dw, dh) - } - HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::CanvasRenderingContext2D(ref image) => { - self.draw_html_canvas_element(&image.Canvas(), - sx, sy, sw, sh, - dx, dy, dw, dh) - } - HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLImageElement(ref image) => { - // https://html.spec.whatwg.org/multipage/#img-error - // If the image argument is an HTMLImageElement object that is in the broken state, - // then throw an InvalidStateError exception - let (image_data, image_size) = match self.fetch_image_data(image) { - Some((mut data, size)) => { - // Pixels come from cache in BGRA order and drawImage expects RGBA so we - // have to swap the color values - byte_swap_and_premultiply(&mut data); - let size = Size2D::new(size.width as f64, size.height as f64); - (data, size) - }, - None => return Err(Error::InvalidState), - }; - let dw = dw.unwrap_or(image_size.width); - let dh = dh.unwrap_or(image_size.height); - let sw = sw.unwrap_or(image_size.width); - let sh = sh.unwrap_or(image_size.height); - self.draw_image_data(image_data, - image_size, - sx, sy, sw, sh, - dx, dy, dw, dh) - } - }; - - if result.is_ok() && !self.is_origin_clean(image) { - self.set_origin_unclean() - } - result - } - - fn draw_html_canvas_element(&self, - canvas: &HTMLCanvasElement, - sx: f64, - sy: f64, - sw: Option<f64>, - sh: Option<f64>, - dx: f64, - dy: f64, - dw: Option<f64>, - dh: Option<f64>) - -> ErrorResult { - // 1. Check the usability of the image argument - if !canvas.is_valid() { - return Err(Error::InvalidState); - } - - let canvas_size = canvas.get_size(); - let dw = dw.unwrap_or(canvas_size.width as f64); - let dh = dh.unwrap_or(canvas_size.height as f64); - let sw = sw.unwrap_or(canvas_size.width as f64); - let sh = sh.unwrap_or(canvas_size.height as f64); - - let image_size = Size2D::new(canvas_size.width as f64, canvas_size.height as f64); - // 2. Establish the source and destination rectangles - let (source_rect, dest_rect) = self.adjust_source_dest_rects(image_size, - sx, - sy, - sw, - sh, - dx, - dy, - dw, - dh); - - if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) { - return Err(Error::IndexSize); - } - - let smoothing_enabled = self.state.borrow().image_smoothing_enabled; - - if &*self.canvas == canvas { - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::DrawImageSelf( - image_size, dest_rect, source_rect, smoothing_enabled)); - self.ipc_renderer.send(msg).unwrap(); - } else { - let context = match canvas.get_or_init_2d_context() { - Some(context) => context, - None => return Err(Error::InvalidState), - }; - - let (sender, receiver) = ipc::channel().unwrap(); - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::DrawImageInOther( - self.ipc_renderer.clone(), - image_size, - dest_rect, - source_rect, - smoothing_enabled, - sender)); - - let renderer = context.get_ipc_renderer(); - renderer.send(msg).unwrap(); - receiver.recv().unwrap(); - }; - - self.mark_as_dirty(); - Ok(()) - } - - fn draw_image_data(&self, - image_data: Vec<u8>, - image_size: Size2D<f64>, - sx: f64, - sy: f64, - sw: f64, - sh: f64, - dx: f64, - dy: f64, - dw: f64, - dh: f64) - -> ErrorResult { - // Establish the source and destination rectangles - let (source_rect, dest_rect) = self.adjust_source_dest_rects(image_size, - sx, - sy, - sw, - sh, - dx, - dy, - dw, - dh); - - if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) { - return Err(Error::IndexSize); - } - - let smoothing_enabled = self.state.borrow().image_smoothing_enabled; - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::DrawImage(image_data, - image_size, - dest_rect, - source_rect, - smoothing_enabled))) - .unwrap(); - self.mark_as_dirty(); - Ok(()) + pub fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) { + self.canvas_state.set_bitmap_dimensions(size); } - fn fetch_image_data(&self, image_element: &HTMLImageElement) -> Option<(Vec<u8>, Size2D<i32>)> { - let url = match image_element.get_url() { - Some(url) => url, - None => return None, - }; - - let img = match self.request_image_from_cache(url) { - ImageResponse::Loaded(img) => img, - ImageResponse::PlaceholderLoaded(_) | - ImageResponse::None | - ImageResponse::MetadataLoaded(_) => { - return None; - } - }; - - let image_size = Size2D::new(img.width as i32, img.height as i32); - let image_data = match img.format { - PixelFormat::RGBA8 => img.bytes.to_vec(), - PixelFormat::K8 => panic!("K8 color type not supported"), - PixelFormat::RGB8 => panic!("RGB8 color type not supported"), - PixelFormat::KA8 => panic!("KA8 color type not supported"), - }; - - Some((image_data, image_size)) + pub fn mark_as_dirty(&self) { + self.canvas_state + .mark_as_dirty(self.canvas.as_ref().map(|c| &**c)) } - #[inline] - fn request_image_from_cache(&self, url: ServoUrl) -> ImageResponse { - let window = window_from_node(&*self.canvas); - canvas_utils::request_image_from_cache(&window, url) + pub fn take_missing_image_urls(&self) -> Vec<ServoUrl> { + mem::replace( + &mut self.canvas_state.get_missing_image_urls().borrow_mut(), + vec![], + ) } - fn create_drawable_rect(&self, x: f64, y: f64, w: f64, h: f64) -> Option<Rect<f32>> { - if !([x, y, w, h].iter().all(|val| val.is_finite())) { - return None; - } - - if w == 0.0 && h == 0.0 { - return None; - } + pub fn get_canvas_id(&self) -> CanvasId { + self.canvas_state.get_canvas_id() + } - Some(Rect::new(Point2D::new(x as f32, y as f32), - Size2D::new(w as f32, h as f32))) - } - - fn parse_color(&self, string: &str) -> Result<RGBA, ()> { - let mut parser = Parser::new(&string); - let color = CSSColor::parse(&mut parser); - if parser.is_exhausted() { - match color { - Ok(CSSColor::RGBA(rgba)) => Ok(rgba), - Ok(CSSColor::CurrentColor) => { - // TODO: https://github.com/whatwg/html/issues/1099 - // Reconsider how to calculate currentColor in a display:none canvas - - // TODO: will need to check that the context bitmap mode is fixed - // once we implement CanvasProxy - let window = window_from_node(&*self.canvas); - - let style = window.GetComputedStyle(&*self.canvas.upcast(), None); - - let element_not_rendered = - !self.canvas.upcast::<Node>().is_in_doc() || - style.GetPropertyValue(DOMString::from("display")) == "none"; - - if element_not_rendered { - Ok(RGBA::new(0, 0, 0, 255)) - } else { - self.parse_color(&style.GetPropertyValue(DOMString::from("color"))) - } - }, - _ => Err(()) - } - } else { - Err(()) - } + pub fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { + self.canvas_state.send_canvas_2d_msg(msg) } + // TODO: Remove this pub fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg> { - self.ipc_renderer.clone() + self.canvas_state.get_ipc_renderer().clone() } pub fn origin_is_clean(&self) -> bool { - self.origin_clean.get() + self.canvas_state.origin_is_clean() } - fn set_origin_unclean(&self) { - self.origin_clean.set(false) + pub fn get_rect(&self, rect: Rect<u32>) -> Vec<u8> { + let rect = Rect::new( + Point2D::new(rect.origin.x as u64, rect.origin.y as u64), + Size2D::new(rect.size.width as u64, rect.size.height as u64), + ); + self.canvas_state.get_rect( + self.canvas + .as_ref() + .map_or(Size2D::zero(), |c| c.get_size().to_u64()), + rect, + ) } } pub trait LayoutCanvasRenderingContext2DHelpers { #[allow(unsafe_code)] - unsafe fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg>; + unsafe fn get_ipc_renderer(self) -> IpcSender<CanvasMsg>; + fn get_canvas_id(self) -> CanvasId; } -impl LayoutCanvasRenderingContext2DHelpers for LayoutJS<CanvasRenderingContext2D> { +impl LayoutCanvasRenderingContext2DHelpers for LayoutDom<'_, CanvasRenderingContext2D> { + #[allow(unsafe_code)] + unsafe fn get_ipc_renderer(self) -> IpcSender<CanvasMsg> { + (*self.unsafe_get()).canvas_state.get_ipc_renderer().clone() + } + #[allow(unsafe_code)] - unsafe fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg> { - (*self.unsafe_get()).ipc_renderer.clone() + fn get_canvas_id(self) -> CanvasId { + // FIXME(nox): This relies on the fact that CanvasState::get_canvas_id + // does nothing fancy but it would be easier to trust a + // LayoutDom<_>-like type that would wrap the &CanvasState. + unsafe { self.unsafe_get().canvas_state.get_canvas_id() } } } @@ -536,826 +166,501 @@ impl LayoutCanvasRenderingContext2DHelpers for LayoutJS<CanvasRenderingContext2D // FIXME: this behavior should might be generated by some annotattions to idl. impl CanvasRenderingContext2DMethods for CanvasRenderingContext2D { // https://html.spec.whatwg.org/multipage/#dom-context-2d-canvas - fn Canvas(&self) -> Root<HTMLCanvasElement> { - Root::from_ref(&*self.canvas) + fn Canvas(&self) -> DomRoot<HTMLCanvasElement> { + // This method is not called from a paint worklet rendering context, + // so it's OK to panic if self.canvas is None. + DomRoot::from_ref(self.canvas.as_ref().expect("No canvas.")) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-save fn Save(&self) { - self.saved_states.borrow_mut().push(self.state.borrow().clone()); - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SaveContext)).unwrap(); + self.canvas_state.save() } #[allow(unrooted_must_root)] // https://html.spec.whatwg.org/multipage/#dom-context-2d-restore fn Restore(&self) { - let mut saved_states = self.saved_states.borrow_mut(); - if let Some(state) = saved_states.pop() { - self.state.borrow_mut().clone_from(&state); - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::RestoreContext)).unwrap(); - } + self.canvas_state.restore() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-scale fn Scale(&self, x: f64, y: f64) { - if !(x.is_finite() && y.is_finite()) { - return; - } - - let transform = self.state.borrow().transform; - self.state.borrow_mut().transform = transform.pre_scaled(x as f32, y as f32); - self.update_transform() + self.canvas_state.scale(x, y) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-rotate fn Rotate(&self, angle: f64) { - if angle == 0.0 || !angle.is_finite() { - return; - } - - let (sin, cos) = (angle.sin(), angle.cos()); - let transform = self.state.borrow().transform; - self.state.borrow_mut().transform = transform.pre_mul( - &Matrix2D::row_major(cos as f32, sin as f32, - -sin as f32, cos as f32, - 0.0, 0.0)); - self.update_transform() + self.canvas_state.rotate(angle) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-translate fn Translate(&self, x: f64, y: f64) { - if !(x.is_finite() && y.is_finite()) { - return; - } - - let transform = self.state.borrow().transform; - self.state.borrow_mut().transform = transform.pre_translated(x as f32, y as f32); - self.update_transform() + self.canvas_state.translate(x, y) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-transform fn Transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) { - if !(a.is_finite() && b.is_finite() && c.is_finite() && - d.is_finite() && e.is_finite() && f.is_finite()) { - return; - } + self.canvas_state.transform(a, b, c, d, e, f) + } - let transform = self.state.borrow().transform; - self.state.borrow_mut().transform = transform.pre_mul( - &Matrix2D::row_major(a as f32, b as f32, c as f32, d as f32, e as f32, f as f32)); - self.update_transform() + // https://html.spec.whatwg.org/multipage/#dom-context-2d-gettransform + fn GetTransform(&self) -> DomRoot<DOMMatrix> { + self.canvas_state.get_transform(&self.global()) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-settransform fn SetTransform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) { - if !(a.is_finite() && b.is_finite() && c.is_finite() && - d.is_finite() && e.is_finite() && f.is_finite()) { - return; - } - - self.state.borrow_mut().transform = - Matrix2D::row_major(a as f32, b as f32, c as f32, d as f32, e as f32, f as f32); - self.update_transform() + self.canvas_state.set_transform(a, b, c, d, e, f) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-resettransform fn ResetTransform(&self) { - self.state.borrow_mut().transform = Matrix2D::identity(); - self.update_transform() + self.canvas_state.reset_transform() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha fn GlobalAlpha(&self) -> f64 { - let state = self.state.borrow(); - state.global_alpha + self.canvas_state.global_alpha() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha fn SetGlobalAlpha(&self, alpha: f64) { - if !alpha.is_finite() || alpha > 1.0 || alpha < 0.0 { - return; - } - - self.state.borrow_mut().global_alpha = alpha; - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetGlobalAlpha(alpha as f32))) - .unwrap() + self.canvas_state.set_global_alpha(alpha) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation fn GlobalCompositeOperation(&self) -> DOMString { - let state = self.state.borrow(); - match state.global_composition { - CompositionOrBlending::Composition(op) => DOMString::from(op.to_str()), - CompositionOrBlending::Blending(op) => DOMString::from(op.to_str()), - } + self.canvas_state.global_composite_operation() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation fn SetGlobalCompositeOperation(&self, op_str: DOMString) { - if let Ok(op) = CompositionOrBlending::from_str(&op_str) { - self.state.borrow_mut().global_composition = op; - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetGlobalComposition(op))) - .unwrap() - } + self.canvas_state.set_global_composite_operation(op_str) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-fillrect fn FillRect(&self, x: f64, y: f64, width: f64, height: f64) { - if let Some(rect) = self.create_drawable_rect(x, y, width, height) { - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::FillRect(rect))).unwrap(); - self.mark_as_dirty(); - } + self.canvas_state.fill_rect(x, y, width, height); + self.mark_as_dirty(); } // https://html.spec.whatwg.org/multipage/#dom-context-2d-clearrect fn ClearRect(&self, x: f64, y: f64, width: f64, height: f64) { - if let Some(rect) = self.create_drawable_rect(x, y, width, height) { - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::ClearRect(rect))) - .unwrap(); - self.mark_as_dirty(); - } + self.canvas_state.clear_rect(x, y, width, height); + self.mark_as_dirty(); } // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokerect fn StrokeRect(&self, x: f64, y: f64, width: f64, height: f64) { - if let Some(rect) = self.create_drawable_rect(x, y, width, height) { - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::StrokeRect(rect))) - .unwrap(); - self.mark_as_dirty(); - } + self.canvas_state.stroke_rect(x, y, width, height); + self.mark_as_dirty(); } // https://html.spec.whatwg.org/multipage/#dom-context-2d-beginpath fn BeginPath(&self) { - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::BeginPath)).unwrap(); + self.canvas_state.begin_path() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath fn ClosePath(&self) { - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::ClosePath)).unwrap(); + self.canvas_state.close_path() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill - fn Fill(&self, _: CanvasFillRule) { - // TODO: Process fill rule - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::Fill)).unwrap(); + fn Fill(&self, fill_rule: CanvasFillRule) { + self.canvas_state.fill(fill_rule); self.mark_as_dirty(); } // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke fn Stroke(&self) { - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::Stroke)).unwrap(); + self.canvas_state.stroke(); self.mark_as_dirty(); } // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip - fn Clip(&self, _: CanvasFillRule) { - // TODO: Process fill rule - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::Clip)).unwrap(); + fn Clip(&self, fill_rule: CanvasFillRule) { + self.canvas_state.clip(fill_rule) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool { - let fill_rule = match fill_rule { - CanvasFillRule::Nonzero => FillRule::Nonzero, - CanvasFillRule::Evenodd => FillRule::Evenodd, - }; - let (sender, receiver) = ipc::channel::<bool>().unwrap(); - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::IsPointInPath(x, y, fill_rule, sender))) - .unwrap(); - receiver.recv().unwrap() + self.canvas_state + .is_point_in_path(&self.global(), x, y, fill_rule) } - // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage - fn DrawImage(&self, - image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D, - dx: f64, - dy: f64) - -> ErrorResult { - if !(dx.is_finite() && dy.is_finite()) { - return Ok(()); - } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext + fn FillText(&self, text: DOMString, x: f64, y: f64, max_width: Option<f64>) { + self.canvas_state + .fill_text(self.canvas.as_ref().map(|c| &**c), text, x, y, max_width); + self.mark_as_dirty(); + } - self.draw_image(image, 0f64, 0f64, None, None, dx, dy, None, None) + // https://html.spec.whatwg.org/multipage/#textmetrics + fn MeasureText(&self, text: DOMString) -> DomRoot<TextMetrics> { + self.canvas_state.measure_text(&self.global(), text) } - // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage - fn DrawImage_(&self, - image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D, - dx: f64, - dy: f64, - dw: f64, - dh: f64) - -> ErrorResult { - if !(dx.is_finite() && dy.is_finite() && dw.is_finite() && dh.is_finite()) { - return Ok(()); - } + // https://html.spec.whatwg.org/multipage/#dom-context-2d-font + fn Font(&self) -> DOMString { + self.canvas_state.font() + } - self.draw_image(image, 0f64, 0f64, None, None, dx, dy, Some(dw), Some(dh)) + // https://html.spec.whatwg.org/multipage/#dom-context-2d-font + fn SetFont(&self, value: DOMString) { + self.canvas_state + .set_font(self.canvas.as_ref().map(|c| &**c), value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + fn TextAlign(&self) -> CanvasTextAlign { + self.canvas_state.text_align() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + fn SetTextAlign(&self, value: CanvasTextAlign) { + self.canvas_state.set_text_align(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textbaseline + fn TextBaseline(&self) -> CanvasTextBaseline { + self.canvas_state.text_baseline() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textbaseline + fn SetTextBaseline(&self, value: CanvasTextBaseline) { + self.canvas_state.set_text_baseline(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + fn Direction(&self) -> CanvasDirection { + self.canvas_state.direction() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + fn SetDirection(&self, value: CanvasDirection) { + self.canvas_state.set_direction(value) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage - fn DrawImage__(&self, - image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D, - sx: f64, - sy: f64, - sw: f64, - sh: f64, - dx: f64, - dy: f64, - dw: f64, - dh: f64) - -> ErrorResult { - if !(sx.is_finite() && sy.is_finite() && sw.is_finite() && sh.is_finite() && - dx.is_finite() && dy.is_finite() && dw.is_finite() && dh.is_finite()) { - return Ok(()); - } + fn DrawImage(&self, image: CanvasImageSource, dx: f64, dy: f64) -> ErrorResult { + self.canvas_state + .draw_image(self.canvas.as_ref().map(|c| &**c), image, dx, dy) + } - self.draw_image(image, - sx, - sy, - Some(sw), - Some(sh), - dx, - dy, - Some(dw), - Some(dh)) + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + fn DrawImage_( + &self, + image: CanvasImageSource, + dx: f64, + dy: f64, + dw: f64, + dh: f64, + ) -> ErrorResult { + self.canvas_state + .draw_image_(self.canvas.as_ref().map(|c| &**c), image, dx, dy, dw, dh) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + fn DrawImage__( + &self, + image: CanvasImageSource, + sx: f64, + sy: f64, + sw: f64, + sh: f64, + dx: f64, + dy: f64, + dw: f64, + dh: f64, + ) -> ErrorResult { + self.canvas_state.draw_image__( + self.canvas.as_ref().map(|c| &**c), + image, + sx, + sy, + sw, + sh, + dx, + dy, + dw, + dh, + ) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto fn MoveTo(&self, x: f64, y: f64) { - if !(x.is_finite() && y.is_finite()) { - return; - } - - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::MoveTo(Point2D::new(x as f32, y as f32))); - self.ipc_renderer.send(msg).unwrap(); + self.canvas_state.move_to(x, y) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto fn LineTo(&self, x: f64, y: f64) { - if !(x.is_finite() && y.is_finite()) { - return; - } - - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::LineTo(Point2D::new(x as f32, y as f32))); - self.ipc_renderer.send(msg).unwrap(); + self.canvas_state.line_to(x, y) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-rect fn Rect(&self, x: f64, y: f64, width: f64, height: f64) { - if [x, y, width, height].iter().all(|val| val.is_finite()) { - let rect = Rect::new(Point2D::new(x as f32, y as f32), - Size2D::new(width as f32, height as f32)); - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::Rect(rect)); - self.ipc_renderer.send(msg).unwrap(); - } + self.canvas_state.rect(x, y, width, height) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) { - if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) { - return; - } - - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::QuadraticCurveTo(Point2D::new(cpx as f32, - cpy as f32), - Point2D::new(x as f32, - y as f32))); - self.ipc_renderer.send(msg).unwrap(); + self.canvas_state.quadratic_curve_to(cpx, cpy, x, y) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto fn BezierCurveTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) { - if !(cp1x.is_finite() && cp1y.is_finite() && cp2x.is_finite() && cp2y.is_finite() && - x.is_finite() && y.is_finite()) { - return; - } - - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::BezierCurveTo(Point2D::new(cp1x as f32, - cp1y as f32), - Point2D::new(cp2x as f32, - cp2y as f32), - Point2D::new(x as f32, y as f32))); - self.ipc_renderer.send(msg).unwrap(); + self.canvas_state + .bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-arc fn Arc(&self, x: f64, y: f64, r: f64, start: f64, end: f64, ccw: bool) -> ErrorResult { - if !([x, y, r, start, end].iter().all(|x| x.is_finite())) { - return Ok(()); - } - - if r < 0.0 { - return Err(Error::IndexSize); - } - - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::Arc(Point2D::new(x as f32, y as f32), - r as f32, - start as f32, - end as f32, - ccw)); - - self.ipc_renderer.send(msg).unwrap(); - Ok(()) + self.canvas_state.arc(x, y, r, start, end, ccw) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto fn ArcTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, r: f64) -> ErrorResult { - if !([cp1x, cp1y, cp2x, cp2y, r].iter().all(|x| x.is_finite())) { - return Ok(()); - } - if r < 0.0 { - return Err(Error::IndexSize); - } + self.canvas_state.arc_to(cp1x, cp1y, cp2x, cp2y, r) + } - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::ArcTo(Point2D::new(cp1x as f32, cp1y as f32), - Point2D::new(cp2x as f32, cp2y as f32), - r as f32)); - self.ipc_renderer.send(msg).unwrap(); - Ok(()) + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse + fn Ellipse( + &self, + x: f64, + y: f64, + rx: f64, + ry: f64, + rotation: f64, + start: f64, + end: f64, + ccw: bool, + ) -> ErrorResult { + self.canvas_state + .ellipse(x, y, rx, ry, rotation, start, end, ccw) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled fn ImageSmoothingEnabled(&self) -> bool { - let state = self.state.borrow(); - state.image_smoothing_enabled + self.canvas_state.image_smoothing_enabled() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled fn SetImageSmoothingEnabled(&self, value: bool) { - self.state.borrow_mut().image_smoothing_enabled = value; + self.canvas_state.set_image_smoothing_enabled(value) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle fn StrokeStyle(&self) -> StringOrCanvasGradientOrCanvasPattern { - match self.state.borrow().stroke_style { - CanvasFillOrStrokeStyle::Color(ref rgba) => { - let mut result = String::new(); - serialize(rgba, &mut result).unwrap(); - StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result)) - }, - CanvasFillOrStrokeStyle::Gradient(ref gradient) => { - StringOrCanvasGradientOrCanvasPattern::CanvasGradient(Root::from_ref(&*gradient)) - }, - CanvasFillOrStrokeStyle::Pattern(ref pattern) => { - StringOrCanvasGradientOrCanvasPattern::CanvasPattern(Root::from_ref(&*pattern)) - } - } + self.canvas_state.stroke_style() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle fn SetStrokeStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) { - match value { - StringOrCanvasGradientOrCanvasPattern::String(string) => { - if let Ok(rgba) = self.parse_color(&string) { - self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba); - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetStrokeStyle( - FillOrStrokeStyle::Color(rgba)))) - .unwrap(); - } - }, - StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => { - self.state.borrow_mut().stroke_style = - CanvasFillOrStrokeStyle::Gradient(JS::from_ref(&*gradient)); - let msg = CanvasMsg::Canvas2d( - Canvas2dMsg::SetStrokeStyle(gradient.to_fill_or_stroke_style())); - self.ipc_renderer.send(msg).unwrap(); - }, - StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => { - self.state.borrow_mut().stroke_style = - CanvasFillOrStrokeStyle::Pattern(JS::from_ref(&*pattern)); - let msg = CanvasMsg::Canvas2d( - Canvas2dMsg::SetStrokeStyle(pattern.to_fill_or_stroke_style())); - self.ipc_renderer.send(msg).unwrap(); - if !pattern.origin_is_clean() { - self.set_origin_unclean(); - } - } - } + self.canvas_state + .set_stroke_style(self.canvas.as_ref().map(|c| &**c), value) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle fn FillStyle(&self) -> StringOrCanvasGradientOrCanvasPattern { - match self.state.borrow().fill_style { - CanvasFillOrStrokeStyle::Color(ref rgba) => { - let mut result = String::new(); - serialize(rgba, &mut result).unwrap(); - StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result)) - }, - CanvasFillOrStrokeStyle::Gradient(ref gradient) => { - StringOrCanvasGradientOrCanvasPattern::CanvasGradient(Root::from_ref(&*gradient)) - }, - CanvasFillOrStrokeStyle::Pattern(ref pattern) => { - StringOrCanvasGradientOrCanvasPattern::CanvasPattern(Root::from_ref(&*pattern)) - } - } + self.canvas_state.fill_style() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle fn SetFillStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) { - match value { - StringOrCanvasGradientOrCanvasPattern::String(string) => { - if let Ok(rgba) = self.parse_color(&string) { - self.state.borrow_mut().fill_style = CanvasFillOrStrokeStyle::Color(rgba); - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetFillStyle( - FillOrStrokeStyle::Color(rgba)))) - .unwrap() - } - } - StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => { - self.state.borrow_mut().fill_style = - CanvasFillOrStrokeStyle::Gradient(JS::from_ref(&*gradient)); - let msg = CanvasMsg::Canvas2d( - Canvas2dMsg::SetFillStyle(gradient.to_fill_or_stroke_style())); - self.ipc_renderer.send(msg).unwrap(); - } - StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => { - self.state.borrow_mut().fill_style = - CanvasFillOrStrokeStyle::Pattern(JS::from_ref(&*pattern)); - let msg = CanvasMsg::Canvas2d( - Canvas2dMsg::SetFillStyle(pattern.to_fill_or_stroke_style())); - self.ipc_renderer.send(msg).unwrap(); - if !pattern.origin_is_clean() { - self.set_origin_unclean(); - } - } - } + self.canvas_state + .set_fill_style(self.canvas.as_ref().map(|c| &**c), value) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata - fn CreateImageData(&self, sw: Finite<f64>, sh: Finite<f64>) -> Fallible<Root<ImageData>> { - if *sw == 0.0 || *sh == 0.0 { - return Err(Error::IndexSize); - } - - let sw = cmp::max(1, sw.abs().to_u32().unwrap()); - let sh = cmp::max(1, sh.abs().to_u32().unwrap()); - ImageData::new(&self.global(), sw, sh, None) + fn CreateImageData(&self, sw: i32, sh: i32) -> Fallible<DomRoot<ImageData>> { + self.canvas_state.create_image_data(&self.global(), sw, sh) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata - fn CreateImageData_(&self, imagedata: &ImageData) -> Fallible<Root<ImageData>> { - ImageData::new(&self.global(), - imagedata.Width(), - imagedata.Height(), - None) + fn CreateImageData_(&self, imagedata: &ImageData) -> Fallible<DomRoot<ImageData>> { + self.canvas_state + .create_image_data_(&self.global(), imagedata) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-getimagedata - fn GetImageData(&self, - sx: Finite<f64>, - sy: Finite<f64>, - sw: Finite<f64>, - sh: Finite<f64>) - -> Fallible<Root<ImageData>> { - if !self.origin_is_clean() { - return Err(Error::Security) - } - - let mut sx = *sx; - let mut sy = *sy; - let mut sw = *sw; - let mut sh = *sh; - - if sw == 0.0 || sh == 0.0 { - return Err(Error::IndexSize); - } - - if sw < 0.0 { - sw = -sw; - sx -= sw; - } - if sh < 0.0 { - sh = -sh; - sy -= sh; - } - - let sh = cmp::max(1, sh.to_u32().unwrap()); - let sw = cmp::max(1, sw.to_u32().unwrap()); - - let (sender, receiver) = ipc::channel::<Vec<u8>>().unwrap(); - let dest_rect = Rect::new(Point2D::new(sx.to_i32().unwrap(), sy.to_i32().unwrap()), - Size2D::new(sw as i32, sh as i32)); - let canvas_size = self.canvas.get_size(); - let canvas_size = Size2D::new(canvas_size.width as f64, canvas_size.height as f64); - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender))) - .unwrap(); - let mut data = receiver.recv().unwrap(); - - // Un-premultiply alpha - for chunk in data.chunks_mut(4) { - let alpha = chunk[3] as usize; - chunk[0] = UNPREMULTIPLY_TABLE[256 * alpha + chunk[0] as usize]; - chunk[1] = UNPREMULTIPLY_TABLE[256 * alpha + chunk[1] as usize]; - chunk[2] = UNPREMULTIPLY_TABLE[256 * alpha + chunk[2] as usize]; - } - - ImageData::new(&self.global(), sw, sh, Some(data)) + fn GetImageData(&self, sx: i32, sy: i32, sw: i32, sh: i32) -> Fallible<DomRoot<ImageData>> { + self.canvas_state.get_image_data( + self.canvas + .as_ref() + .map_or(Size2D::zero(), |c| c.get_size().to_u64()), + &self.global(), + sx, + sy, + sw, + sh, + ) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata - fn PutImageData(&self, imagedata: &ImageData, dx: Finite<f64>, dy: Finite<f64>) { - self.PutImageData_(imagedata, - dx, - dy, - Finite::wrap(0f64), - Finite::wrap(0f64), - Finite::wrap(imagedata.Width() as f64), - Finite::wrap(imagedata.Height() as f64)) + fn PutImageData(&self, imagedata: &ImageData, dx: i32, dy: i32) { + self.canvas_state.put_image_data( + self.canvas + .as_ref() + .map_or(Size2D::zero(), |c| c.get_size().to_u64()), + imagedata, + dx, + dy, + ) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata - fn PutImageData_(&self, - imagedata: &ImageData, - dx: Finite<f64>, - dy: Finite<f64>, - dirty_x: Finite<f64>, - dirty_y: Finite<f64>, - dirty_width: Finite<f64>, - dirty_height: Finite<f64>) { - let data = imagedata.get_data_array(); - let offset = Point2D::new(*dx, *dy); - let image_data_size = Size2D::new(imagedata.Width() as f64, imagedata.Height() as f64); - - let dirty_rect = Rect::new(Point2D::new(*dirty_x, *dirty_y), - Size2D::new(*dirty_width, *dirty_height)); - let msg = CanvasMsg::Canvas2d(Canvas2dMsg::PutImageData(data, - offset, - image_data_size, - dirty_rect)); - self.ipc_renderer.send(msg).unwrap(); + #[allow(unsafe_code)] + fn PutImageData_( + &self, + imagedata: &ImageData, + dx: i32, + dy: i32, + dirty_x: i32, + dirty_y: i32, + dirty_width: i32, + dirty_height: i32, + ) { + self.canvas_state.put_image_data_( + self.canvas + .as_ref() + .map_or(Size2D::zero(), |c| c.get_size().to_u64()), + imagedata, + dx, + dy, + dirty_x, + dirty_y, + dirty_width, + dirty_height, + ); self.mark_as_dirty(); } // https://html.spec.whatwg.org/multipage/#dom-context-2d-createlineargradient - fn CreateLinearGradient(&self, - x0: Finite<f64>, - y0: Finite<f64>, - x1: Finite<f64>, - y1: Finite<f64>) - -> Root<CanvasGradient> { - CanvasGradient::new(&self.global(), - CanvasGradientStyle::Linear(LinearGradientStyle::new(*x0, - *y0, - *x1, - *y1, - Vec::new()))) + fn CreateLinearGradient( + &self, + x0: Finite<f64>, + y0: Finite<f64>, + x1: Finite<f64>, + y1: Finite<f64>, + ) -> DomRoot<CanvasGradient> { + self.canvas_state + .create_linear_gradient(&self.global(), x0, y0, x1, y1) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-createradialgradient - fn CreateRadialGradient(&self, - x0: Finite<f64>, - y0: Finite<f64>, - r0: Finite<f64>, - x1: Finite<f64>, - y1: Finite<f64>, - r1: Finite<f64>) - -> Fallible<Root<CanvasGradient>> { - if *r0 < 0. || *r1 < 0. { - return Err(Error::IndexSize); - } - - Ok(CanvasGradient::new(&self.global(), - CanvasGradientStyle::Radial(RadialGradientStyle::new(*x0, - *y0, - *r0, - *x1, - *y1, - *r1, - Vec::new())))) + fn CreateRadialGradient( + &self, + x0: Finite<f64>, + y0: Finite<f64>, + r0: Finite<f64>, + x1: Finite<f64>, + y1: Finite<f64>, + r1: Finite<f64>, + ) -> Fallible<DomRoot<CanvasGradient>> { + self.canvas_state + .create_radial_gradient(&self.global(), x0, y0, r0, x1, y1, r1) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern - fn CreatePattern(&self, - image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D, - mut repetition: DOMString) - -> Fallible<Root<CanvasPattern>> { - let (image_data, image_size) = match image { - HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLImageElement(ref image) => { - // https://html.spec.whatwg.org/multipage/#img-error - // If the image argument is an HTMLImageElement object that is in the broken state, - // then throw an InvalidStateError exception - try!(self.fetch_image_data(image).ok_or(Error::InvalidState)) - }, - HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::HTMLCanvasElement(ref canvas) => { - let _ = canvas.get_or_init_2d_context(); - - try!(canvas.fetch_all_data().ok_or(Error::InvalidState)) - }, - HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::CanvasRenderingContext2D(ref context) => { - let canvas = context.Canvas(); - let _ = canvas.get_or_init_2d_context(); - - try!(canvas.fetch_all_data().ok_or(Error::InvalidState)) - } - }; - - if repetition.is_empty() { - repetition.push_str("repeat"); - } - - if let Ok(rep) = RepetitionStyle::from_str(&repetition) { - Ok(CanvasPattern::new(&self.global(), - image_data, - image_size, - rep, - self.is_origin_clean(image))) - } else { - Err(Error::Syntax) - } + fn CreatePattern( + &self, + image: CanvasImageSource, + repetition: DOMString, + ) -> Fallible<Option<DomRoot<CanvasPattern>>> { + self.canvas_state + .create_pattern(&self.global(), image, repetition) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth fn LineWidth(&self) -> f64 { - let state = self.state.borrow(); - state.line_width + self.canvas_state.line_width() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth fn SetLineWidth(&self, width: f64) { - if !width.is_finite() || width <= 0.0 { - return; - } - - self.state.borrow_mut().line_width = width; - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetLineWidth(width as f32))) - .unwrap() + self.canvas_state.set_line_width(width) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap fn LineCap(&self) -> CanvasLineCap { - match self.state.borrow().line_cap { - LineCapStyle::Butt => CanvasLineCap::Butt, - LineCapStyle::Round => CanvasLineCap::Round, - LineCapStyle::Square => CanvasLineCap::Square, - } + self.canvas_state.line_cap() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap fn SetLineCap(&self, cap: CanvasLineCap) { - let line_cap = match cap { - CanvasLineCap::Butt => LineCapStyle::Butt, - CanvasLineCap::Round => LineCapStyle::Round, - CanvasLineCap::Square => LineCapStyle::Square, - }; - self.state.borrow_mut().line_cap = line_cap; - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetLineCap(line_cap))).unwrap(); + self.canvas_state.set_line_cap(cap) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin fn LineJoin(&self) -> CanvasLineJoin { - match self.state.borrow().line_join { - LineJoinStyle::Round => CanvasLineJoin::Round, - LineJoinStyle::Bevel => CanvasLineJoin::Bevel, - LineJoinStyle::Miter => CanvasLineJoin::Miter, - } + self.canvas_state.line_join() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin fn SetLineJoin(&self, join: CanvasLineJoin) { - let line_join = match join { - CanvasLineJoin::Round => LineJoinStyle::Round, - CanvasLineJoin::Bevel => LineJoinStyle::Bevel, - CanvasLineJoin::Miter => LineJoinStyle::Miter, - }; - self.state.borrow_mut().line_join = line_join; - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetLineJoin(line_join))).unwrap(); + self.canvas_state.set_line_join(join) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit fn MiterLimit(&self) -> f64 { - let state = self.state.borrow(); - state.miter_limit + self.canvas_state.miter_limit() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit fn SetMiterLimit(&self, limit: f64) { - if !limit.is_finite() || limit <= 0.0 { - return; - } - - self.state.borrow_mut().miter_limit = limit; - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetMiterLimit(limit as f32))) - .unwrap() + self.canvas_state.set_miter_limit(limit) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx fn ShadowOffsetX(&self) -> f64 { - self.state.borrow().shadow_offset_x + self.canvas_state.shadow_offset_x() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx fn SetShadowOffsetX(&self, value: f64) { - if !value.is_finite() || value == self.state.borrow().shadow_offset_x { - return; - } - self.state.borrow_mut().shadow_offset_x = value; - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetShadowOffsetX(value))).unwrap() + self.canvas_state.set_shadow_offset_x(value) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety fn ShadowOffsetY(&self) -> f64 { - self.state.borrow().shadow_offset_y + self.canvas_state.shadow_offset_y() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety fn SetShadowOffsetY(&self, value: f64) { - if !value.is_finite() || value == self.state.borrow().shadow_offset_y { - return; - } - self.state.borrow_mut().shadow_offset_y = value; - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetShadowOffsetY(value))).unwrap() + self.canvas_state.set_shadow_offset_y(value) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur fn ShadowBlur(&self) -> f64 { - self.state.borrow().shadow_blur + self.canvas_state.shadow_blur() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur fn SetShadowBlur(&self, value: f64) { - if !value.is_finite() || value < 0f64 || value == self.state.borrow().shadow_blur { - return; - } - self.state.borrow_mut().shadow_blur = value; - self.ipc_renderer.send(CanvasMsg::Canvas2d(Canvas2dMsg::SetShadowBlur(value))).unwrap() + self.canvas_state.set_shadow_blur(value) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor fn ShadowColor(&self) -> DOMString { - let mut result = String::new(); - serialize(&self.state.borrow().shadow_color, &mut result).unwrap(); - DOMString::from(result) + self.canvas_state.shadow_color() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor fn SetShadowColor(&self, value: DOMString) { - if let Ok(color) = parse_color(&value) { - self.state.borrow_mut().shadow_color = color; - self.ipc_renderer - .send(CanvasMsg::Canvas2d(Canvas2dMsg::SetShadowColor(color))) - .unwrap() - } + self.canvas_state.set_shadow_color(value) } } impl Drop for CanvasRenderingContext2D { fn drop(&mut self) { - if let Err(err) = self.ipc_renderer.send(CanvasMsg::Common(CanvasCommonMsg::Close)) { + 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) } } } - -pub fn parse_color(string: &str) -> Result<RGBA, ()> { - let mut parser = Parser::new(&string); - match CSSColor::parse(&mut parser) { - Ok(CSSColor::RGBA(rgba)) => { - if parser.is_exhausted() { - Ok(rgba) - } else { - Err(()) - } - }, - _ => Err(()), - } -} - -// Used by drawImage to determine if a source or destination rectangle is valid -// Origin coordinates and size cannot be negative. Size has to be greater than zero -fn is_rect_valid(rect: Rect<f64>) -> bool { - rect.size.width > 0.0 && rect.size.height > 0.0 -} - -// https://html.spec.whatwg.org/multipage/#serialisation-of-a-colour -fn serialize<W>(color: &RGBA, dest: &mut W) -> fmt::Result - where W: fmt::Write -{ - let red = color.red; - let green = color.green; - let blue = color.blue; - - if color.alpha == 255 { - write!(dest, - "#{:x}{:x}{:x}{:x}{:x}{:x}", - red >> 4, - red & 0xF, - green >> 4, - green & 0xF, - blue >> 4, - blue & 0xF) - } else { - write!(dest, "rgba({}, {}, {}, {})", red, green, blue, color.alpha_f32()) - } -} diff --git a/components/script/dom/cdatasection.rs b/components/script/dom/cdatasection.rs new file mode 100644 index 00000000000..7e026bb7673 --- /dev/null +++ b/components/script/dom/cdatasection.rs @@ -0,0 +1,30 @@ +/* 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 crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::node::Node; +use crate::dom::text::Text; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct CDATASection { + text: Text, +} + +impl CDATASection { + fn new_inherited(text: DOMString, document: &Document) -> CDATASection { + CDATASection { + text: Text::new_inherited(text, document), + } + } + + pub fn new(text: DOMString, document: &Document) -> DomRoot<CDATASection> { + Node::reflect_node( + Box::new(CDATASection::new_inherited(text, document)), + document, + ) + } +} diff --git a/components/script/dom/channelmergernode.rs b/components/script/dom/channelmergernode.rs new file mode 100644 index 00000000000..7e8bccd885e --- /dev/null +++ b/components/script/dom/channelmergernode.rs @@ -0,0 +1,81 @@ +/* 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 crate::dom::audionode::{AudioNode, MAX_CHANNEL_COUNT}; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::Bindings::ChannelMergerNodeBinding::ChannelMergerOptions; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::channel_node::ChannelNodeOptions; +use servo_media::audio::node::AudioNodeInit; + +#[dom_struct] +pub struct ChannelMergerNode { + node: AudioNode, +} + +impl ChannelMergerNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + _: &Window, + context: &BaseAudioContext, + options: &ChannelMergerOptions, + ) -> Fallible<ChannelMergerNode> { + let node_options = options.parent.unwrap_or( + 1, + ChannelCountMode::Explicit, + ChannelInterpretation::Speakers, + ); + + if node_options.count != 1 || node_options.mode != ChannelCountMode::Explicit { + return Err(Error::InvalidState); + } + + if options.numberOfInputs < 1 || options.numberOfInputs > MAX_CHANNEL_COUNT { + return Err(Error::IndexSize); + } + + let node = AudioNode::new_inherited( + AudioNodeInit::ChannelMergerNode(options.into()), + context, + node_options, + options.numberOfInputs, // inputs + 1, // outputs + )?; + Ok(ChannelMergerNode { node }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &ChannelMergerOptions, + ) -> Fallible<DomRoot<ChannelMergerNode>> { + let node = ChannelMergerNode::new_inherited(window, context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &ChannelMergerOptions, + ) -> Fallible<DomRoot<ChannelMergerNode>> { + ChannelMergerNode::new(window, context, options) + } +} + +impl<'a> From<&'a ChannelMergerOptions> for ChannelNodeOptions { + fn from(options: &'a ChannelMergerOptions) -> Self { + Self { + channels: options.numberOfInputs as u8, + } + } +} diff --git a/components/script/dom/channelsplitternode.rs b/components/script/dom/channelsplitternode.rs new file mode 100644 index 00000000000..3efd3b2465c --- /dev/null +++ b/components/script/dom/channelsplitternode.rs @@ -0,0 +1,75 @@ +/* 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 crate::dom::audionode::{AudioNode, MAX_CHANNEL_COUNT}; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::Bindings::ChannelSplitterNodeBinding::ChannelSplitterOptions; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::node::AudioNodeInit; + +#[dom_struct] +pub struct ChannelSplitterNode { + node: AudioNode, +} + +impl ChannelSplitterNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + _: &Window, + context: &BaseAudioContext, + options: &ChannelSplitterOptions, + ) -> Fallible<ChannelSplitterNode> { + if options.numberOfOutputs < 1 || options.numberOfOutputs > MAX_CHANNEL_COUNT { + return Err(Error::IndexSize); + } + + let node_options = options.parent.unwrap_or( + options.numberOfOutputs, + ChannelCountMode::Explicit, + ChannelInterpretation::Discrete, + ); + + if node_options.count != options.numberOfOutputs || + node_options.mode != ChannelCountMode::Explicit || + node_options.interpretation != ChannelInterpretation::Discrete + { + return Err(Error::InvalidState); + } + + let node = AudioNode::new_inherited( + AudioNodeInit::ChannelSplitterNode, + context, + node_options, + 1, // inputs + options.numberOfOutputs, // outputs + )?; + Ok(ChannelSplitterNode { node }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &ChannelSplitterOptions, + ) -> Fallible<DomRoot<ChannelSplitterNode>> { + let node = ChannelSplitterNode::new_inherited(window, context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &ChannelSplitterOptions, + ) -> Fallible<DomRoot<ChannelSplitterNode>> { + ChannelSplitterNode::new(window, context, options) + } +} diff --git a/components/script/dom/characterdata.rs b/components/script/dom/characterdata.rs index be4fc4d0ac1..f8c5acb5a8f 100644 --- a/components/script/dom/characterdata.rs +++ b/components/script/dom/characterdata.rs @@ -1,54 +1,59 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! DOM bindings for `CharacterData`. -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; -use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; -use dom::bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId}; -use dom::bindings::codegen::UnionTypes::NodeOrString; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, Root}; -use dom::bindings::str::DOMString; -use dom::comment::Comment; -use dom::document::Document; -use dom::element::Element; -use dom::node::{Node, NodeDamage}; -use dom::processinginstruction::ProcessingInstruction; -use dom::text::Text; +use crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; +use crate::dom::bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId, TextTypeId}; +use crate::dom::bindings::codegen::UnionTypes::NodeOrString; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cdatasection::CDATASection; +use crate::dom::comment::Comment; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::mutationobserver::{Mutation, MutationObserver}; +use crate::dom::node::{ChildrenMutation, Node, NodeDamage}; +use crate::dom::processinginstruction::ProcessingInstruction; +use crate::dom::text::Text; +use crate::dom::virtualmethods::vtable_for; use dom_struct::dom_struct; -use servo_config::opts; -use std::cell::Ref; // https://dom.spec.whatwg.org/#characterdata #[dom_struct] pub struct CharacterData { node: Node, - data: DOMRefCell<DOMString>, + data: DomRefCell<DOMString>, } impl CharacterData { pub fn new_inherited(data: DOMString, document: &Document) -> CharacterData { CharacterData { node: Node::new_inherited(document), - data: DOMRefCell::new(data), + data: DomRefCell::new(data), } } - pub fn clone_with_data(&self, data: DOMString, document: &Document) -> Root<Node> { + pub fn clone_with_data(&self, data: DOMString, document: &Document) -> DomRoot<Node> { match self.upcast::<Node>().type_id() { NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => { - Root::upcast(Comment::new(data, &document)) - } + DomRoot::upcast(Comment::new(data, &document)) + }, NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => { let pi = self.downcast::<ProcessingInstruction>().unwrap(); - Root::upcast(ProcessingInstruction::new(pi.Target(), data, &document)) + DomRoot::upcast(ProcessingInstruction::new(pi.Target(), data, &document)) + }, + NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::CDATASection)) => { + DomRoot::upcast(CDATASection::new(data, &document)) }, - NodeTypeId::CharacterData(CharacterDataTypeId::Text) => { - Root::upcast(Text::new(data, &document)) + NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => { + DomRoot::upcast(Text::new(data, &document)) }, _ => unreachable!(), } @@ -61,6 +66,7 @@ impl CharacterData { #[inline] pub fn append_data(&self, data: &str) { + self.queue_mutation_record(); self.data.borrow_mut().push_str(data); self.content_changed(); } @@ -68,6 +74,24 @@ impl CharacterData { fn content_changed(&self) { let node = self.upcast::<Node>(); node.dirty(NodeDamage::OtherNodeDamage); + + // If this is a Text node, we might need to re-parse (say, if our parent + // is a <style> element.) We don't need to if this is a Comment or + // ProcessingInstruction. + if self.is::<Text>() { + if let Some(parent_node) = node.GetParentNode() { + let mutation = ChildrenMutation::ChangeText; + vtable_for(&parent_node).children_changed(&mutation); + } + } + } + + // Queue a MutationObserver record before changing the content. + fn queue_mutation_record(&self) { + let mutation = Mutation::CharacterData { + old_value: self.data.borrow().clone(), + }; + MutationObserver::queue_a_mutation_record(self.upcast::<Node>(), mutation); } } @@ -79,12 +103,14 @@ impl CharacterDataMethods for CharacterData { // https://dom.spec.whatwg.org/#dom-characterdata-data fn SetData(&self, data: DOMString) { + self.queue_mutation_record(); let old_length = self.Length(); let new_length = data.encode_utf16().count() as u32; *self.data.borrow_mut() = data; self.content_changed(); let node = self.upcast::<Node>(); - node.ranges().replace_code_units(node, 0, old_length, new_length); + node.ranges() + .replace_code_units(node, 0, old_length, new_length); } // https://dom.spec.whatwg.org/#dom-characterdata-length @@ -94,11 +120,16 @@ impl CharacterDataMethods for CharacterData { // https://dom.spec.whatwg.org/#dom-characterdata-substringdata fn SubstringData(&self, offset: u32, count: u32) -> Fallible<DOMString> { + let replace_surrogates = self + .upcast::<Node>() + .owner_doc() + .window() + .replace_surrogates(); let data = self.data.borrow(); // Step 1. let mut substring = String::new(); let remaining; - match split_at_utf16_code_unit_offset(&data, offset) { + match split_at_utf16_code_unit_offset(&data, offset, replace_surrogates) { Ok((_, astral, s)) => { // As if we had split the UTF-16 surrogate pair in half // and then transcoded that to UTF-8 lossily, @@ -107,11 +138,11 @@ impl CharacterDataMethods for CharacterData { substring = substring + "\u{FFFD}"; } remaining = s; - } + }, // Step 2. Err(()) => return Err(Error::IndexSize), } - match split_at_utf16_code_unit_offset(remaining, count) { + match split_at_utf16_code_unit_offset(remaining, count, replace_surrogates) { // Steps 3. Err(()) => substring = substring + remaining, // Steps 4. @@ -123,7 +154,7 @@ impl CharacterDataMethods for CharacterData { if astral.is_some() { substring = substring + "\u{FFFD}"; } - } + }, }; Ok(DOMString::from(substring)) } @@ -148,11 +179,16 @@ impl CharacterDataMethods for CharacterData { fn ReplaceData(&self, offset: u32, count: u32, arg: DOMString) -> ErrorResult { let mut new_data; { + let replace_surrogates = self + .upcast::<Node>() + .owner_doc() + .window() + .replace_surrogates(); let data = self.data.borrow(); let prefix; let replacement_before; let remaining; - match split_at_utf16_code_unit_offset(&data, offset) { + match split_at_utf16_code_unit_offset(&data, offset, replace_surrogates) { Ok((p, astral, r)) => { prefix = p; // As if we had split the UTF-16 surrogate pair in half @@ -160,34 +196,37 @@ impl CharacterDataMethods for CharacterData { // since our DOMString is currently strict UTF-8. replacement_before = if astral.is_some() { "\u{FFFD}" } else { "" }; remaining = r; - } + }, // Step 2. Err(()) => return Err(Error::IndexSize), }; let replacement_after; let suffix; - match split_at_utf16_code_unit_offset(remaining, count) { + match split_at_utf16_code_unit_offset(remaining, count, replace_surrogates) { // Steps 3. Err(()) => { replacement_after = ""; suffix = ""; - } + }, Ok((_, astral, s)) => { // As if we had split the UTF-16 surrogate pair in half // and then transcoded that to UTF-8 lossily, // since our DOMString is currently strict UTF-8. replacement_after = if astral.is_some() { "\u{FFFD}" } else { "" }; suffix = s; - } + }, }; // Step 4: Mutation observers. + self.queue_mutation_record(); + // Step 5 to 7. new_data = String::with_capacity( prefix.len() + - replacement_before.len() + - arg.len() + - replacement_after.len() + - suffix.len()); + replacement_before.len() + + arg.len() + + replacement_after.len() + + suffix.len(), + ); new_data.push_str(prefix); new_data.push_str(replacement_before); new_data.push_str(&arg); @@ -198,8 +237,8 @@ impl CharacterDataMethods for CharacterData { self.content_changed(); // Steps 8-11. let node = self.upcast::<Node>(); - node.ranges().replace_code_units( - node, offset, count, arg.encode_utf16().count() as u32); + node.ranges() + .replace_code_units(node, offset, count, arg.encode_utf16().count() as u32); Ok(()) } @@ -225,26 +264,31 @@ impl CharacterDataMethods for CharacterData { } // https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-previouselementsibling - fn GetPreviousElementSibling(&self) -> Option<Root<Element>> { - self.upcast::<Node>().preceding_siblings().filter_map(Root::downcast).next() + fn GetPreviousElementSibling(&self) -> Option<DomRoot<Element>> { + self.upcast::<Node>() + .preceding_siblings() + .filter_map(DomRoot::downcast) + .next() } // https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-nextelementsibling - fn GetNextElementSibling(&self) -> Option<Root<Element>> { - self.upcast::<Node>().following_siblings().filter_map(Root::downcast).next() + fn GetNextElementSibling(&self) -> Option<DomRoot<Element>> { + self.upcast::<Node>() + .following_siblings() + .filter_map(DomRoot::downcast) + .next() } } -#[allow(unsafe_code)] -pub trait LayoutCharacterDataHelpers { - unsafe fn data_for_layout(&self) -> &str; +pub trait LayoutCharacterDataHelpers<'dom> { + fn data_for_layout(self) -> &'dom str; } -#[allow(unsafe_code)] -impl LayoutCharacterDataHelpers for LayoutJS<CharacterData> { +impl<'dom> LayoutCharacterDataHelpers<'dom> for LayoutDom<'dom, CharacterData> { + #[allow(unsafe_code)] #[inline] - unsafe fn data_for_layout(&self) -> &str { - &(*self.unsafe_get()).data.borrow_for_layout() + fn data_for_layout(self) -> &'dom str { + unsafe { self.unsafe_get().data.borrow_for_layout() } } } @@ -268,7 +312,11 @@ impl LayoutCharacterDataHelpers for LayoutJS<CharacterData> { /// Note that the third variant is only ever returned when the `-Z replace-surrogates` /// command-line option is specified. /// When it *would* be returned but the option is *not* specified, this function panics. -fn split_at_utf16_code_unit_offset(s: &str, offset: u32) -> Result<(&str, Option<char>, &str), ()> { +fn split_at_utf16_code_unit_offset( + s: &str, + offset: u32, + replace_surrogates: bool, +) -> Result<(&str, Option<char>, &str), ()> { let mut code_units = 0; for (i, c) in s.char_indices() { if code_units == offset { @@ -278,15 +326,17 @@ fn split_at_utf16_code_unit_offset(s: &str, offset: u32) -> Result<(&str, Option code_units += 1; if c > '\u{FFFF}' { if code_units == offset { - if opts::get().replace_surrogates { - debug_assert!(c.len_utf8() == 4); - return Ok((&s[..i], Some(c), &s[i + c.len_utf8()..])) + if replace_surrogates { + debug_assert_eq!(c.len_utf8(), 4); + return Ok((&s[..i], Some(c), &s[i + c.len_utf8()..])); } - panic!("\n\n\ - Would split a surrogate pair in CharacterData API.\n\ - If you see this in real content, please comment with the URL\n\ - on https://github.com/servo/servo/issues/6873\n\ - \n"); + panic!( + "\n\n\ + Would split a surrogate pair in CharacterData API.\n\ + If you see this in real content, please comment with the URL\n\ + on https://github.com/servo/servo/issues/6873\n\ + \n" + ); } code_units += 1; } diff --git a/components/script/dom/client.rs b/components/script/dom/client.rs index 7910ffc8dfa..2768ee7c9ba 100644 --- a/components/script/dom/client.rs +++ b/components/script/dom/client.rs @@ -1,14 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::ClientBinding::{ClientMethods, Wrap}; -use dom::bindings::codegen::Bindings::ClientBinding::FrameType; -use dom::bindings::js::{Root, MutNullableJS}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::serviceworker::ServiceWorker; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::ClientBinding::ClientMethods; +use crate::dom::bindings::codegen::Bindings::ClientBinding::FrameType; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::serviceworker::ServiceWorker; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_url::ServoUrl; use std::default::Default; @@ -17,11 +17,11 @@ use uuid::Uuid; #[dom_struct] pub struct Client { reflector_: Reflector, - active_worker: MutNullableJS<ServiceWorker>, + active_worker: MutNullableDom<ServiceWorker>, url: ServoUrl, frame_type: FrameType, - #[ignore_heap_size_of = "Defined in uuid"] - id: Uuid + #[ignore_malloc_size_of = "Defined in uuid"] + id: Uuid, } impl Client { @@ -31,24 +31,23 @@ impl Client { active_worker: Default::default(), url: url, frame_type: FrameType::None, - id: Uuid::new_v4() + id: Uuid::new_v4(), } } - pub fn new(window: &Window) -> Root<Client> { - reflect_dom_object(box Client::new_inherited(window.get_url()), - window, - Wrap) + pub fn new(window: &Window) -> DomRoot<Client> { + reflect_dom_object(Box::new(Client::new_inherited(window.get_url())), window) } pub fn creation_url(&self) -> ServoUrl { self.url.clone() } - pub fn get_controller(&self) -> Option<Root<ServiceWorker>> { + pub fn get_controller(&self) -> Option<DomRoot<ServiceWorker>> { self.active_worker.get() } + #[allow(dead_code)] pub fn set_controller(&self, worker: &ServiceWorker) { self.active_worker.set(Some(worker)); } diff --git a/components/script/dom/closeevent.rs b/components/script/dom/closeevent.rs index f052c0eff44..abc579d8df9 100644 --- a/components/script/dom/closeevent.rs +++ b/components/script/dom/closeevent.rs @@ -1,17 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CloseEventBinding; -use dom::bindings::codegen::Bindings::CloseEventBinding::CloseEventMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::CloseEventBinding; +use crate::dom::bindings::codegen::Bindings::CloseEventBinding::CloseEventMethods; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; use servo_atoms::Atom; @@ -23,6 +23,7 @@ pub struct CloseEvent { reason: DOMString, } +#[allow(non_snake_case)] impl CloseEvent { pub fn new_inherited(was_clean: bool, code: u16, reason: DOMString) -> CloseEvent { CloseEvent { @@ -33,46 +34,41 @@ impl CloseEvent { } } - pub fn new_uninitialized(global: &GlobalScope) -> Root<CloseEvent> { - reflect_dom_object(box CloseEvent::new_inherited(false, 0, DOMString::new()), - global, - CloseEventBinding::Wrap) - } - - pub fn new(global: &GlobalScope, - type_: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable, - wasClean: bool, - code: u16, - reason: DOMString) - -> Root<CloseEvent> { - let event = box CloseEvent::new_inherited(wasClean, code, reason); - let ev = reflect_dom_object(event, global, CloseEventBinding::Wrap); + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + wasClean: bool, + code: u16, + reason: DOMString, + ) -> DomRoot<CloseEvent> { + let event = Box::new(CloseEvent::new_inherited(wasClean, code, reason)); + let ev = reflect_dom_object(event, global); { let event = ev.upcast::<Event>(); - event.init_event(type_, - bool::from(bubbles), - bool::from(cancelable)); + event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); } ev } - pub fn Constructor(global: &GlobalScope, - type_: DOMString, - init: &CloseEventBinding::CloseEventInit) - -> Fallible<Root<CloseEvent>> { + pub fn Constructor( + global: &GlobalScope, + type_: DOMString, + init: &CloseEventBinding::CloseEventInit, + ) -> Fallible<DomRoot<CloseEvent>> { let bubbles = EventBubbles::from(init.parent.bubbles); let cancelable = EventCancelable::from(init.parent.cancelable); - Ok(CloseEvent::new(global, - Atom::from(type_), - bubbles, - cancelable, - init.wasClean, - init.code, - init.reason.clone())) + Ok(CloseEvent::new( + global, + Atom::from(type_), + bubbles, + cancelable, + init.wasClean, + init.code, + init.reason.clone(), + )) } - } impl CloseEventMethods for CloseEvent { diff --git a/components/script/dom/comment.rs b/components/script/dom/comment.rs index ad512132698..d87f4d77094 100644 --- a/components/script/dom/comment.rs +++ b/components/script/dom/comment.rs @@ -1,16 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CommentBinding; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::characterdata::CharacterData; -use dom::document::Document; -use dom::node::Node; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::characterdata::CharacterData; +use crate::dom::document::Document; +use crate::dom::node::Node; +use crate::dom::window::Window; use dom_struct::dom_struct; /// An HTML comment. @@ -26,13 +25,12 @@ impl Comment { } } - pub fn new(text: DOMString, document: &Document) -> Root<Comment> { - Node::reflect_node(box Comment::new_inherited(text, document), - document, - CommentBinding::Wrap) + pub fn new(text: DOMString, document: &Document) -> DomRoot<Comment> { + Node::reflect_node(Box::new(Comment::new_inherited(text, document)), document) } - pub fn Constructor(window: &Window, data: DOMString) -> Fallible<Root<Comment>> { + #[allow(non_snake_case)] + pub fn Constructor(window: &Window, data: DOMString) -> Fallible<DomRoot<Comment>> { let document = window.Document(); Ok(Comment::new(data, &document)) } diff --git a/components/script/dom/compositionevent.rs b/components/script/dom/compositionevent.rs new file mode 100644 index 00000000000..1cdc1cd8dcc --- /dev/null +++ b/components/script/dom/compositionevent.rs @@ -0,0 +1,89 @@ +/* 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 crate::dom::bindings::codegen::Bindings::CompositionEventBinding::{ + self, CompositionEventMethods, +}; +use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventBinding::UIEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::uievent::UIEvent; +use crate::dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct CompositionEvent { + uievent: UIEvent, + data: DOMString, +} + +impl CompositionEvent { + pub fn new_inherited() -> CompositionEvent { + CompositionEvent { + uievent: UIEvent::new_inherited(), + data: DOMString::new(), + } + } + + pub fn new_uninitialized(window: &Window) -> DomRoot<CompositionEvent> { + reflect_dom_object(Box::new(CompositionEvent::new_inherited()), window) + } + + pub fn new( + window: &Window, + type_: DOMString, + can_bubble: bool, + cancelable: bool, + view: Option<&Window>, + detail: i32, + data: DOMString, + ) -> DomRoot<CompositionEvent> { + let ev = reflect_dom_object( + Box::new(CompositionEvent { + uievent: UIEvent::new_inherited(), + data: data, + }), + window, + ); + ev.uievent + .InitUIEvent(type_, can_bubble, cancelable, view, detail); + ev + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &CompositionEventBinding::CompositionEventInit, + ) -> Fallible<DomRoot<CompositionEvent>> { + let event = CompositionEvent::new( + window, + type_, + init.parent.parent.bubbles, + init.parent.parent.cancelable, + init.parent.view.as_deref(), + init.parent.detail, + init.data.clone(), + ); + Ok(event) + } + + pub fn data(&self) -> &str { + &*self.data + } +} + +impl CompositionEventMethods for CompositionEvent { + // https://w3c.github.io/uievents/#dom-compositionevent-data + fn Data(&self) -> DOMString { + self.data.clone() + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.uievent.IsTrusted() + } +} diff --git a/components/script/dom/console.rs b/components/script/dom/console.rs index d9df56ebef0..93171beb547 100644 --- a/components/script/dom/console.rs +++ b/components/script/dom/console.rs @@ -1,110 +1,140 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::workerglobalscope::WorkerGlobalScope; use devtools_traits::{ConsoleMessage, LogLevel, ScriptToDevtoolsControlMsg}; -use dom::bindings::inheritance::Castable; -use dom::bindings::str::DOMString; -use dom::globalscope::GlobalScope; -use dom::workerglobalscope::WorkerGlobalScope; +use js::rust::describe_scripted_caller; +use std::io; // https://developer.mozilla.org/en-US/docs/Web/API/Console pub struct Console(()); impl Console { + #[allow(unsafe_code)] fn send_to_devtools(global: &GlobalScope, level: LogLevel, message: DOMString) { if let Some(chan) = global.devtools_chan() { - let console_message = prepare_message(level, message); - let worker_id = global.downcast::<WorkerGlobalScope>().map(|worker| { - worker.get_worker_id() - }); + let caller = unsafe { describe_scripted_caller(*global.get_cx()) }.unwrap_or_default(); + let console_message = ConsoleMessage { + message: String::from(message), + logLevel: level, + filename: caller.filename, + lineNumber: caller.line as usize, + columnNumber: caller.col as usize, + }; + let worker_id = global + .downcast::<WorkerGlobalScope>() + .map(|worker| worker.get_worker_id()); let devtools_message = ScriptToDevtoolsControlMsg::ConsoleAPI( global.pipeline_id(), console_message, - worker_id); + worker_id, + ); chan.send(devtools_message).unwrap(); } } } +// In order to avoid interleaving the stdout output of the Console API methods +// with stderr that could be in use on other threads, we lock stderr until +// we're finished with stdout. Since the stderr lock is reentrant, there is +// no risk of deadlock if the callback ends up trying to write to stderr for +// any reason. +fn with_stderr_lock<F>(f: F) +where + F: FnOnce(), +{ + let stderr = io::stderr(); + let _handle = stderr.lock(); + f() +} + +fn console_messages(global: &GlobalScope, messages: &[DOMString], level: LogLevel) { + console_message(global, DOMString::from(messages.join(" ")), level) +} + +fn console_message(global: &GlobalScope, message: DOMString, level: LogLevel) { + with_stderr_lock(move || { + let prefix = global.current_group_label().unwrap_or_default(); + let message = DOMString::from(format!("{}{}", prefix, message)); + println!("{}", message); + Console::send_to_devtools(global, level, message); + }) +} + +#[allow(non_snake_case)] impl Console { // https://developer.mozilla.org/en-US/docs/Web/API/Console/log pub fn Log(global: &GlobalScope, messages: Vec<DOMString>) { - for message in messages { - println!("{}", message); - Self::send_to_devtools(global, LogLevel::Log, message); - } + console_messages(global, &messages, LogLevel::Log) + } + + // https://developer.mozilla.org/en-US/docs/Web/API/Console/clear + pub fn Clear(global: &GlobalScope) { + let message: Vec<DOMString> = Vec::new(); + console_messages(global, &message, LogLevel::Clear) } // https://developer.mozilla.org/en-US/docs/Web/API/Console pub fn Debug(global: &GlobalScope, messages: Vec<DOMString>) { - for message in messages { - println!("{}", message); - Self::send_to_devtools(global, LogLevel::Debug, message); - } + console_messages(global, &messages, LogLevel::Debug) } // https://developer.mozilla.org/en-US/docs/Web/API/Console/info pub fn Info(global: &GlobalScope, messages: Vec<DOMString>) { - for message in messages { - println!("{}", message); - Self::send_to_devtools(global, LogLevel::Info, message); - } + console_messages(global, &messages, LogLevel::Info) } // https://developer.mozilla.org/en-US/docs/Web/API/Console/warn pub fn Warn(global: &GlobalScope, messages: Vec<DOMString>) { - for message in messages { - println!("{}", message); - Self::send_to_devtools(global, LogLevel::Warn, message); - } + console_messages(global, &messages, LogLevel::Warn) } // https://developer.mozilla.org/en-US/docs/Web/API/Console/error pub fn Error(global: &GlobalScope, messages: Vec<DOMString>) { - for message in messages { - println!("{}", message); - Self::send_to_devtools(global, LogLevel::Error, message); - } + console_messages(global, &messages, LogLevel::Error) } // https://developer.mozilla.org/en-US/docs/Web/API/Console/assert pub fn Assert(global: &GlobalScope, condition: bool, message: Option<DOMString>) { if !condition { let message = message.unwrap_or_else(|| DOMString::from("no message")); - println!("Assertion failed: {}", message); - Self::send_to_devtools(global, LogLevel::Error, message); - } + let message = DOMString::from(format!("Assertion failed: {}", message)); + console_message(global, message, LogLevel::Error) + }; } // https://developer.mozilla.org/en-US/docs/Web/API/Console/time pub fn Time(global: &GlobalScope, label: DOMString) { if let Ok(()) = global.time(label.clone()) { let message = DOMString::from(format!("{}: timer started", label)); - println!("{}", message); - Self::send_to_devtools(global, LogLevel::Log, message); + console_message(global, message, LogLevel::Log); } } // https://developer.mozilla.org/en-US/docs/Web/API/Console/timeEnd pub fn TimeEnd(global: &GlobalScope, label: DOMString) { if let Ok(delta) = global.time_end(&label) { - let message = DOMString::from( - format!("{}: {}ms", label, delta) - ); - println!("{}", message); - Self::send_to_devtools(global, LogLevel::Log, message); - }; + let message = DOMString::from(format!("{}: {}ms", label, delta)); + console_message(global, message, LogLevel::Log); + } + } + + // https://console.spec.whatwg.org/#group + pub fn Group(global: &GlobalScope, messages: Vec<DOMString>) { + global.push_console_group(DOMString::from(messages.join(" "))); + } + + // https://console.spec.whatwg.org/#groupcollapsed + pub fn GroupCollapsed(global: &GlobalScope, messages: Vec<DOMString>) { + global.push_console_group(DOMString::from(messages.join(" "))); } -} -fn prepare_message(log_level: LogLevel, message: DOMString) -> ConsoleMessage { - // TODO: Sending fake values for filename, lineNumber and columnNumber in LogMessage; adjust later - ConsoleMessage { - message: String::from(message), - logLevel: log_level, - filename: "test".to_owned(), - lineNumber: 1, - columnNumber: 1, + // https://console.spec.whatwg.org/#groupend + pub fn GroupEnd(global: &GlobalScope) { + global.pop_console_group(); } } diff --git a/components/script/dom/constantsourcenode.rs b/components/script/dom/constantsourcenode.rs new file mode 100644 index 00000000000..90851da88c2 --- /dev/null +++ b/components/script/dom/constantsourcenode.rs @@ -0,0 +1,93 @@ +/* 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 crate::dom::audioparam::AudioParam; +use crate::dom::audioscheduledsourcenode::AudioScheduledSourceNode; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate; +use crate::dom::bindings::codegen::Bindings::ConstantSourceNodeBinding::ConstantSourceNodeMethods; +use crate::dom::bindings::codegen::Bindings::ConstantSourceNodeBinding::ConstantSourceOptions; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::constant_source_node::ConstantSourceNodeOptions as ServoMediaConstantSourceOptions; +use servo_media::audio::node::AudioNodeInit; +use servo_media::audio::param::ParamType; +use std::f32; + +#[dom_struct] +pub struct ConstantSourceNode { + source_node: AudioScheduledSourceNode, + offset: Dom<AudioParam>, +} + +impl ConstantSourceNode { + #[allow(unrooted_must_root)] + fn new_inherited( + window: &Window, + context: &BaseAudioContext, + options: &ConstantSourceOptions, + ) -> Fallible<ConstantSourceNode> { + let node_options = Default::default(); + let source_node = AudioScheduledSourceNode::new_inherited( + AudioNodeInit::ConstantSourceNode(options.into()), + context, + node_options, /* 2, MAX, Speakers */ + 0, /* inputs */ + 1, /* outputs */ + )?; + let node_id = source_node.node().node_id(); + let offset = AudioParam::new( + window, + context, + node_id, + ParamType::Offset, + AutomationRate::A_rate, + *options.offset, + f32::MIN, + f32::MAX, + ); + + Ok(ConstantSourceNode { + source_node, + offset: Dom::from_ref(&offset), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &ConstantSourceOptions, + ) -> Fallible<DomRoot<ConstantSourceNode>> { + let node = ConstantSourceNode::new_inherited(window, context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &ConstantSourceOptions, + ) -> Fallible<DomRoot<ConstantSourceNode>> { + ConstantSourceNode::new(window, context, options) + } +} + +impl ConstantSourceNodeMethods for ConstantSourceNode { + // https://webaudio.github.io/web-audio-api/#dom-constantsourcenode-offset + fn Offset(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.offset) + } +} + +impl<'a> From<&'a ConstantSourceOptions> for ServoMediaConstantSourceOptions { + fn from(options: &'a ConstantSourceOptions) -> Self { + Self { + offset: *options.offset, + } + } +} diff --git a/components/script/dom/create.rs b/components/script/dom/create.rs index 7c9a9eeea9c..37aaf07286f 100644 --- a/components/script/dom/create.rs +++ b/components/script/dom/create.rs @@ -1,290 +1,396 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::Element; -use dom::element::ElementCreator; -use dom::htmlanchorelement::HTMLAnchorElement; -use dom::htmlappletelement::HTMLAppletElement; -use dom::htmlareaelement::HTMLAreaElement; -use dom::htmlaudioelement::HTMLAudioElement; -use dom::htmlbaseelement::HTMLBaseElement; -use dom::htmlbodyelement::HTMLBodyElement; -use dom::htmlbrelement::HTMLBRElement; -use dom::htmlbuttonelement::HTMLButtonElement; -use dom::htmlcanvaselement::HTMLCanvasElement; -use dom::htmldataelement::HTMLDataElement; -use dom::htmldatalistelement::HTMLDataListElement; -use dom::htmldetailselement::HTMLDetailsElement; -use dom::htmldialogelement::HTMLDialogElement; -use dom::htmldirectoryelement::HTMLDirectoryElement; -use dom::htmldivelement::HTMLDivElement; -use dom::htmldlistelement::HTMLDListElement; -use dom::htmlelement::HTMLElement; -use dom::htmlembedelement::HTMLEmbedElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlfontelement::HTMLFontElement; -use dom::htmlformelement::HTMLFormElement; -use dom::htmlframeelement::HTMLFrameElement; -use dom::htmlframesetelement::HTMLFrameSetElement; -use dom::htmlheadelement::HTMLHeadElement; -use dom::htmlheadingelement::HTMLHeadingElement; -use dom::htmlheadingelement::HeadingLevel; -use dom::htmlhrelement::HTMLHRElement; -use dom::htmlhtmlelement::HTMLHtmlElement; -use dom::htmliframeelement::HTMLIFrameElement; -use dom::htmlimageelement::HTMLImageElement; -use dom::htmlinputelement::HTMLInputElement; -use dom::htmllabelelement::HTMLLabelElement; -use dom::htmllegendelement::HTMLLegendElement; -use dom::htmllielement::HTMLLIElement; -use dom::htmllinkelement::HTMLLinkElement; -use dom::htmlmapelement::HTMLMapElement; -use dom::htmlmetaelement::HTMLMetaElement; -use dom::htmlmeterelement::HTMLMeterElement; -use dom::htmlmodelement::HTMLModElement; -use dom::htmlobjectelement::HTMLObjectElement; -use dom::htmlolistelement::HTMLOListElement; -use dom::htmloptgroupelement::HTMLOptGroupElement; -use dom::htmloptionelement::HTMLOptionElement; -use dom::htmloutputelement::HTMLOutputElement; -use dom::htmlparagraphelement::HTMLParagraphElement; -use dom::htmlparamelement::HTMLParamElement; -use dom::htmlpreelement::HTMLPreElement; -use dom::htmlprogresselement::HTMLProgressElement; -use dom::htmlquoteelement::HTMLQuoteElement; -use dom::htmlscriptelement::HTMLScriptElement; -use dom::htmlselectelement::HTMLSelectElement; -use dom::htmlsourceelement::HTMLSourceElement; -use dom::htmlspanelement::HTMLSpanElement; -use dom::htmlstyleelement::HTMLStyleElement; -use dom::htmltablecaptionelement::HTMLTableCaptionElement; -use dom::htmltablecolelement::HTMLTableColElement; -use dom::htmltabledatacellelement::HTMLTableDataCellElement; -use dom::htmltableelement::HTMLTableElement; -use dom::htmltableheadercellelement::HTMLTableHeaderCellElement; -use dom::htmltablerowelement::HTMLTableRowElement; -use dom::htmltablesectionelement::HTMLTableSectionElement; -use dom::htmltemplateelement::HTMLTemplateElement; -use dom::htmltextareaelement::HTMLTextAreaElement; -use dom::htmltimeelement::HTMLTimeElement; -use dom::htmltitleelement::HTMLTitleElement; -use dom::htmltrackelement::HTMLTrackElement; -use dom::htmlulistelement::HTMLUListElement; -use dom::htmlunknownelement::HTMLUnknownElement; -use dom::htmlvideoelement::HTMLVideoElement; -use dom::svgsvgelement::SVGSVGElement; -use html5ever_atoms::{Prefix, QualName}; -use servo_config::prefs::PREFS; +use crate::dom::bindings::error::{report_pending_exception, throw_dom_exception}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::DomRoot; +use crate::dom::customelementregistry::{ + is_valid_custom_element_name, upgrade_element, CustomElementState, +}; +use crate::dom::document::Document; +use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlanchorelement::HTMLAnchorElement; +use crate::dom::htmlareaelement::HTMLAreaElement; +use crate::dom::htmlaudioelement::HTMLAudioElement; +use crate::dom::htmlbaseelement::HTMLBaseElement; +use crate::dom::htmlbodyelement::HTMLBodyElement; +use crate::dom::htmlbrelement::HTMLBRElement; +use crate::dom::htmlbuttonelement::HTMLButtonElement; +use crate::dom::htmlcanvaselement::HTMLCanvasElement; +use crate::dom::htmldataelement::HTMLDataElement; +use crate::dom::htmldatalistelement::HTMLDataListElement; +use crate::dom::htmldetailselement::HTMLDetailsElement; +use crate::dom::htmldialogelement::HTMLDialogElement; +use crate::dom::htmldirectoryelement::HTMLDirectoryElement; +use crate::dom::htmldivelement::HTMLDivElement; +use crate::dom::htmldlistelement::HTMLDListElement; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlembedelement::HTMLEmbedElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlfontelement::HTMLFontElement; +use crate::dom::htmlformelement::HTMLFormElement; +use crate::dom::htmlframeelement::HTMLFrameElement; +use crate::dom::htmlframesetelement::HTMLFrameSetElement; +use crate::dom::htmlheadelement::HTMLHeadElement; +use crate::dom::htmlheadingelement::HTMLHeadingElement; +use crate::dom::htmlheadingelement::HeadingLevel; +use crate::dom::htmlhrelement::HTMLHRElement; +use crate::dom::htmlhtmlelement::HTMLHtmlElement; +use crate::dom::htmliframeelement::HTMLIFrameElement; +use crate::dom::htmlimageelement::HTMLImageElement; +use crate::dom::htmlinputelement::HTMLInputElement; +use crate::dom::htmllabelelement::HTMLLabelElement; +use crate::dom::htmllegendelement::HTMLLegendElement; +use crate::dom::htmllielement::HTMLLIElement; +use crate::dom::htmllinkelement::HTMLLinkElement; +use crate::dom::htmlmapelement::HTMLMapElement; +use crate::dom::htmlmenuelement::HTMLMenuElement; +use crate::dom::htmlmetaelement::HTMLMetaElement; +use crate::dom::htmlmeterelement::HTMLMeterElement; +use crate::dom::htmlmodelement::HTMLModElement; +use crate::dom::htmlobjectelement::HTMLObjectElement; +use crate::dom::htmlolistelement::HTMLOListElement; +use crate::dom::htmloptgroupelement::HTMLOptGroupElement; +use crate::dom::htmloptionelement::HTMLOptionElement; +use crate::dom::htmloutputelement::HTMLOutputElement; +use crate::dom::htmlparagraphelement::HTMLParagraphElement; +use crate::dom::htmlparamelement::HTMLParamElement; +use crate::dom::htmlpictureelement::HTMLPictureElement; +use crate::dom::htmlpreelement::HTMLPreElement; +use crate::dom::htmlprogresselement::HTMLProgressElement; +use crate::dom::htmlquoteelement::HTMLQuoteElement; +use crate::dom::htmlscriptelement::HTMLScriptElement; +use crate::dom::htmlselectelement::HTMLSelectElement; +use crate::dom::htmlsourceelement::HTMLSourceElement; +use crate::dom::htmlspanelement::HTMLSpanElement; +use crate::dom::htmlstyleelement::HTMLStyleElement; +use crate::dom::htmltablecaptionelement::HTMLTableCaptionElement; +use crate::dom::htmltablecellelement::HTMLTableCellElement; +use crate::dom::htmltablecolelement::HTMLTableColElement; +use crate::dom::htmltableelement::HTMLTableElement; +use crate::dom::htmltablerowelement::HTMLTableRowElement; +use crate::dom::htmltablesectionelement::HTMLTableSectionElement; +use crate::dom::htmltemplateelement::HTMLTemplateElement; +use crate::dom::htmltextareaelement::HTMLTextAreaElement; +use crate::dom::htmltimeelement::HTMLTimeElement; +use crate::dom::htmltitleelement::HTMLTitleElement; +use crate::dom::htmltrackelement::HTMLTrackElement; +use crate::dom::htmlulistelement::HTMLUListElement; +use crate::dom::htmlunknownelement::HTMLUnknownElement; +use crate::dom::htmlvideoelement::HTMLVideoElement; +use crate::dom::svgelement::SVGElement; +use crate::dom::svgsvgelement::SVGSVGElement; +use crate::realms::{enter_realm, InRealm}; +use crate::script_thread::ScriptThread; +use html5ever::{LocalName, Prefix, QualName}; +use servo_config::pref; -fn create_svg_element(name: QualName, - prefix: Option<DOMString>, - document: &Document) - -> Root<Element> { - assert!(name.ns == ns!(svg)); +fn create_svg_element( + name: QualName, + prefix: Option<Prefix>, + document: &Document, +) -> DomRoot<Element> { + assert_eq!(name.ns, ns!(svg)); macro_rules! make( ($ctor:ident) => ({ let obj = $ctor::new(name.local, prefix, document); - Root::upcast(obj) + DomRoot::upcast(obj) }); ($ctor:ident, $($arg:expr),+) => ({ let obj = $ctor::new(name.local, prefix, document, $($arg),+); - Root::upcast(obj) + DomRoot::upcast(obj) }) ); - if !PREFS.get("dom.svg.enabled").as_boolean().unwrap_or(false) { + if !pref!(dom.svg.enabled) { return Element::new(name.local, name.ns, prefix, document); } match name.local { - local_name!("svg") => make!(SVGSVGElement), - _ => Element::new(name.local, name.ns, prefix, document), + local_name!("svg") => make!(SVGSVGElement), + _ => make!(SVGElement), } } -fn create_html_element(name: QualName, - prefix: Option<DOMString>, - document: &Document, - creator: ElementCreator) - -> Root<Element> { - assert!(name.ns == ns!(html)); +// https://dom.spec.whatwg.org/#concept-create-element +#[allow(unsafe_code)] +fn create_html_element( + name: QualName, + prefix: Option<Prefix>, + is: Option<LocalName>, + document: &Document, + creator: ElementCreator, + mode: CustomElementCreationMode, +) -> DomRoot<Element> { + assert_eq!(name.ns, ns!(html)); + + // Step 4 + let definition = document.lookup_custom_element_definition(&name.ns, &name.local, is.as_ref()); + + if let Some(definition) = definition { + if definition.is_autonomous() { + match mode { + CustomElementCreationMode::Asynchronous => { + let result = DomRoot::upcast::<Element>(HTMLElement::new( + name.local.clone(), + prefix.clone(), + document, + )); + result.set_custom_element_state(CustomElementState::Undefined); + ScriptThread::enqueue_upgrade_reaction(&*result, definition); + return result; + }, + CustomElementCreationMode::Synchronous => { + let local_name = name.local.clone(); + return match definition.create_element(document, prefix.clone()) { + Ok(element) => { + element.set_custom_element_definition(definition.clone()); + element + }, + Err(error) => { + // Step 6. Recovering from exception. + let global = + GlobalScope::current().unwrap_or_else(|| document.global()); + let cx = global.get_cx(); + + // Step 6.1.1 + unsafe { + let ar = enter_realm(&*global); + throw_dom_exception(cx, &global, error); + report_pending_exception(*cx, true, InRealm::Entered(&ar)); + } + + // Step 6.1.2 + let element = DomRoot::upcast::<Element>(HTMLUnknownElement::new( + local_name, prefix, document, + )); + element.set_custom_element_state(CustomElementState::Failed); + element + }, + }; + }, + } + } else { + // Steps 5.1-5.2 + let element = create_native_html_element(name, prefix, document, creator); + element.set_is(definition.name.clone()); + element.set_custom_element_state(CustomElementState::Undefined); + match mode { + // Step 5.3 + CustomElementCreationMode::Synchronous => upgrade_element(definition, &*element), + // Step 5.4 + CustomElementCreationMode::Asynchronous => { + ScriptThread::enqueue_upgrade_reaction(&*element, definition) + }, + } + return element; + } + } + + // Steps 7.1-7.3 + let result = create_native_html_element(name.clone(), prefix, document, creator); + match is { + Some(is) => { + result.set_is(is); + result.set_custom_element_state(CustomElementState::Undefined); + }, + None => { + if is_valid_custom_element_name(&*name.local) { + result.set_custom_element_state(CustomElementState::Undefined); + } else { + result.set_custom_element_state(CustomElementState::Uncustomized); + } + }, + }; + + // Step 8 + result +} + +pub fn create_native_html_element( + name: QualName, + prefix: Option<Prefix>, + document: &Document, + creator: ElementCreator, +) -> DomRoot<Element> { + assert_eq!(name.ns, ns!(html)); macro_rules! make( ($ctor:ident) => ({ let obj = $ctor::new(name.local, prefix, document); - Root::upcast(obj) + DomRoot::upcast(obj) }); ($ctor:ident, $($arg:expr),+) => ({ let obj = $ctor::new(name.local, prefix, document, $($arg),+); - Root::upcast(obj) + DomRoot::upcast(obj) }) ); // This is a big match, and the IDs for inline-interned atoms are not very structured. // Perhaps we should build a perfect hash from those IDs instead. + // https://html.spec.whatwg.org/multipage/#elements-in-the-dom match name.local { - local_name!("a") => make!(HTMLAnchorElement), - local_name!("abbr") => make!(HTMLElement), - local_name!("acronym") => make!(HTMLElement), - local_name!("address") => make!(HTMLElement), - local_name!("applet") => make!(HTMLAppletElement), - local_name!("area") => make!(HTMLAreaElement), - local_name!("article") => make!(HTMLElement), - local_name!("aside") => make!(HTMLElement), - local_name!("audio") => make!(HTMLAudioElement), - local_name!("b") => make!(HTMLElement), - local_name!("base") => make!(HTMLBaseElement), - local_name!("bdi") => make!(HTMLElement), - local_name!("bdo") => make!(HTMLElement), + local_name!("a") => make!(HTMLAnchorElement), + local_name!("abbr") => make!(HTMLElement), + local_name!("acronym") => make!(HTMLElement), + local_name!("address") => make!(HTMLElement), + local_name!("area") => make!(HTMLAreaElement), + local_name!("article") => make!(HTMLElement), + local_name!("aside") => make!(HTMLElement), + local_name!("audio") => make!(HTMLAudioElement), + local_name!("b") => make!(HTMLElement), + local_name!("base") => make!(HTMLBaseElement), + local_name!("bdi") => make!(HTMLElement), + local_name!("bdo") => make!(HTMLElement), // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:bgsound - local_name!("bgsound") => make!(HTMLUnknownElement), - local_name!("big") => make!(HTMLElement), + local_name!("bgsound") => make!(HTMLUnknownElement), + local_name!("big") => make!(HTMLElement), // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:blink - local_name!("blink") => make!(HTMLUnknownElement), + local_name!("blink") => make!(HTMLUnknownElement), // https://html.spec.whatwg.org/multipage/#the-blockquote-element local_name!("blockquote") => make!(HTMLQuoteElement), - local_name!("body") => make!(HTMLBodyElement), - local_name!("br") => make!(HTMLBRElement), - local_name!("button") => make!(HTMLButtonElement), - local_name!("canvas") => make!(HTMLCanvasElement), - local_name!("caption") => make!(HTMLTableCaptionElement), - local_name!("center") => make!(HTMLElement), - local_name!("cite") => make!(HTMLElement), - local_name!("code") => make!(HTMLElement), - local_name!("col") => make!(HTMLTableColElement), - local_name!("colgroup") => make!(HTMLTableColElement), - local_name!("data") => make!(HTMLDataElement), - local_name!("datalist") => make!(HTMLDataListElement), - local_name!("dd") => make!(HTMLElement), - local_name!("del") => make!(HTMLModElement), - local_name!("details") => make!(HTMLDetailsElement), - local_name!("dfn") => make!(HTMLElement), - local_name!("dialog") => make!(HTMLDialogElement), - local_name!("dir") => make!(HTMLDirectoryElement), - local_name!("div") => make!(HTMLDivElement), - local_name!("dl") => make!(HTMLDListElement), - local_name!("dt") => make!(HTMLElement), - local_name!("em") => make!(HTMLElement), - local_name!("embed") => make!(HTMLEmbedElement), - local_name!("fieldset") => make!(HTMLFieldSetElement), + local_name!("body") => make!(HTMLBodyElement), + local_name!("br") => make!(HTMLBRElement), + local_name!("button") => make!(HTMLButtonElement), + local_name!("canvas") => make!(HTMLCanvasElement), + local_name!("caption") => make!(HTMLTableCaptionElement), + local_name!("center") => make!(HTMLElement), + local_name!("cite") => make!(HTMLElement), + local_name!("code") => make!(HTMLElement), + local_name!("col") => make!(HTMLTableColElement), + local_name!("colgroup") => make!(HTMLTableColElement), + local_name!("data") => make!(HTMLDataElement), + local_name!("datalist") => make!(HTMLDataListElement), + local_name!("dd") => make!(HTMLElement), + local_name!("del") => make!(HTMLModElement), + local_name!("details") => make!(HTMLDetailsElement), + local_name!("dfn") => make!(HTMLElement), + local_name!("dialog") => make!(HTMLDialogElement), + local_name!("dir") => make!(HTMLDirectoryElement), + local_name!("div") => make!(HTMLDivElement), + local_name!("dl") => make!(HTMLDListElement), + local_name!("dt") => make!(HTMLElement), + local_name!("em") => make!(HTMLElement), + local_name!("embed") => make!(HTMLEmbedElement), + local_name!("fieldset") => make!(HTMLFieldSetElement), local_name!("figcaption") => make!(HTMLElement), - local_name!("figure") => make!(HTMLElement), - local_name!("font") => make!(HTMLFontElement), - local_name!("footer") => make!(HTMLElement), - local_name!("form") => make!(HTMLFormElement), - local_name!("frame") => make!(HTMLFrameElement), - local_name!("frameset") => make!(HTMLFrameSetElement), - local_name!("h1") => make!(HTMLHeadingElement, HeadingLevel::Heading1), - local_name!("h2") => make!(HTMLHeadingElement, HeadingLevel::Heading2), - local_name!("h3") => make!(HTMLHeadingElement, HeadingLevel::Heading3), - local_name!("h4") => make!(HTMLHeadingElement, HeadingLevel::Heading4), - local_name!("h5") => make!(HTMLHeadingElement, HeadingLevel::Heading5), - local_name!("h6") => make!(HTMLHeadingElement, HeadingLevel::Heading6), - local_name!("head") => make!(HTMLHeadElement), - local_name!("header") => make!(HTMLElement), - local_name!("hgroup") => make!(HTMLElement), - local_name!("hr") => make!(HTMLHRElement), - local_name!("html") => make!(HTMLHtmlElement), - local_name!("i") => make!(HTMLElement), - local_name!("iframe") => make!(HTMLIFrameElement), - local_name!("img") => make!(HTMLImageElement), - local_name!("input") => make!(HTMLInputElement), - local_name!("ins") => make!(HTMLModElement), + local_name!("figure") => make!(HTMLElement), + local_name!("font") => make!(HTMLFontElement), + local_name!("footer") => make!(HTMLElement), + local_name!("form") => make!(HTMLFormElement), + local_name!("frame") => make!(HTMLFrameElement), + local_name!("frameset") => make!(HTMLFrameSetElement), + local_name!("h1") => make!(HTMLHeadingElement, HeadingLevel::Heading1), + local_name!("h2") => make!(HTMLHeadingElement, HeadingLevel::Heading2), + local_name!("h3") => make!(HTMLHeadingElement, HeadingLevel::Heading3), + local_name!("h4") => make!(HTMLHeadingElement, HeadingLevel::Heading4), + local_name!("h5") => make!(HTMLHeadingElement, HeadingLevel::Heading5), + local_name!("h6") => make!(HTMLHeadingElement, HeadingLevel::Heading6), + local_name!("head") => make!(HTMLHeadElement), + local_name!("header") => make!(HTMLElement), + local_name!("hgroup") => make!(HTMLElement), + local_name!("hr") => make!(HTMLHRElement), + local_name!("html") => make!(HTMLHtmlElement), + local_name!("i") => make!(HTMLElement), + local_name!("iframe") => make!(HTMLIFrameElement), + local_name!("img") => make!(HTMLImageElement), + local_name!("input") => make!(HTMLInputElement), + local_name!("ins") => make!(HTMLModElement), // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:isindex-2 - local_name!("isindex") => make!(HTMLUnknownElement), - local_name!("kbd") => make!(HTMLElement), - local_name!("label") => make!(HTMLLabelElement), - local_name!("legend") => make!(HTMLLegendElement), - local_name!("li") => make!(HTMLLIElement), - local_name!("link") => make!(HTMLLinkElement, creator), + local_name!("isindex") => make!(HTMLUnknownElement), + local_name!("kbd") => make!(HTMLElement), + local_name!("label") => make!(HTMLLabelElement), + local_name!("legend") => make!(HTMLLegendElement), + local_name!("li") => make!(HTMLLIElement), + local_name!("link") => make!(HTMLLinkElement, creator), // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:listing - local_name!("listing") => make!(HTMLPreElement), - local_name!("main") => make!(HTMLElement), - local_name!("map") => make!(HTMLMapElement), - local_name!("mark") => make!(HTMLElement), - local_name!("marquee") => make!(HTMLElement), - local_name!("meta") => make!(HTMLMetaElement), - local_name!("meter") => make!(HTMLMeterElement), + local_name!("listing") => make!(HTMLPreElement), + local_name!("main") => make!(HTMLElement), + local_name!("map") => make!(HTMLMapElement), + local_name!("mark") => make!(HTMLElement), + local_name!("marquee") => make!(HTMLElement), + local_name!("menu") => make!(HTMLMenuElement), + local_name!("meta") => make!(HTMLMetaElement), + local_name!("meter") => make!(HTMLMeterElement), // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:multicol - local_name!("multicol") => make!(HTMLUnknownElement), - local_name!("nav") => make!(HTMLElement), + local_name!("multicol") => make!(HTMLUnknownElement), + local_name!("nav") => make!(HTMLElement), // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:nextid - local_name!("nextid") => make!(HTMLUnknownElement), - local_name!("nobr") => make!(HTMLElement), - local_name!("noframes") => make!(HTMLElement), - local_name!("noscript") => make!(HTMLElement), - local_name!("object") => make!(HTMLObjectElement), - local_name!("ol") => make!(HTMLOListElement), - local_name!("optgroup") => make!(HTMLOptGroupElement), - local_name!("option") => make!(HTMLOptionElement), - local_name!("output") => make!(HTMLOutputElement), - local_name!("p") => make!(HTMLParagraphElement), - local_name!("param") => make!(HTMLParamElement), - local_name!("plaintext") => make!(HTMLPreElement), - local_name!("pre") => make!(HTMLPreElement), - local_name!("progress") => make!(HTMLProgressElement), - local_name!("q") => make!(HTMLQuoteElement), - local_name!("rp") => make!(HTMLElement), - local_name!("rt") => make!(HTMLElement), - local_name!("ruby") => make!(HTMLElement), - local_name!("s") => make!(HTMLElement), - local_name!("samp") => make!(HTMLElement), - local_name!("script") => make!(HTMLScriptElement, creator), - local_name!("section") => make!(HTMLElement), - local_name!("select") => make!(HTMLSelectElement), - local_name!("small") => make!(HTMLElement), - local_name!("source") => make!(HTMLSourceElement), + local_name!("nextid") => make!(HTMLUnknownElement), + local_name!("nobr") => make!(HTMLElement), + local_name!("noframes") => make!(HTMLElement), + local_name!("noscript") => make!(HTMLElement), + local_name!("object") => make!(HTMLObjectElement), + local_name!("ol") => make!(HTMLOListElement), + local_name!("optgroup") => make!(HTMLOptGroupElement), + local_name!("option") => make!(HTMLOptionElement), + local_name!("output") => make!(HTMLOutputElement), + local_name!("p") => make!(HTMLParagraphElement), + local_name!("param") => make!(HTMLParamElement), + local_name!("picture") => make!(HTMLPictureElement), + local_name!("plaintext") => make!(HTMLPreElement), + local_name!("pre") => make!(HTMLPreElement), + local_name!("progress") => make!(HTMLProgressElement), + local_name!("q") => make!(HTMLQuoteElement), + local_name!("rp") => make!(HTMLElement), + local_name!("rt") => make!(HTMLElement), + local_name!("ruby") => make!(HTMLElement), + local_name!("s") => make!(HTMLElement), + local_name!("samp") => make!(HTMLElement), + local_name!("script") => make!(HTMLScriptElement, creator), + local_name!("section") => make!(HTMLElement), + local_name!("select") => make!(HTMLSelectElement), + local_name!("small") => make!(HTMLElement), + local_name!("source") => make!(HTMLSourceElement), // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:spacer - local_name!("spacer") => make!(HTMLUnknownElement), - local_name!("span") => make!(HTMLSpanElement), - local_name!("strike") => make!(HTMLElement), - local_name!("strong") => make!(HTMLElement), - local_name!("style") => make!(HTMLStyleElement, creator), - local_name!("sub") => make!(HTMLElement), - local_name!("summary") => make!(HTMLElement), - local_name!("sup") => make!(HTMLElement), - local_name!("table") => make!(HTMLTableElement), - local_name!("tbody") => make!(HTMLTableSectionElement), - local_name!("td") => make!(HTMLTableDataCellElement), - local_name!("template") => make!(HTMLTemplateElement), - local_name!("textarea") => make!(HTMLTextAreaElement), + local_name!("spacer") => make!(HTMLUnknownElement), + local_name!("span") => make!(HTMLSpanElement), + local_name!("strike") => make!(HTMLElement), + local_name!("strong") => make!(HTMLElement), + local_name!("style") => make!(HTMLStyleElement, creator), + local_name!("sub") => make!(HTMLElement), + local_name!("summary") => make!(HTMLElement), + local_name!("sup") => make!(HTMLElement), + local_name!("table") => make!(HTMLTableElement), + local_name!("tbody") => make!(HTMLTableSectionElement), + local_name!("td") => make!(HTMLTableCellElement), + local_name!("template") => make!(HTMLTemplateElement), + local_name!("textarea") => make!(HTMLTextAreaElement), // https://html.spec.whatwg.org/multipage/#the-tfoot-element:concept-element-dom - local_name!("tfoot") => make!(HTMLTableSectionElement), - local_name!("th") => make!(HTMLTableHeaderCellElement), + local_name!("tfoot") => make!(HTMLTableSectionElement), + local_name!("th") => make!(HTMLTableCellElement), // https://html.spec.whatwg.org/multipage/#the-thead-element:concept-element-dom - local_name!("thead") => make!(HTMLTableSectionElement), - local_name!("time") => make!(HTMLTimeElement), - local_name!("title") => make!(HTMLTitleElement), - local_name!("tr") => make!(HTMLTableRowElement), - local_name!("tt") => make!(HTMLElement), - local_name!("track") => make!(HTMLTrackElement), - local_name!("u") => make!(HTMLElement), - local_name!("ul") => make!(HTMLUListElement), - local_name!("var") => make!(HTMLElement), - local_name!("video") => make!(HTMLVideoElement), - local_name!("wbr") => make!(HTMLElement), - local_name!("xmp") => make!(HTMLPreElement), - _ => make!(HTMLUnknownElement), + local_name!("thead") => make!(HTMLTableSectionElement), + local_name!("time") => make!(HTMLTimeElement), + local_name!("title") => make!(HTMLTitleElement), + local_name!("tr") => make!(HTMLTableRowElement), + local_name!("tt") => make!(HTMLElement), + local_name!("track") => make!(HTMLTrackElement), + local_name!("u") => make!(HTMLElement), + local_name!("ul") => make!(HTMLUListElement), + local_name!("var") => make!(HTMLElement), + local_name!("video") => make!(HTMLVideoElement), + local_name!("wbr") => make!(HTMLElement), + local_name!("xmp") => make!(HTMLPreElement), + _ if is_valid_custom_element_name(&*name.local) => make!(HTMLElement), + _ => make!(HTMLUnknownElement), } } -pub fn create_element(name: QualName, - prefix: Option<Prefix>, - document: &Document, - creator: ElementCreator) - -> Root<Element> { - // FIXME(ajeffrey): Convert directly from Prefix to DOMString. - - let prefix = prefix.map(|p| DOMString::from(&*p)); - +pub fn create_element( + name: QualName, + is: Option<LocalName>, + document: &Document, + creator: ElementCreator, + mode: CustomElementCreationMode, +) -> DomRoot<Element> { + let prefix = name.prefix.clone(); match name.ns { - ns!(html) => create_html_element(name, prefix, document, creator), - ns!(svg) => create_svg_element(name, prefix, document), - _ => Element::new(name.local, name.ns, prefix, document) + ns!(html) => create_html_element(name, prefix, is, document, creator, mode), + ns!(svg) => create_svg_element(name, prefix, document), + _ => Element::new(name.local, name.ns, prefix, document), } } diff --git a/components/script/dom/crypto.rs b/components/script/dom/crypto.rs index fc71faa660a..6aec75cbf3f 100644 --- a/components/script/dom/crypto.rs +++ b/components/script/dom/crypto.rs @@ -1,19 +1,21 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use core::nonzero::NonZero; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::CryptoBinding; -use dom::bindings::codegen::Bindings::CryptoBinding::CryptoMethods; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::CryptoBinding::CryptoMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{JSContext, JSObject}; +use js::jsapi::JSObject; use js::jsapi::Type; -use servo_rand::{ServoRng, Rng}; +use js::rust::CustomAutoRooterGuard; +use js::typedarray::ArrayBufferView; +use servo_rand::{RngCore, ServoRng}; +use std::ptr::NonNull; unsafe_no_jsmanaged_fields!(ServoRng); @@ -21,51 +23,44 @@ unsafe_no_jsmanaged_fields!(ServoRng); #[dom_struct] pub struct Crypto { reflector_: Reflector, - #[ignore_heap_size_of = "Defined in rand"] - rng: DOMRefCell<ServoRng>, + #[ignore_malloc_size_of = "Defined in rand"] + rng: DomRefCell<ServoRng>, } impl Crypto { fn new_inherited() -> Crypto { Crypto { reflector_: Reflector::new(), - rng: DOMRefCell::new(ServoRng::new()), + rng: DomRefCell::new(ServoRng::new()), } } - pub fn new(global: &GlobalScope) -> Root<Crypto> { - reflect_dom_object(box Crypto::new_inherited(), global, CryptoBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<Crypto> { + reflect_dom_object(Box::new(Crypto::new_inherited()), global) } } impl CryptoMethods for Crypto { #[allow(unsafe_code)] // https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#Crypto-method-getRandomValues - unsafe fn GetRandomValues(&self, - _cx: *mut JSContext, - input: *mut JSObject) - -> Fallible<NonZero<*mut JSObject>> { - assert!(!input.is_null()); - typedarray!(in(_cx) let mut array_buffer_view: ArrayBufferView = input); - let (array_type, mut data) = match array_buffer_view.as_mut() { - Ok(x) => (x.get_array_type(), x.as_mut_slice()), - Err(_) => { - return Err(Error::Type("Argument to Crypto.getRandomValues is not an ArrayBufferView" - .to_owned())); - } - }; + fn GetRandomValues( + &self, + _cx: JSContext, + mut input: CustomAutoRooterGuard<ArrayBufferView>, + ) -> Fallible<NonNull<JSObject>> { + let array_type = input.get_array_type(); if !is_integer_buffer(array_type) { return Err(Error::TypeMismatch); + } else { + let mut data = unsafe { input.as_mut_slice() }; + if data.len() > 65536 { + return Err(Error::QuotaExceeded); + } + self.rng.borrow_mut().fill_bytes(&mut data); } - if data.len() > 65536 { - return Err(Error::QuotaExceeded); - } - - self.rng.borrow_mut().fill_bytes(&mut data); - - Ok(NonZero::new(input)) + unsafe { Ok(NonNull::new_unchecked(*input.underlying_object())) } } } diff --git a/components/script/dom/css.rs b/components/script/dom/css.rs index 0e4a7f85065..8c9600549c5 100644 --- a/components/script/dom/css.rs +++ b/components/script/dom/css.rs @@ -1,51 +1,80 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use cssparser::{Parser, serialize_identifier}; -use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::error::Fallible; -use dom::bindings::reflector::Reflector; -use dom::bindings::str::DOMString; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::window::Window; +use crate::dom::worklet::Worklet; +use cssparser::{serialize_identifier, Parser, ParserInput}; use dom_struct::dom_struct; -use style::parser::{LengthParsingMode, ParserContext}; -use style::stylesheets::CssRuleType; -use style::supports::{Declaration, parse_condition_or_declaration}; +use style::context::QuirksMode; +use style::parser::ParserContext; +use style::stylesheets::supports_rule::{parse_condition_or_declaration, Declaration}; +use style::stylesheets::{CssRuleType, Origin}; +use style_traits::ParsingMode; #[dom_struct] pub struct CSS { reflector_: Reflector, } +#[allow(non_snake_case)] impl CSS { - /// http://dev.w3.org/csswg/cssom/#serialize-an-identifier + /// <http://dev.w3.org/csswg/cssom/#serialize-an-identifier> pub fn Escape(_: &Window, ident: DOMString) -> Fallible<DOMString> { let mut escaped = String::new(); serialize_identifier(&ident, &mut escaped).unwrap(); Ok(DOMString::from(escaped)) } - /// https://drafts.csswg.org/css-conditional/#dom-css-supports + /// <https://drafts.csswg.org/css-conditional/#dom-css-supports> pub fn Supports(win: &Window, property: DOMString, value: DOMString) -> bool { - let decl = Declaration { prop: property.into(), val: value.into() }; + let mut decl = String::new(); + serialize_identifier(&property, &mut decl).unwrap(); + decl.push_str(": "); + decl.push_str(&value); + let decl = Declaration(decl); let url = win.Document().url(); - let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Supports), - LengthParsingMode::Default); + let context = ParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Style), + ParsingMode::DEFAULT, + QuirksMode::NoQuirks, + None, + None, + ); decl.eval(&context) } - /// https://drafts.csswg.org/css-conditional/#dom-css-supports + /// <https://drafts.csswg.org/css-conditional/#dom-css-supports> pub fn Supports_(win: &Window, condition: DOMString) -> bool { - let mut input = Parser::new(&condition); - let cond = parse_condition_or_declaration(&mut input); - if let Ok(cond) = cond { - let url = win.Document().url(); - let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Supports), - LengthParsingMode::Default); - cond.eval(&context) - } else { - false - } + let mut input = ParserInput::new(&condition); + let mut input = Parser::new(&mut input); + let cond = match parse_condition_or_declaration(&mut input) { + Ok(c) => c, + Err(..) => return false, + }; + + let url = win.Document().url(); + let context = ParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Style), + ParsingMode::DEFAULT, + QuirksMode::NoQuirks, + None, + None, + ); + cond.eval(&context, &Default::default()) + } + + /// <https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet> + pub fn PaintWorklet(win: &Window) -> DomRoot<Worklet> { + win.paint_worklet() } } diff --git a/components/script/dom/cssconditionrule.rs b/components/script/dom/cssconditionrule.rs index 8bf5dad6c93..86010eba4e9 100644 --- a/components/script/dom/cssconditionrule.rs +++ b/components/script/dom/cssconditionrule.rs @@ -1,17 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CSSConditionRuleBinding::CSSConditionRuleMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::str::DOMString; -use dom::cssgroupingrule::CSSGroupingRule; -use dom::cssmediarule::CSSMediaRule; -use dom::cssstylesheet::CSSStyleSheet; -use dom::csssupportsrule::CSSSupportsRule; +use crate::dom::bindings::codegen::Bindings::CSSConditionRuleBinding::CSSConditionRuleMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssgroupingrule::CSSGroupingRule; +use crate::dom::cssmediarule::CSSMediaRule; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::csssupportsrule::CSSSupportsRule; use dom_struct::dom_struct; -use std::sync::Arc; -use style::shared_lock::{SharedRwLock, Locked}; +use servo_arc::Arc; +use style::shared_lock::{Locked, SharedRwLock}; use style::stylesheets::CssRules as StyleCssRules; #[dom_struct] @@ -20,8 +20,10 @@ pub struct CSSConditionRule { } impl CSSConditionRule { - pub fn new_inherited(parent_stylesheet: &CSSStyleSheet, - rules: Arc<Locked<StyleCssRules>>) -> CSSConditionRule { + pub fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + rules: Arc<Locked<StyleCssRules>>, + ) -> CSSConditionRule { CSSConditionRule { cssgroupingrule: CSSGroupingRule::new_inherited(parent_stylesheet, rules), } @@ -37,7 +39,7 @@ impl CSSConditionRule { } impl CSSConditionRuleMethods for CSSConditionRule { - /// https://drafts.csswg.org/css-conditional-3/#dom-cssconditionrule-conditiontext + /// <https://drafts.csswg.org/css-conditional-3/#dom-cssconditionrule-conditiontext> fn ConditionText(&self) -> DOMString { if let Some(rule) = self.downcast::<CSSMediaRule>() { rule.get_condition_text() @@ -48,7 +50,7 @@ impl CSSConditionRuleMethods for CSSConditionRule { } } - /// https://drafts.csswg.org/css-conditional-3/#dom-cssconditionrule-conditiontext + /// <https://drafts.csswg.org/css-conditional-3/#dom-cssconditionrule-conditiontext> fn SetConditionText(&self, text: DOMString) { if let Some(rule) = self.downcast::<CSSMediaRule>() { rule.set_condition_text(text) diff --git a/components/script/dom/cssfontfacerule.rs b/components/script/dom/cssfontfacerule.rs index 1f247c25fdc..8e2349a0429 100644 --- a/components/script/dom/cssfontfacerule.rs +++ b/components/script/dom/cssfontfacerule.rs @@ -1,29 +1,30 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CSSFontFaceRuleBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::cssrule::{CSSRule, SpecificCSSRule}; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssrule::{CSSRule, SpecificCSSRule}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::window::Window; use dom_struct::dom_struct; -use std::sync::Arc; +use servo_arc::Arc; use style::shared_lock::{Locked, ToCssWithGuard}; use style::stylesheets::FontFaceRule; #[dom_struct] pub struct CSSFontFaceRule { cssrule: CSSRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] fontfacerule: Arc<Locked<FontFaceRule>>, } impl CSSFontFaceRule { - fn new_inherited(parent_stylesheet: &CSSStyleSheet, fontfacerule: Arc<Locked<FontFaceRule>>) - -> CSSFontFaceRule { + fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + fontfacerule: Arc<Locked<FontFaceRule>>, + ) -> CSSFontFaceRule { CSSFontFaceRule { cssrule: CSSRule::new_inherited(parent_stylesheet), fontfacerule: fontfacerule, @@ -31,22 +32,32 @@ impl CSSFontFaceRule { } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - fontfacerule: Arc<Locked<FontFaceRule>>) -> Root<CSSFontFaceRule> { - reflect_dom_object(box CSSFontFaceRule::new_inherited(parent_stylesheet, fontfacerule), - window, - CSSFontFaceRuleBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + fontfacerule: Arc<Locked<FontFaceRule>>, + ) -> DomRoot<CSSFontFaceRule> { + reflect_dom_object( + Box::new(CSSFontFaceRule::new_inherited( + parent_stylesheet, + fontfacerule, + )), + window, + ) } } impl SpecificCSSRule for CSSFontFaceRule { fn ty(&self) -> u16 { - use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; CSSRuleConstants::FONT_FACE_RULE } fn get_css(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - self.fontfacerule.read_with(&guard).to_css_string(&guard).into() + self.fontfacerule + .read_with(&guard) + .to_css_string(&guard) + .into() } } diff --git a/components/script/dom/cssgroupingrule.rs b/components/script/dom/cssgroupingrule.rs index 249aaccd42e..af0b969058c 100644 --- a/components/script/dom/cssgroupingrule.rs +++ b/components/script/dom/cssgroupingrule.rs @@ -1,44 +1,50 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CSSGroupingRuleBinding::CSSGroupingRuleMethods; -use dom::bindings::error::{ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::cssrule::CSSRule; -use dom::cssrulelist::{CSSRuleList, RulesSource}; -use dom::cssstylesheet::CSSStyleSheet; +use crate::dom::bindings::codegen::Bindings::CSSGroupingRuleBinding::CSSGroupingRuleMethods; +use crate::dom::bindings::error::{ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssrule::CSSRule; +use crate::dom::cssrulelist::{CSSRuleList, RulesSource}; +use crate::dom::cssstylesheet::CSSStyleSheet; use dom_struct::dom_struct; -use std::sync::Arc; -use style::shared_lock::{SharedRwLock, Locked}; +use servo_arc::Arc; +use style::shared_lock::{Locked, SharedRwLock}; use style::stylesheets::CssRules as StyleCssRules; #[dom_struct] pub struct CSSGroupingRule { cssrule: CSSRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] rules: Arc<Locked<StyleCssRules>>, - rulelist: MutNullableJS<CSSRuleList>, + rulelist: MutNullableDom<CSSRuleList>, } impl CSSGroupingRule { - pub fn new_inherited(parent_stylesheet: &CSSStyleSheet, - rules: Arc<Locked<StyleCssRules>>) -> CSSGroupingRule { + pub fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + rules: Arc<Locked<StyleCssRules>>, + ) -> CSSGroupingRule { CSSGroupingRule { cssrule: CSSRule::new_inherited(parent_stylesheet), rules: rules, - rulelist: MutNullableJS::new(None), + rulelist: MutNullableDom::new(None), } } - fn rulelist(&self) -> Root<CSSRuleList> { + fn rulelist(&self) -> DomRoot<CSSRuleList> { let parent_stylesheet = self.upcast::<CSSRule>().parent_stylesheet(); - self.rulelist.or_init(|| CSSRuleList::new(self.global().as_window(), - parent_stylesheet, - RulesSource::Rules(self.rules.clone()))) + self.rulelist.or_init(|| { + CSSRuleList::new( + self.global().as_window(), + parent_stylesheet, + RulesSource::Rules(self.rules.clone()), + ) + }) } pub fn parent_stylesheet(&self) -> &CSSStyleSheet { @@ -52,7 +58,7 @@ impl CSSGroupingRule { impl CSSGroupingRuleMethods for CSSGroupingRule { // https://drafts.csswg.org/cssom/#dom-cssgroupingrule-cssrules - fn CssRules(&self) -> Root<CSSRuleList> { + fn CssRules(&self) -> DomRoot<CSSRuleList> { // XXXManishearth check origin clean flag self.rulelist() } diff --git a/components/script/dom/cssimportrule.rs b/components/script/dom/cssimportrule.rs index 3c0eb7eb4aa..52fbe62c2bd 100644 --- a/components/script/dom/cssimportrule.rs +++ b/components/script/dom/cssimportrule.rs @@ -1,30 +1,30 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CSSImportRuleBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::cssrule::{CSSRule, SpecificCSSRule}; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssrule::{CSSRule, SpecificCSSRule}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::window::Window; use dom_struct::dom_struct; -use std::sync::Arc; +use servo_arc::Arc; use style::shared_lock::{Locked, ToCssWithGuard}; use style::stylesheets::ImportRule; #[dom_struct] pub struct CSSImportRule { cssrule: CSSRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] import_rule: Arc<Locked<ImportRule>>, } impl CSSImportRule { - fn new_inherited(parent_stylesheet: &CSSStyleSheet, - import_rule: Arc<Locked<ImportRule>>) - -> Self { + fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + import_rule: Arc<Locked<ImportRule>>, + ) -> Self { CSSImportRule { cssrule: CSSRule::new_inherited(parent_stylesheet), import_rule: import_rule, @@ -32,23 +32,29 @@ impl CSSImportRule { } #[allow(unrooted_must_root)] - pub fn new(window: &Window, - parent_stylesheet: &CSSStyleSheet, - import_rule: Arc<Locked<ImportRule>>) -> Root<Self> { - reflect_dom_object(box Self::new_inherited(parent_stylesheet, import_rule), - window, - CSSImportRuleBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + import_rule: Arc<Locked<ImportRule>>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(Self::new_inherited(parent_stylesheet, import_rule)), + window, + ) } } impl SpecificCSSRule for CSSImportRule { fn ty(&self) -> u16 { - use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; CSSRuleConstants::IMPORT_RULE } fn get_css(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - self.import_rule.read_with(&guard).to_css_string(&guard).into() + self.import_rule + .read_with(&guard) + .to_css_string(&guard) + .into() } } diff --git a/components/script/dom/csskeyframerule.rs b/components/script/dom/csskeyframerule.rs index d36e8988e04..281fc4b8e5e 100644 --- a/components/script/dom/csskeyframerule.rs +++ b/components/script/dom/csskeyframerule.rs @@ -1,32 +1,34 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CSSKeyframeRuleBinding::{self, CSSKeyframeRuleMethods}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::cssrule::{CSSRule, SpecificCSSRule}; -use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::CSSKeyframeRuleBinding::CSSKeyframeRuleMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssrule::{CSSRule, SpecificCSSRule}; +use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::window::Window; use dom_struct::dom_struct; -use std::sync::Arc; -use style::keyframes::Keyframe; +use servo_arc::Arc; use style::shared_lock::{Locked, ToCssWithGuard}; +use style::stylesheets::keyframes_rule::Keyframe; #[dom_struct] pub struct CSSKeyframeRule { cssrule: CSSRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] keyframerule: Arc<Locked<Keyframe>>, - style_decl: MutNullableJS<CSSStyleDeclaration>, + style_decl: MutNullableDom<CSSStyleDeclaration>, } impl CSSKeyframeRule { - fn new_inherited(parent_stylesheet: &CSSStyleSheet, keyframerule: Arc<Locked<Keyframe>>) - -> CSSKeyframeRule { + fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + keyframerule: Arc<Locked<Keyframe>>, + ) -> CSSKeyframeRule { CSSKeyframeRule { cssrule: CSSRule::new_inherited(parent_stylesheet), keyframerule: keyframerule, @@ -35,23 +37,30 @@ impl CSSKeyframeRule { } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - keyframerule: Arc<Locked<Keyframe>>) -> Root<CSSKeyframeRule> { - reflect_dom_object(box CSSKeyframeRule::new_inherited(parent_stylesheet, keyframerule), - window, - CSSKeyframeRuleBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + keyframerule: Arc<Locked<Keyframe>>, + ) -> DomRoot<CSSKeyframeRule> { + reflect_dom_object( + Box::new(CSSKeyframeRule::new_inherited( + parent_stylesheet, + keyframerule, + )), + window, + ) } } impl CSSKeyframeRuleMethods for CSSKeyframeRule { // https://drafts.csswg.org/css-animations/#dom-csskeyframerule-style - fn Style(&self) -> Root<CSSStyleDeclaration> { + fn Style(&self) -> DomRoot<CSSStyleDeclaration> { self.style_decl.or_init(|| { let guard = self.cssrule.shared_lock().read(); CSSStyleDeclaration::new( self.global().as_window(), CSSStyleOwner::CSSRule( - JS::from_ref(self.upcast()), + Dom::from_ref(self.upcast()), self.keyframerule.read_with(&guard).block.clone(), ), None, @@ -63,12 +72,15 @@ impl CSSKeyframeRuleMethods for CSSKeyframeRule { impl SpecificCSSRule for CSSKeyframeRule { fn ty(&self) -> u16 { - use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; CSSRuleConstants::KEYFRAME_RULE } fn get_css(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - self.keyframerule.read_with(&guard).to_css_string(&guard).into() + self.keyframerule + .read_with(&guard) + .to_css_string(&guard) + .into() } } diff --git a/components/script/dom/csskeyframesrule.rs b/components/script/dom/csskeyframesrule.rs index 281ccd972a7..aa2be337022 100644 --- a/components/script/dom/csskeyframesrule.rs +++ b/components/script/dom/csskeyframesrule.rs @@ -1,73 +1,84 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use cssparser::Parser; -use dom::bindings::codegen::Bindings::CSSKeyframesRuleBinding; -use dom::bindings::codegen::Bindings::CSSKeyframesRuleBinding::CSSKeyframesRuleMethods; -use dom::bindings::error::{Error, ErrorResult}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::csskeyframerule::CSSKeyframeRule; -use dom::cssrule::{CSSRule, SpecificCSSRule}; -use dom::cssrulelist::{CSSRuleList, RulesSource}; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::CSSKeyframesRuleBinding::CSSKeyframesRuleMethods; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::csskeyframerule::CSSKeyframeRule; +use crate::dom::cssrule::{CSSRule, SpecificCSSRule}; +use crate::dom::cssrulelist::{CSSRuleList, RulesSource}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::window::Window; +use cssparser::{Parser, ParserInput}; use dom_struct::dom_struct; -use servo_atoms::Atom; -use std::sync::Arc; -use style::keyframes::{Keyframe, KeyframeSelector}; +use servo_arc::Arc; use style::shared_lock::{Locked, ToCssWithGuard}; -use style::stylesheets::KeyframesRule; +use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframesRule}; +use style::values::KeyframesName; #[dom_struct] pub struct CSSKeyframesRule { cssrule: CSSRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] keyframesrule: Arc<Locked<KeyframesRule>>, - rulelist: MutNullableJS<CSSRuleList>, + rulelist: MutNullableDom<CSSRuleList>, } impl CSSKeyframesRule { - fn new_inherited(parent_stylesheet: &CSSStyleSheet, keyframesrule: Arc<Locked<KeyframesRule>>) - -> CSSKeyframesRule { + fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + keyframesrule: Arc<Locked<KeyframesRule>>, + ) -> CSSKeyframesRule { CSSKeyframesRule { cssrule: CSSRule::new_inherited(parent_stylesheet), keyframesrule: keyframesrule, - rulelist: MutNullableJS::new(None), + rulelist: MutNullableDom::new(None), } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - keyframesrule: Arc<Locked<KeyframesRule>>) -> Root<CSSKeyframesRule> { - reflect_dom_object(box CSSKeyframesRule::new_inherited(parent_stylesheet, keyframesrule), - window, - CSSKeyframesRuleBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + keyframesrule: Arc<Locked<KeyframesRule>>, + ) -> DomRoot<CSSKeyframesRule> { + reflect_dom_object( + Box::new(CSSKeyframesRule::new_inherited( + parent_stylesheet, + keyframesrule, + )), + window, + ) } - fn rulelist(&self) -> Root<CSSRuleList> { + fn rulelist(&self) -> DomRoot<CSSRuleList> { self.rulelist.or_init(|| { let parent_stylesheet = &self.upcast::<CSSRule>().parent_stylesheet(); - CSSRuleList::new(self.global().as_window(), - parent_stylesheet, - RulesSource::Keyframes(self.keyframesrule.clone())) + CSSRuleList::new( + self.global().as_window(), + parent_stylesheet, + RulesSource::Keyframes(self.keyframesrule.clone()), + ) }) } /// Given a keyframe selector, finds the index of the first corresponding rule if any fn find_rule(&self, selector: &str) -> Option<usize> { - let mut input = Parser::new(selector); + let mut input = ParserInput::new(selector); + let mut input = Parser::new(&mut input); if let Ok(sel) = KeyframeSelector::parse(&mut input) { let guard = self.cssrule.shared_lock().read(); // This finds the *last* element matching a selector // because that's the rule that applies. Thus, rposition - self.keyframesrule.read_with(&guard) - .keyframes.iter().rposition(|frame| { - frame.read_with(&guard).selector == sel - }) + self.keyframesrule + .read_with(&guard) + .keyframes + .iter() + .rposition(|frame| frame.read_with(&guard).selector == sel) } else { None } @@ -76,16 +87,25 @@ impl CSSKeyframesRule { impl CSSKeyframesRuleMethods for CSSKeyframesRule { // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-cssrules - fn CssRules(&self) -> Root<CSSRuleList> { + fn CssRules(&self) -> DomRoot<CSSRuleList> { self.rulelist() } // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-appendrule fn AppendRule(&self, rule: DOMString) { - let rule = Keyframe::parse(&rule, self.cssrule.parent_stylesheet().style_stylesheet()); + let style_stylesheet = self.cssrule.parent_stylesheet().style_stylesheet(); + let rule = Keyframe::parse( + &rule, + &style_stylesheet.contents, + &style_stylesheet.shared_lock, + ); + if let Ok(rule) = rule { let mut guard = self.cssrule.shared_lock().write(); - self.keyframesrule.write_with(&mut guard).keyframes.push(rule); + self.keyframesrule + .write_with(&mut guard) + .keyframes + .push(rule); self.rulelist().append_lazy_dom_rule(); } } @@ -98,45 +118,42 @@ impl CSSKeyframesRuleMethods for CSSKeyframesRule { } // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-findrule - fn FindRule(&self, selector: DOMString) -> Option<Root<CSSKeyframeRule>> { - self.find_rule(&selector).and_then(|idx| { - self.rulelist().item(idx as u32) - }).and_then(Root::downcast) + fn FindRule(&self, selector: DOMString) -> Option<DomRoot<CSSKeyframeRule>> { + self.find_rule(&selector) + .and_then(|idx| self.rulelist().item(idx as u32)) + .and_then(DomRoot::downcast) } // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name fn Name(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - DOMString::from(&*self.keyframesrule.read_with(&guard).name) + DOMString::from(&**self.keyframesrule.read_with(&guard).name.as_atom()) } // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name fn SetName(&self, value: DOMString) -> ErrorResult { - // https://github.com/w3c/csswg-drafts/issues/801 - // Setting this property to a CSS-wide keyword or `none` will - // throw a Syntax Error. - match_ignore_ascii_case! { &value, - "initial" => return Err(Error::Syntax), - "inherit" => return Err(Error::Syntax), - "unset" => return Err(Error::Syntax), - "none" => return Err(Error::Syntax), - _ => () - } + // Spec deviation: https://github.com/w3c/csswg-drafts/issues/801 + // Setting this property to a CSS-wide keyword or `none` does not throw, + // it stores a value that serializes as a quoted string. + let name = KeyframesName::from_ident(&value); let mut guard = self.cssrule.shared_lock().write(); - self.keyframesrule.write_with(&mut guard).name = Atom::from(value); + self.keyframesrule.write_with(&mut guard).name = name; Ok(()) } } impl SpecificCSSRule for CSSKeyframesRule { fn ty(&self) -> u16 { - use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; CSSRuleConstants::KEYFRAMES_RULE } fn get_css(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - self.keyframesrule.read_with(&guard).to_css_string(&guard).into() + self.keyframesrule + .read_with(&guard) + .to_css_string(&guard) + .into() } fn deparent_children(&self) { diff --git a/components/script/dom/cssmediarule.rs b/components/script/dom/cssmediarule.rs index aff12e2e2ad..ca2bdd4d447 100644 --- a/components/script/dom/cssmediarule.rs +++ b/components/script/dom/cssmediarule.rs @@ -1,64 +1,72 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use cssparser::Parser; -use dom::bindings::codegen::Bindings::CSSMediaRuleBinding; -use dom::bindings::codegen::Bindings::CSSMediaRuleBinding::CSSMediaRuleMethods; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::cssconditionrule::CSSConditionRule; -use dom::cssrule::SpecificCSSRule; -use dom::cssstylesheet::CSSStyleSheet; -use dom::medialist::MediaList; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::CSSMediaRuleBinding::CSSMediaRuleMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssconditionrule::CSSConditionRule; +use crate::dom::cssrule::SpecificCSSRule; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::medialist::MediaList; +use crate::dom::window::Window; +use cssparser::{Parser, ParserInput}; use dom_struct::dom_struct; -use std::sync::Arc; -use style::media_queries::parse_media_query_list; -use style::parser::{LengthParsingMode, ParserContext}; +use servo_arc::Arc; +use style::media_queries::MediaList as StyleMediaList; +use style::parser::ParserContext; use style::shared_lock::{Locked, ToCssWithGuard}; -use style::stylesheets::{CssRuleType, MediaRule}; -use style_traits::ToCss; +use style::stylesheets::{CssRuleType, MediaRule, Origin}; +use style_traits::{ParsingMode, ToCss}; #[dom_struct] pub struct CSSMediaRule { cssconditionrule: CSSConditionRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] mediarule: Arc<Locked<MediaRule>>, - medialist: MutNullableJS<MediaList>, + medialist: MutNullableDom<MediaList>, } impl CSSMediaRule { - fn new_inherited(parent_stylesheet: &CSSStyleSheet, mediarule: Arc<Locked<MediaRule>>) - -> CSSMediaRule { + fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + mediarule: Arc<Locked<MediaRule>>, + ) -> CSSMediaRule { let guard = parent_stylesheet.shared_lock().read(); let list = mediarule.read_with(&guard).rules.clone(); CSSMediaRule { cssconditionrule: CSSConditionRule::new_inherited(parent_stylesheet, list), mediarule: mediarule, - medialist: MutNullableJS::new(None), + medialist: MutNullableDom::new(None), } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - mediarule: Arc<Locked<MediaRule>>) -> Root<CSSMediaRule> { - reflect_dom_object(box CSSMediaRule::new_inherited(parent_stylesheet, mediarule), - window, - CSSMediaRuleBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + mediarule: Arc<Locked<MediaRule>>, + ) -> DomRoot<CSSMediaRule> { + reflect_dom_object( + Box::new(CSSMediaRule::new_inherited(parent_stylesheet, mediarule)), + window, + ) } - fn medialist(&self) -> Root<MediaList> { + fn medialist(&self) -> DomRoot<MediaList> { self.medialist.or_init(|| { let guard = self.cssconditionrule.shared_lock().read(); - MediaList::new(self.global().as_window(), - self.cssconditionrule.parent_stylesheet(), - self.mediarule.read_with(&guard).media_queries.clone()) + MediaList::new( + self.global().as_window(), + self.cssconditionrule.parent_stylesheet(), + self.mediarule.read_with(&guard).media_queries.clone(), + ) }) } - /// https://drafts.csswg.org/css-conditional-3/#the-cssmediarule-interface + /// <https://drafts.csswg.org/css-conditional-3/#the-cssmediarule-interface> pub fn get_condition_text(&self) -> DOMString { let guard = self.cssconditionrule.shared_lock().read(); let rule = self.mediarule.read_with(&guard); @@ -66,15 +74,25 @@ impl CSSMediaRule { list.to_css_string().into() } - /// https://drafts.csswg.org/css-conditional-3/#the-cssmediarule-interface + /// <https://drafts.csswg.org/css-conditional-3/#the-cssmediarule-interface> pub fn set_condition_text(&self, text: DOMString) { - let mut input = Parser::new(&text); + let mut input = ParserInput::new(&text); + let mut input = Parser::new(&mut input); let global = self.global(); - let win = global.as_window(); - let url = win.get_url(); - let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Media), - LengthParsingMode::Default); - let new_medialist = parse_media_query_list(&context, &mut input); + let window = global.as_window(); + let url = window.get_url(); + let quirks_mode = window.Document().quirks_mode(); + let context = ParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Media), + ParsingMode::DEFAULT, + quirks_mode, + window.css_error_reporter(), + None, + ); + + let new_medialist = StyleMediaList::parse(&context, &mut input); let mut guard = self.cssconditionrule.shared_lock().write(); // Clone an Arc because we can’t borrow `guard` twice at the same time. @@ -90,19 +108,22 @@ impl CSSMediaRule { impl SpecificCSSRule for CSSMediaRule { fn ty(&self) -> u16 { - use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; CSSRuleConstants::MEDIA_RULE } fn get_css(&self) -> DOMString { let guard = self.cssconditionrule.shared_lock().read(); - self.mediarule.read_with(&guard).to_css_string(&guard).into() + self.mediarule + .read_with(&guard) + .to_css_string(&guard) + .into() } } impl CSSMediaRuleMethods for CSSMediaRule { // https://drafts.csswg.org/cssom/#dom-cssgroupingrule-media - fn Media(&self) -> Root<MediaList> { + fn Media(&self) -> DomRoot<MediaList> { self.medialist() } } diff --git a/components/script/dom/cssnamespacerule.rs b/components/script/dom/cssnamespacerule.rs index 744a8020667..3a15c7d5ffd 100644 --- a/components/script/dom/cssnamespacerule.rs +++ b/components/script/dom/cssnamespacerule.rs @@ -1,30 +1,31 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CSSNamespaceRuleBinding; -use dom::bindings::codegen::Bindings::CSSNamespaceRuleBinding::CSSNamespaceRuleMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::cssrule::{CSSRule, SpecificCSSRule}; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::CSSNamespaceRuleBinding::CSSNamespaceRuleMethods; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssrule::{CSSRule, SpecificCSSRule}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::window::Window; use dom_struct::dom_struct; -use std::sync::Arc; +use servo_arc::Arc; use style::shared_lock::{Locked, ToCssWithGuard}; use style::stylesheets::NamespaceRule; #[dom_struct] pub struct CSSNamespaceRule { cssrule: CSSRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] namespacerule: Arc<Locked<NamespaceRule>>, } impl CSSNamespaceRule { - fn new_inherited(parent_stylesheet: &CSSStyleSheet, namespacerule: Arc<Locked<NamespaceRule>>) - -> CSSNamespaceRule { + fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + namespacerule: Arc<Locked<NamespaceRule>>, + ) -> CSSNamespaceRule { CSSNamespaceRule { cssrule: CSSRule::new_inherited(parent_stylesheet), namespacerule: namespacerule, @@ -32,11 +33,18 @@ impl CSSNamespaceRule { } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - namespacerule: Arc<Locked<NamespaceRule>>) -> Root<CSSNamespaceRule> { - reflect_dom_object(box CSSNamespaceRule::new_inherited(parent_stylesheet, namespacerule), - window, - CSSNamespaceRuleBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + namespacerule: Arc<Locked<NamespaceRule>>, + ) -> DomRoot<CSSNamespaceRule> { + reflect_dom_object( + Box::new(CSSNamespaceRule::new_inherited( + parent_stylesheet, + namespacerule, + )), + window, + ) } } @@ -44,26 +52,32 @@ impl CSSNamespaceRuleMethods for CSSNamespaceRule { // https://drafts.csswg.org/cssom/#dom-cssnamespacerule-prefix fn Prefix(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - self.namespacerule.read_with(&guard).prefix - .as_ref().map(|s| s.to_string().into()) + self.namespacerule + .read_with(&guard) + .prefix + .as_ref() + .map(|s| s.to_string().into()) .unwrap_or(DOMString::new()) } // https://drafts.csswg.org/cssom/#dom-cssnamespacerule-namespaceuri fn NamespaceURI(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - (*self.namespacerule.read_with(&guard).url).into() + (**self.namespacerule.read_with(&guard).url).into() } } impl SpecificCSSRule for CSSNamespaceRule { fn ty(&self) -> u16 { - use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; CSSRuleConstants::NAMESPACE_RULE } fn get_css(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - self.namespacerule.read_with(&guard).to_css_string(&guard).into() + self.namespacerule + .read_with(&guard) + .to_css_string(&guard) + .into() } } diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index 1a24d5839bf..a541ee411c7 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -1,33 +1,32 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::Reflector; -use dom::bindings::str::DOMString; -use dom::cssfontfacerule::CSSFontFaceRule; -use dom::cssimportrule::CSSImportRule; -use dom::csskeyframerule::CSSKeyframeRule; -use dom::csskeyframesrule::CSSKeyframesRule; -use dom::cssmediarule::CSSMediaRule; -use dom::cssnamespacerule::CSSNamespaceRule; -use dom::cssstylerule::CSSStyleRule; -use dom::cssstylesheet::CSSStyleSheet; -use dom::csssupportsrule::CSSSupportsRule; -use dom::cssviewportrule::CSSViewportRule; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssfontfacerule::CSSFontFaceRule; +use crate::dom::cssimportrule::CSSImportRule; +use crate::dom::csskeyframerule::CSSKeyframeRule; +use crate::dom::csskeyframesrule::CSSKeyframesRule; +use crate::dom::cssmediarule::CSSMediaRule; +use crate::dom::cssnamespacerule::CSSNamespaceRule; +use crate::dom::cssstylerule::CSSStyleRule; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::csssupportsrule::CSSSupportsRule; +use crate::dom::cssviewportrule::CSSViewportRule; +use crate::dom::window::Window; use dom_struct::dom_struct; use std::cell::Cell; use style::shared_lock::SharedRwLock; use style::stylesheets::CssRule as StyleCssRule; - #[dom_struct] pub struct CSSRule { reflector_: Reflector, - parent_stylesheet: JS<CSSStyleSheet>, + parent_stylesheet: Dom<CSSStyleSheet>, /// Whether the parentStyleSheet attribute should return null. /// We keep parent_stylesheet in that case because insertRule needs it @@ -40,30 +39,30 @@ impl CSSRule { pub fn new_inherited(parent_stylesheet: &CSSStyleSheet) -> CSSRule { CSSRule { reflector_: Reflector::new(), - parent_stylesheet: JS::from_ref(parent_stylesheet), + parent_stylesheet: Dom::from_ref(parent_stylesheet), parent_stylesheet_removed: Cell::new(false), } } - pub fn as_specific(&self) -> &SpecificCSSRule { + pub fn as_specific(&self) -> &dyn SpecificCSSRule { if let Some(rule) = self.downcast::<CSSStyleRule>() { - rule as &SpecificCSSRule + rule as &dyn SpecificCSSRule } else if let Some(rule) = self.downcast::<CSSFontFaceRule>() { - rule as &SpecificCSSRule + rule as &dyn SpecificCSSRule } else if let Some(rule) = self.downcast::<CSSKeyframesRule>() { - rule as &SpecificCSSRule + rule as &dyn SpecificCSSRule } else if let Some(rule) = self.downcast::<CSSMediaRule>() { - rule as &SpecificCSSRule + rule as &dyn SpecificCSSRule } else if let Some(rule) = self.downcast::<CSSNamespaceRule>() { - rule as &SpecificCSSRule + rule as &dyn SpecificCSSRule } else if let Some(rule) = self.downcast::<CSSViewportRule>() { - rule as &SpecificCSSRule + rule as &dyn SpecificCSSRule } else if let Some(rule) = self.downcast::<CSSKeyframeRule>() { - rule as &SpecificCSSRule + rule as &dyn SpecificCSSRule } else if let Some(rule) = self.downcast::<CSSImportRule>() { - rule as &SpecificCSSRule + rule as &dyn SpecificCSSRule } else if let Some(rule) = self.downcast::<CSSSupportsRule>() { - rule as &SpecificCSSRule + rule as &dyn SpecificCSSRule } else { unreachable!() } @@ -71,19 +70,41 @@ impl CSSRule { // Given a StyleCssRule, create a new instance of a derived class of // CSSRule based on which rule it is - pub fn new_specific(window: &Window, parent_stylesheet: &CSSStyleSheet, - rule: StyleCssRule) -> Root<CSSRule> { + pub fn new_specific( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + rule: StyleCssRule, + ) -> DomRoot<CSSRule> { // be sure to update the match in as_specific when this is updated match rule { - StyleCssRule::Import(s) => Root::upcast(CSSImportRule::new(window, parent_stylesheet, s)), - StyleCssRule::Style(s) => Root::upcast(CSSStyleRule::new(window, parent_stylesheet, s)), - StyleCssRule::FontFace(s) => Root::upcast(CSSFontFaceRule::new(window, parent_stylesheet, s)), - StyleCssRule::Keyframes(s) => Root::upcast(CSSKeyframesRule::new(window, parent_stylesheet, s)), - StyleCssRule::Media(s) => Root::upcast(CSSMediaRule::new(window, parent_stylesheet, s)), - StyleCssRule::Namespace(s) => Root::upcast(CSSNamespaceRule::new(window, parent_stylesheet, s)), - StyleCssRule::Viewport(s) => Root::upcast(CSSViewportRule::new(window, parent_stylesheet, s)), - StyleCssRule::Supports(s) => Root::upcast(CSSSupportsRule::new(window, parent_stylesheet, s)), + StyleCssRule::Import(s) => { + DomRoot::upcast(CSSImportRule::new(window, parent_stylesheet, s)) + }, + StyleCssRule::Style(s) => { + DomRoot::upcast(CSSStyleRule::new(window, parent_stylesheet, s)) + }, + StyleCssRule::FontFace(s) => { + DomRoot::upcast(CSSFontFaceRule::new(window, parent_stylesheet, s)) + }, + StyleCssRule::FontFeatureValues(_) => unimplemented!(), + StyleCssRule::CounterStyle(_) => unimplemented!(), + StyleCssRule::Keyframes(s) => { + DomRoot::upcast(CSSKeyframesRule::new(window, parent_stylesheet, s)) + }, + StyleCssRule::Media(s) => { + DomRoot::upcast(CSSMediaRule::new(window, parent_stylesheet, s)) + }, + StyleCssRule::Namespace(s) => { + DomRoot::upcast(CSSNamespaceRule::new(window, parent_stylesheet, s)) + }, + StyleCssRule::Viewport(s) => { + DomRoot::upcast(CSSViewportRule::new(window, parent_stylesheet, s)) + }, + StyleCssRule::Supports(s) => { + DomRoot::upcast(CSSSupportsRule::new(window, parent_stylesheet, s)) + }, StyleCssRule::Page(_) => unreachable!(), + StyleCssRule::Document(_) => unimplemented!(), // TODO } } @@ -118,11 +139,11 @@ impl CSSRuleMethods for CSSRule { } // https://drafts.csswg.org/cssom/#dom-cssrule-parentstylesheet - fn GetParentStyleSheet(&self) -> Option<Root<CSSStyleSheet>> { + fn GetParentStyleSheet(&self) -> Option<DomRoot<CSSStyleSheet>> { if self.parent_stylesheet_removed.get() { None } else { - Some(Root::from_ref(&*self.parent_stylesheet)) + Some(DomRoot::from_ref(&*self.parent_stylesheet)) } } diff --git a/components/script/dom/cssrulelist.rs b/components/script/dom/cssrulelist.rs index 67eac2beda2..d4fde8ae308 100644 --- a/components/script/dom/cssrulelist.rs +++ b/components/script/dom/cssrulelist.rs @@ -1,21 +1,25 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::CSSRuleListBinding; -use dom::bindings::codegen::Bindings::CSSRuleListBinding::CSSRuleListMethods; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::csskeyframerule::CSSKeyframeRule; -use dom::cssrule::CSSRule; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::CSSRuleListBinding::CSSRuleListMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::csskeyframerule::CSSKeyframeRule; +use crate::dom::cssrule::CSSRule; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::window::Window; +use crate::style::stylesheets::StylesheetLoader as StyleStylesheetLoader; +use crate::stylesheet_loader::StylesheetLoader; use dom_struct::dom_struct; -use std::sync::Arc; +use servo_arc::Arc; use style::shared_lock::Locked; -use style::stylesheets::{CssRules, CssRulesHelpers, KeyframesRule, RulesMutateError}; +use style::stylesheets::{ + AllowImportRules, CssRules, CssRulesHelpers, KeyframesRule, RulesMutateError, +}; #[allow(unsafe_code)] unsafe_no_jsmanaged_fields!(RulesSource); @@ -36,10 +40,10 @@ impl From<RulesMutateError> for Error { #[dom_struct] pub struct CSSRuleList { reflector_: Reflector, - parent_stylesheet: JS<CSSStyleSheet>, - #[ignore_heap_size_of = "Arc"] + parent_stylesheet: Dom<CSSStyleSheet>, + #[ignore_malloc_size_of = "Arc"] rules: RulesSource, - dom_rules: DOMRefCell<Vec<MutNullableJS<CSSRule>>> + dom_rules: DomRefCell<Vec<MutNullableDom<CSSRule>>>, } pub enum RulesSource { @@ -52,28 +56,38 @@ impl CSSRuleList { pub fn new_inherited(parent_stylesheet: &CSSStyleSheet, rules: RulesSource) -> CSSRuleList { let guard = parent_stylesheet.shared_lock().read(); let dom_rules = match rules { - RulesSource::Rules(ref rules) => { - rules.read_with(&guard).0.iter().map(|_| MutNullableJS::new(None)).collect() - } - RulesSource::Keyframes(ref rules) => { - rules.read_with(&guard).keyframes.iter().map(|_| MutNullableJS::new(None)).collect() - } + RulesSource::Rules(ref rules) => rules + .read_with(&guard) + .0 + .iter() + .map(|_| MutNullableDom::new(None)) + .collect(), + RulesSource::Keyframes(ref rules) => rules + .read_with(&guard) + .keyframes + .iter() + .map(|_| MutNullableDom::new(None)) + .collect(), }; CSSRuleList { reflector_: Reflector::new(), - parent_stylesheet: JS::from_ref(parent_stylesheet), + parent_stylesheet: Dom::from_ref(parent_stylesheet), rules: rules, - dom_rules: DOMRefCell::new(dom_rules), + dom_rules: DomRefCell::new(dom_rules), } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - rules: RulesSource) -> Root<CSSRuleList> { - reflect_dom_object(box CSSRuleList::new_inherited(parent_stylesheet, rules), - window, - CSSRuleListBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + rules: RulesSource, + ) -> DomRoot<CSSRuleList> { + reflect_dom_object( + Box::new(CSSRuleList::new_inherited(parent_stylesheet, rules)), + window, + ) } /// Should only be called for CssRules-backed rules. Use append_lazy_rule @@ -90,18 +104,32 @@ impl CSSRuleList { let index = idx as usize; let parent_stylesheet = self.parent_stylesheet.style_stylesheet(); - let new_rule = - css_rules.insert_rule(&parent_stylesheet.shared_lock, - rule, - parent_stylesheet, - index, - nested, - None)?; + let owner = self + .parent_stylesheet + .get_owner() + .map(DomRoot::downcast::<HTMLElement>) + .flatten(); + let loader = owner + .as_ref() + .map(|element| StylesheetLoader::for_element(&**element)); + let new_rule = css_rules.with_raw_offset_arc(|arc| { + arc.insert_rule( + &parent_stylesheet.shared_lock, + rule, + &parent_stylesheet.contents, + index, + nested, + loader.as_ref().map(|l| l as &dyn StyleStylesheetLoader), + AllowImportRules::Yes, + ) + })?; let parent_stylesheet = &*self.parent_stylesheet; let dom_rule = CSSRule::new_specific(&window, parent_stylesheet, new_rule); - self.dom_rules.borrow_mut().insert(index, MutNullableJS::new(Some(&*dom_rule))); - Ok((idx)) + self.dom_rules + .borrow_mut() + .insert(index, MutNullableDom::new(Some(&*dom_rule))); + Ok(idx) } // In case of a keyframe rule, index must be valid. @@ -116,7 +144,7 @@ impl CSSRuleList { dom_rules[index].get().map(|r| r.detach()); dom_rules.remove(index); Ok(()) - } + }, RulesSource::Keyframes(ref kf) => { // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-deleterule let mut dom_rules = self.dom_rules.borrow_mut(); @@ -124,37 +152,34 @@ impl CSSRuleList { dom_rules.remove(index); kf.write_with(&mut guard).keyframes.remove(index); Ok(()) - } + }, } } // Remove parent stylesheets from all children pub fn deparent_all(&self) { for rule in self.dom_rules.borrow().iter() { - rule.get().map(|r| Root::upcast(r).deparent()); + rule.get().map(|r| DomRoot::upcast(r).deparent()); } } - pub fn item(&self, idx: u32) -> Option<Root<CSSRule>> { + pub fn item(&self, idx: u32) -> Option<DomRoot<CSSRule>> { self.dom_rules.borrow().get(idx as usize).map(|rule| { rule.or_init(|| { let parent_stylesheet = &self.parent_stylesheet; let guard = parent_stylesheet.shared_lock().read(); match self.rules { - RulesSource::Rules(ref rules) => { - CSSRule::new_specific(self.global().as_window(), - parent_stylesheet, - rules.read_with(&guard).0[idx as usize].clone()) - } - RulesSource::Keyframes(ref rules) => { - Root::upcast(CSSKeyframeRule::new(self.global().as_window(), - parent_stylesheet, - rules.read_with(&guard) - .keyframes[idx as usize] - .clone())) - } + RulesSource::Rules(ref rules) => CSSRule::new_specific( + self.global().as_window(), + parent_stylesheet, + rules.read_with(&guard).0[idx as usize].clone(), + ), + RulesSource::Keyframes(ref rules) => DomRoot::upcast(CSSKeyframeRule::new( + self.global().as_window(), + parent_stylesheet, + rules.read_with(&guard).keyframes[idx as usize].clone(), + )), } - }) }) } @@ -168,13 +193,13 @@ impl CSSRuleList { if let RulesSource::Rules(..) = self.rules { panic!("Can only call append_lazy_rule with keyframes-backed CSSRules"); } - self.dom_rules.borrow_mut().push(MutNullableJS::new(None)); + self.dom_rules.borrow_mut().push(MutNullableDom::new(None)); } } impl CSSRuleListMethods for CSSRuleList { // https://drafts.csswg.org/cssom/#ref-for-dom-cssrulelist-item-1 - fn Item(&self, idx: u32) -> Option<Root<CSSRule>> { + fn Item(&self, idx: u32) -> Option<DomRoot<CSSRule>> { self.item(idx) } @@ -184,8 +209,7 @@ impl CSSRuleListMethods for CSSRuleList { } // check-tidy: no specs after this line - fn IndexedGetter(&self, index: u32) -> Option<Root<CSSRule>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<CSSRule>> { self.Item(index) } } - diff --git a/components/script/dom/cssstyledeclaration.rs b/components/script/dom/cssstyledeclaration.rs index 22fd1194034..41101afd625 100644 --- a/components/script/dom/cssstyledeclaration.rs +++ b/components/script/dom/cssstyledeclaration.rs @@ -1,29 +1,32 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::{self, CSSStyleDeclarationMethods}; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::cssrule::CSSRule; -use dom::element::Element; -use dom::node::{Node, window_from_node, document_from_node}; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssrule::CSSRule; +use crate::dom::element::Element; +use crate::dom::node::{document_from_node, stylesheets_owner_from_node, window_from_node, Node}; +use crate::dom::window::Window; use dom_struct::dom_struct; +use servo_arc::Arc; use servo_url::ServoUrl; -use std::ascii::AsciiExt; -use std::sync::Arc; use style::attr::AttrValue; -use style::parser::LengthParsingMode; -use style::properties::{Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId}; -use style::properties::{parse_one_declaration, parse_style_attribute}; +use style::properties::{ + parse_one_declaration_into, parse_style_attribute, SourcePropertyDeclaration, +}; +use style::properties::{ + Importance, LonghandId, PropertyDeclarationBlock, PropertyId, ShorthandId, +}; use style::selector_parser::PseudoElement; use style::shared_lock::Locked; -use style_traits::ToCss; +use style::stylesheets::{CssRuleType, Origin}; +use style_traits::ParsingMode; // http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface #[dom_struct] @@ -34,20 +37,22 @@ pub struct CSSStyleDeclaration { pseudo: Option<PseudoElement>, } -#[derive(HeapSizeOf, JSTraceable)] -#[must_root] +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] pub enum CSSStyleOwner { - Element(JS<Element>), - CSSRule(JS<CSSRule>, - #[ignore_heap_size_of = "Arc"] - Arc<Locked<PropertyDeclarationBlock>>), + Element(Dom<Element>), + CSSRule( + Dom<CSSRule>, + #[ignore_malloc_size_of = "Arc"] Arc<Locked<PropertyDeclarationBlock>>, + ), } impl CSSStyleOwner { // Mutate the declaration block associated to this style owner, and // optionally indicate if it has changed (assumed to be true). fn mutate_associated_block<F, R>(&self, f: F) -> R - where F: FnOnce(&mut PropertyDeclarationBlock, &mut bool) -> R, + where + F: FnOnce(&mut PropertyDeclarationBlock, &mut bool) -> R, { // TODO(emilio): This has some duplication just to avoid dummy clones. // @@ -87,10 +92,12 @@ impl CSSStyleOwner { // [1]: https://github.com/whatwg/html/issues/2306 if let Some(pdb) = attr { let guard = shared_lock.read(); - let serialization = pdb.read_with(&guard).to_css_string(); - el.set_attribute(&local_name!("style"), - AttrValue::Declaration(serialization, - pdb)); + let mut serialization = String::new(); + pdb.read_with(&guard).to_css(&mut serialization).unwrap(); + el.set_attribute( + &local_name!("style"), + AttrValue::Declaration(serialization, pdb), + ); } } else { // Remember to put it back. @@ -98,62 +105,70 @@ impl CSSStyleOwner { } result - } + }, CSSStyleOwner::CSSRule(ref rule, ref pdb) => { let result = { let mut guard = rule.shared_lock().write(); f(&mut *pdb.write_with(&mut guard), &mut changed) }; if changed { - rule.global().as_window().Document().invalidate_stylesheets(); + // If this is changed, see also + // CSSStyleRule::SetSelectorText, which does the same thing. + if let Some(owner) = rule.parent_stylesheet().get_owner() { + stylesheets_owner_from_node(owner.upcast::<Node>()) + .invalidate_stylesheets(); + } } result - } + }, } } fn with_block<F, R>(&self, f: F) -> R - where F: FnOnce(&PropertyDeclarationBlock) -> R, + where + F: FnOnce(&PropertyDeclarationBlock) -> R, { match *self { - CSSStyleOwner::Element(ref el) => { - match *el.style_attribute().borrow() { - Some(ref pdb) => { - let document = document_from_node(&**el); - let guard = document.style_shared_lock().read(); - f(pdb.read_with(&guard)) - } - None => { - let pdb = PropertyDeclarationBlock::new(); - f(&pdb) - } - } - } + CSSStyleOwner::Element(ref el) => match *el.style_attribute().borrow() { + Some(ref pdb) => { + let document = document_from_node(&**el); + let guard = document.style_shared_lock().read(); + f(pdb.read_with(&guard)) + }, + None => { + let pdb = PropertyDeclarationBlock::new(); + f(&pdb) + }, + }, CSSStyleOwner::CSSRule(ref rule, ref pdb) => { let guard = rule.shared_lock().read(); f(pdb.read_with(&guard)) - } + }, } } - fn window(&self) -> Root<Window> { + fn window(&self) -> DomRoot<Window> { match *self { CSSStyleOwner::Element(ref el) => window_from_node(&**el), - CSSStyleOwner::CSSRule(ref rule, _) => Root::from_ref(rule.global().as_window()), + CSSStyleOwner::CSSRule(ref rule, _) => DomRoot::from_ref(rule.global().as_window()), } } fn base_url(&self) -> ServoUrl { match *self { CSSStyleOwner::Element(ref el) => window_from_node(&**el).Document().base_url(), - CSSStyleOwner::CSSRule(ref rule, _) => { - rule.parent_stylesheet().style_stylesheet().url_data.clone() - } + CSSStyleOwner::CSSRule(ref rule, _) => (*rule + .parent_stylesheet() + .style_stylesheet() + .contents + .url_data + .read()) + .clone(), } } } -#[derive(PartialEq, HeapSizeOf)] +#[derive(MallocSizeOf, PartialEq)] pub enum CSSModificationAccess { ReadWrite, Readonly, @@ -163,21 +178,40 @@ macro_rules! css_properties( ( $([$getter:ident, $setter:ident, $id:expr],)* ) => ( $( fn $getter(&self) -> DOMString { + debug_assert!( + $id.enabled_for_all_content(), + "Someone forgot a #[Pref] annotation" + ); self.get_property_value($id) } fn $setter(&self, value: DOMString) -> ErrorResult { + debug_assert!( + $id.enabled_for_all_content(), + "Someone forgot a #[Pref] annotation" + ); self.set_property($id, value, DOMString::new()) } )* ); ); +fn remove_property(decls: &mut PropertyDeclarationBlock, id: &PropertyId) -> bool { + let first_declaration = decls.first_declaration_to_remove(id); + let first_declaration = match first_declaration { + Some(i) => i, + None => return false, + }; + decls.remove_property(id, first_declaration); + true +} + impl CSSStyleDeclaration { #[allow(unrooted_must_root)] - pub fn new_inherited(owner: CSSStyleOwner, - pseudo: Option<PseudoElement>, - modification_access: CSSModificationAccess) - -> CSSStyleDeclaration { + pub fn new_inherited( + owner: CSSStyleOwner, + pseudo: Option<PseudoElement>, + modification_access: CSSModificationAccess, + ) -> CSSStyleDeclaration { CSSStyleDeclaration { reflector_: Reflector::new(), owner: owner, @@ -187,32 +221,35 @@ impl CSSStyleDeclaration { } #[allow(unrooted_must_root)] - pub fn new(global: &Window, - owner: CSSStyleOwner, - pseudo: Option<PseudoElement>, - modification_access: CSSModificationAccess) - -> Root<CSSStyleDeclaration> { - reflect_dom_object(box CSSStyleDeclaration::new_inherited(owner, - pseudo, - modification_access), - global, - CSSStyleDeclarationBinding::Wrap) + pub fn new( + global: &Window, + owner: CSSStyleOwner, + pseudo: Option<PseudoElement>, + modification_access: CSSModificationAccess, + ) -> DomRoot<CSSStyleDeclaration> { + reflect_dom_object( + Box::new(CSSStyleDeclaration::new_inherited( + owner, + pseudo, + modification_access, + )), + global, + ) } fn get_computed_style(&self, property: PropertyId) -> DOMString { match self.owner { - CSSStyleOwner::CSSRule(..) => - panic!("get_computed_style called on CSSStyleDeclaration with a CSSRule owner"), + CSSStyleOwner::CSSRule(..) => { + panic!("get_computed_style called on CSSStyleDeclaration with a CSSRule owner") + }, CSSStyleOwner::Element(ref el) => { let node = el.upcast::<Node>(); - if !node.is_in_doc() { - // TODO: Node should be matched against the style rules of this window. - // Firefox is currently the only browser to implement this. + if !node.is_connected() { return DOMString::new(); } let addr = node.to_trusted_node_address(); window_from_node(node).resolved_style_query(addr, self.pseudo.clone(), property) - } + }, } } @@ -237,41 +274,62 @@ impl CSSStyleDeclaration { return Err(Error::NoModificationAllowed); } - self.owner.mutate_associated_block(|ref mut pdb, mut changed| { + if !id.enabled_for_all_content() { + return Ok(()); + } + + self.owner.mutate_associated_block(|pdb, changed| { if value.is_empty() { - // Step 4 - *changed = pdb.remove_property(&id); + // Step 3 + *changed = remove_property(pdb, &id); return Ok(()); } - // Step 5 + // Step 4 let importance = match &*priority { "" => Importance::Normal, p if p.eq_ignore_ascii_case("important") => Importance::Important, _ => { *changed = false; return Ok(()); - } + }, }; - // Step 6 + // Step 5 let window = self.owner.window(); - let result = - parse_one_declaration(id, &value, &self.owner.base_url(), - window.css_error_reporter(), LengthParsingMode::Default); + let quirks_mode = window.Document().quirks_mode(); + let mut declarations = SourcePropertyDeclaration::new(); + let result = parse_one_declaration_into( + &mut declarations, + id, + &value, + Origin::Author, + &self.owner.base_url(), + window.css_error_reporter(), + ParsingMode::DEFAULT, + quirks_mode, + CssRuleType::Style, + ); - // Step 7 - let parsed = match result { - Ok(parsed) => parsed, + // Step 6 + match result { + Ok(()) => {}, Err(_) => { *changed = false; return Ok(()); - } - }; + }, + } + let mut updates = Default::default(); + *changed = pdb.prepare_for_update(&declarations, importance, &mut updates); + + if !*changed { + return Ok(()); + } + + // Step 7 // Step 8 - // Step 9 - *changed = parsed.expand_set_into(pdb, importance); + pdb.update(declarations.drain(), importance, &mut updates); Ok(()) }) @@ -281,9 +339,7 @@ impl CSSStyleDeclaration { impl CSSStyleDeclarationMethods for CSSStyleDeclaration { // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-length fn Length(&self) -> u32 { - self.owner.with_block(|pdb| { - pdb.declarations().len() as u32 - }) + self.owner.with_block(|pdb| pdb.declarations().len() as u32) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item @@ -293,22 +349,18 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue fn GetPropertyValue(&self, property: DOMString) -> DOMString { - let id = if let Ok(id) = PropertyId::parse(property.into()) { - id - } else { - // Unkwown property - return DOMString::new() + let id = match PropertyId::parse_enabled_for_all_content(&property) { + Ok(id) => id, + Err(..) => return DOMString::new(), }; self.get_property_value(id) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority fn GetPropertyPriority(&self, property: DOMString) -> DOMString { - let id = if let Ok(id) = PropertyId::parse(property.into()) { - id - } else { - // Unkwown property - return DOMString::new() + let id = match PropertyId::parse_enabled_for_all_content(&property) { + Ok(id) => id, + Err(..) => return DOMString::new(), }; self.owner.with_block(|pdb| { @@ -322,52 +374,18 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty - fn SetProperty(&self, - property: DOMString, - value: DOMString, - priority: DOMString) - -> ErrorResult { + fn SetProperty( + &self, + property: DOMString, + value: DOMString, + priority: DOMString, + ) -> ErrorResult { // Step 3 - let id = if let Ok(id) = PropertyId::parse(property.into()) { - id - } else { - // Unknown property - return Ok(()) - }; - self.set_property(id, value, priority) - } - - // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertypriority - fn SetPropertyPriority(&self, property: DOMString, priority: DOMString) -> ErrorResult { - // Step 1 - if self.readonly { - return Err(Error::NoModificationAllowed); - } - - // Step 2 & 3 - let id = match PropertyId::parse(property.into()) { + let id = match PropertyId::parse_enabled_for_all_content(&property) { Ok(id) => id, - Err(..) => return Ok(()), // Unkwown property - }; - - // Step 4 - let importance = match &*priority { - "" => Importance::Normal, - p if p.eq_ignore_ascii_case("important") => Importance::Important, - _ => return Ok(()), + Err(..) => return Ok(()), }; - - self.owner.mutate_associated_block(|ref mut pdb, mut changed| { - // Step 5 & 6 - *changed = pdb.set_importance(&id, importance); - }); - - Ok(()) - } - - // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertyvalue - fn SetPropertyValue(&self, property: DOMString, value: DOMString) -> ErrorResult { - self.SetProperty(property, value, DOMString::new()) + self.set_property(id, value, priority) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty @@ -377,17 +395,15 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { return Err(Error::NoModificationAllowed); } - let id = if let Ok(id) = PropertyId::parse(property.into()) { - id - } else { - // Unkwown property, cannot be there to remove. - return Ok(DOMString::new()) + let id = match PropertyId::parse_enabled_for_all_content(&property) { + Ok(id) => id, + Err(..) => return Ok(DOMString::new()), }; let mut string = String::new(); - self.owner.mutate_associated_block(|mut pdb, mut changed| { + self.owner.mutate_associated_block(|pdb, changed| { pdb.property_value_to_css(&id, &mut string).unwrap(); - *changed = pdb.remove_property(&id); + *changed = remove_property(pdb, &id); }); // Step 6 @@ -396,32 +412,38 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat fn CssFloat(&self) -> DOMString { - self.GetPropertyValue(DOMString::from("float")) + self.get_property_value(PropertyId::Longhand(LonghandId::Float)) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat fn SetCssFloat(&self, value: DOMString) -> ErrorResult { - self.SetPropertyValue(DOMString::from("float"), value) + self.set_property( + PropertyId::Longhand(LonghandId::Float), + value, + DOMString::new(), + ) } // https://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface fn IndexedGetter(&self, index: u32) -> Option<DOMString> { self.owner.with_block(|pdb| { - pdb.declarations().get(index as usize).map(|entry| { - let (ref declaration, importance) = *entry; - let mut css = declaration.to_css_string(); - if importance.important() { - css += " !important"; - } - DOMString::from(css) - }) + let declaration = pdb.declarations().get(index as usize)?; + let important = pdb.declarations_importance().get(index as usize)?; + let mut css = String::new(); + declaration.to_css(&mut css).unwrap(); + if important { + css += " !important"; + } + Some(DOMString::from(css)) }) } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext fn CssText(&self) -> DOMString { self.owner.with_block(|pdb| { - DOMString::from(pdb.to_css_string()) + let mut serialization = String::new(); + pdb.to_css(&mut serialization).unwrap(); + DOMString::from(serialization) }) } @@ -434,11 +456,16 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { return Err(Error::NoModificationAllowed); } - self.owner.mutate_associated_block(|mut pdb, mut _changed| { + let quirks_mode = window.Document().quirks_mode(); + self.owner.mutate_associated_block(|pdb, _changed| { // Step 3 - *pdb = parse_style_attribute(&value, - &self.owner.base_url(), - window.css_error_reporter()); + *pdb = parse_style_attribute( + &value, + &self.owner.base_url(), + window.css_error_reporter(), + quirks_mode, + CssRuleType::Style, + ); }); Ok(()) diff --git a/components/script/dom/cssstylerule.rs b/components/script/dom/cssstylerule.rs index fed2b947f90..a84c5c598b7 100644 --- a/components/script/dom/cssstylerule.rs +++ b/components/script/dom/cssstylerule.rs @@ -1,32 +1,40 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CSSStyleRuleBinding::{self, CSSStyleRuleMethods}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::cssrule::{CSSRule, SpecificCSSRule}; -use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::CSSStyleRuleBinding::CSSStyleRuleMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssrule::{CSSRule, SpecificCSSRule}; +use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::node::{stylesheets_owner_from_node, Node}; +use crate::dom::window::Window; +use cssparser::ToCss; +use cssparser::{Parser as CssParser, ParserInput as CssParserInput}; use dom_struct::dom_struct; -use std::sync::Arc; +use selectors::parser::SelectorList; +use servo_arc::Arc; +use std::mem; +use style::selector_parser::SelectorParser; use style::shared_lock::{Locked, ToCssWithGuard}; -use style::stylesheets::StyleRule; +use style::stylesheets::{Origin, StyleRule}; #[dom_struct] pub struct CSSStyleRule { cssrule: CSSRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] stylerule: Arc<Locked<StyleRule>>, - style_decl: MutNullableJS<CSSStyleDeclaration>, + style_decl: MutNullableDom<CSSStyleDeclaration>, } impl CSSStyleRule { - fn new_inherited(parent_stylesheet: &CSSStyleSheet, stylerule: Arc<Locked<StyleRule>>) - -> CSSStyleRule { + fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + stylerule: Arc<Locked<StyleRule>>, + ) -> CSSStyleRule { CSSStyleRule { cssrule: CSSRule::new_inherited(parent_stylesheet), stylerule: stylerule, @@ -35,40 +43,83 @@ impl CSSStyleRule { } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - stylerule: Arc<Locked<StyleRule>>) -> Root<CSSStyleRule> { - reflect_dom_object(box CSSStyleRule::new_inherited(parent_stylesheet, stylerule), - window, - CSSStyleRuleBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + stylerule: Arc<Locked<StyleRule>>, + ) -> DomRoot<CSSStyleRule> { + reflect_dom_object( + Box::new(CSSStyleRule::new_inherited(parent_stylesheet, stylerule)), + window, + ) } } impl SpecificCSSRule for CSSStyleRule { fn ty(&self) -> u16 { - use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; CSSRuleConstants::STYLE_RULE } fn get_css(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - self.stylerule.read_with(&guard).to_css_string(&guard).into() + self.stylerule + .read_with(&guard) + .to_css_string(&guard) + .into() } } impl CSSStyleRuleMethods for CSSStyleRule { // https://drafts.csswg.org/cssom/#dom-cssstylerule-style - fn Style(&self) -> Root<CSSStyleDeclaration> { + fn Style(&self) -> DomRoot<CSSStyleDeclaration> { self.style_decl.or_init(|| { let guard = self.cssrule.shared_lock().read(); CSSStyleDeclaration::new( self.global().as_window(), CSSStyleOwner::CSSRule( - JS::from_ref(self.upcast()), - self.stylerule.read_with(&guard).block.clone() + Dom::from_ref(self.upcast()), + self.stylerule.read_with(&guard).block.clone(), ), None, - CSSModificationAccess::ReadWrite + CSSModificationAccess::ReadWrite, ) }) } + + // https://drafts.csswg.org/cssom/#dom-cssstylerule-selectortext + fn SelectorText(&self) -> DOMString { + let guard = self.cssrule.shared_lock().read(); + let stylerule = self.stylerule.read_with(&guard); + return DOMString::from_string(stylerule.selectors.to_css_string()); + } + + // https://drafts.csswg.org/cssom/#dom-cssstylerule-selectortext + fn SetSelectorText(&self, value: DOMString) { + // It's not clear from the spec if we should use the stylesheet's namespaces. + // https://github.com/w3c/csswg-drafts/issues/1511 + let namespaces = self + .cssrule + .parent_stylesheet() + .style_stylesheet() + .contents + .namespaces + .read(); + let parser = SelectorParser { + stylesheet_origin: Origin::Author, + namespaces: &namespaces, + url_data: None, + }; + let mut css_parser = CssParserInput::new(&*value); + let mut css_parser = CssParser::new(&mut css_parser); + if let Ok(mut s) = SelectorList::parse(&parser, &mut css_parser) { + // This mirrors what we do in CSSStyleOwner::mutate_associated_block. + let mut guard = self.cssrule.shared_lock().write(); + let stylerule = self.stylerule.write_with(&mut guard); + mem::swap(&mut stylerule.selectors, &mut s); + if let Some(owner) = self.cssrule.parent_stylesheet().get_owner() { + stylesheets_owner_from_node(owner.upcast::<Node>()).invalidate_stylesheets(); + } + } + } } diff --git a/components/script/dom/cssstylesheet.rs b/components/script/dom/cssstylesheet.rs index ee097313c2e..dda321be153 100644 --- a/components/script/dom/cssstylesheet.rs +++ b/components/script/dom/cssstylesheet.rs @@ -1,78 +1,95 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::CSSStyleSheetBinding; -use dom::bindings::codegen::Bindings::CSSStyleSheetBinding::CSSStyleSheetMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::reflector::{reflect_dom_object, DomObject}; -use dom::bindings::str::DOMString; -use dom::cssrulelist::{CSSRuleList, RulesSource}; -use dom::element::Element; -use dom::stylesheet::StyleSheet; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::CSSStyleSheetMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssrulelist::{CSSRuleList, RulesSource}; +use crate::dom::element::Element; +use crate::dom::medialist::MediaList; +use crate::dom::node::{stylesheets_owner_from_node, Node}; +use crate::dom::stylesheet::StyleSheet; +use crate::dom::window::Window; use dom_struct::dom_struct; +use servo_arc::Arc; use std::cell::Cell; -use std::sync::Arc; use style::shared_lock::SharedRwLock; use style::stylesheets::Stylesheet as StyleStyleSheet; #[dom_struct] pub struct CSSStyleSheet { stylesheet: StyleSheet, - owner: JS<Element>, - rulelist: MutNullableJS<CSSRuleList>, - #[ignore_heap_size_of = "Arc"] + owner: MutNullableDom<Element>, + rulelist: MutNullableDom<CSSRuleList>, + #[ignore_malloc_size_of = "Arc"] style_stylesheet: Arc<StyleStyleSheet>, origin_clean: Cell<bool>, } impl CSSStyleSheet { - fn new_inherited(owner: &Element, - type_: DOMString, - href: Option<DOMString>, - title: Option<DOMString>, - stylesheet: Arc<StyleStyleSheet>) -> CSSStyleSheet { + fn new_inherited( + owner: &Element, + type_: DOMString, + href: Option<DOMString>, + title: Option<DOMString>, + stylesheet: Arc<StyleStyleSheet>, + ) -> CSSStyleSheet { CSSStyleSheet { stylesheet: StyleSheet::new_inherited(type_, href, title), - owner: JS::from_ref(owner), - rulelist: MutNullableJS::new(None), + owner: MutNullableDom::new(Some(owner)), + rulelist: MutNullableDom::new(None), style_stylesheet: stylesheet, origin_clean: Cell::new(true), } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, - owner: &Element, - type_: DOMString, - href: Option<DOMString>, - title: Option<DOMString>, - stylesheet: Arc<StyleStyleSheet>) -> Root<CSSStyleSheet> { - reflect_dom_object(box CSSStyleSheet::new_inherited(owner, type_, href, title, stylesheet), - window, - CSSStyleSheetBinding::Wrap) + pub fn new( + window: &Window, + owner: &Element, + type_: DOMString, + href: Option<DOMString>, + title: Option<DOMString>, + stylesheet: Arc<StyleStyleSheet>, + ) -> DomRoot<CSSStyleSheet> { + reflect_dom_object( + Box::new(CSSStyleSheet::new_inherited( + owner, type_, href, title, stylesheet, + )), + window, + ) } - fn rulelist(&self) -> Root<CSSRuleList> { - self.rulelist.or_init(|| CSSRuleList::new(self.global().as_window(), - self, - RulesSource::Rules(self.style_stylesheet - .rules.clone()))) + fn rulelist(&self) -> DomRoot<CSSRuleList> { + self.rulelist.or_init(|| { + let rules = self.style_stylesheet.contents.rules.clone(); + CSSRuleList::new(self.global().as_window(), self, RulesSource::Rules(rules)) + }) } pub fn disabled(&self) -> bool { self.style_stylesheet.disabled() } + pub fn get_owner(&self) -> Option<DomRoot<Element>> { + self.owner.get() + } + pub fn set_disabled(&self, disabled: bool) { - if self.style_stylesheet.set_disabled(disabled) { - self.global().as_window().Document().invalidate_stylesheets(); + if self.style_stylesheet.set_disabled(disabled) && self.get_owner().is_some() { + stylesheets_owner_from_node(self.get_owner().unwrap().upcast::<Node>()) + .invalidate_stylesheets(); } } + pub fn set_owner(&self, value: Option<&Element>) { + self.owner.set(value); + } + pub fn shared_lock(&self) -> &SharedRwLock { &self.style_stylesheet.shared_lock } @@ -84,11 +101,19 @@ impl CSSStyleSheet { pub fn set_origin_clean(&self, origin_clean: bool) { self.origin_clean.set(origin_clean); } + + pub fn medialist(&self) -> DomRoot<MediaList> { + MediaList::new( + self.global().as_window(), + self, + self.style_stylesheet().media.clone(), + ) + } } impl CSSStyleSheetMethods for CSSStyleSheet { // https://drafts.csswg.org/cssom/#dom-cssstylesheet-cssrules - fn GetCssRules(&self) -> Fallible<Root<CSSRuleList>> { + fn GetCssRules(&self) -> Fallible<DomRoot<CSSRuleList>> { if !self.origin_clean.get() { return Err(Error::Security); } @@ -100,7 +125,8 @@ impl CSSStyleSheetMethods for CSSStyleSheet { if !self.origin_clean.get() { return Err(Error::Security); } - self.rulelist().insert_rule(&rule, index, /* nested */ false) + self.rulelist() + .insert_rule(&rule, index, /* nested */ false) } // https://drafts.csswg.org/cssom/#dom-cssstylesheet-deleterule @@ -111,4 +137,3 @@ impl CSSStyleSheetMethods for CSSStyleSheet { self.rulelist().remove_rule(index) } } - diff --git a/components/script/dom/cssstylevalue.rs b/components/script/dom/cssstylevalue.rs new file mode 100644 index 00000000000..f272b34e945 --- /dev/null +++ b/components/script/dom/cssstylevalue.rs @@ -0,0 +1,55 @@ +/* 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 crate::dom::bindings::codegen::Bindings::CSSStyleValueBinding::CSSStyleValueMethods; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use cssparser::Parser; +use cssparser::ParserInput; +use dom_struct::dom_struct; +use servo_url::ServoUrl; + +#[dom_struct] +pub struct CSSStyleValue { + reflector: Reflector, + value: String, +} + +impl CSSStyleValue { + fn new_inherited(value: String) -> CSSStyleValue { + CSSStyleValue { + reflector: Reflector::new(), + value: value, + } + } + + pub fn new(global: &GlobalScope, value: String) -> DomRoot<CSSStyleValue> { + reflect_dom_object(Box::new(CSSStyleValue::new_inherited(value)), global) + } +} + +impl CSSStyleValueMethods for CSSStyleValue { + /// <https://drafts.css-houdini.org/css-typed-om-1/#CSSStyleValue-stringification-behavior> + fn Stringifier(&self) -> DOMString { + DOMString::from(&*self.value) + } +} + +impl CSSStyleValue { + /// Parse the value as a `url()`. + /// TODO: This should really always be an absolute URL, but we currently + /// return relative URLs for computed values, so we pass in a base. + /// <https://github.com/servo/servo/issues/17625> + pub fn get_url(&self, base_url: ServoUrl) -> Option<ServoUrl> { + let mut input = ParserInput::new(&*self.value); + let mut parser = Parser::new(&mut input); + parser + .expect_url() + .ok() + .and_then(|string| base_url.join(&*string).ok()) + } +} diff --git a/components/script/dom/csssupportsrule.rs b/components/script/dom/csssupportsrule.rs index 3ac60a00e06..8a028e9efb2 100644 --- a/components/script/dom/csssupportsrule.rs +++ b/components/script/dom/csssupportsrule.rs @@ -1,35 +1,36 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use cssparser::Parser; -use dom::bindings::codegen::Bindings::CSSSupportsRuleBinding; -use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::cssconditionrule::CSSConditionRule; -use dom::cssrule::SpecificCSSRule; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssconditionrule::CSSConditionRule; +use crate::dom::cssrule::SpecificCSSRule; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::window::Window; +use cssparser::{Parser, ParserInput}; use dom_struct::dom_struct; -use std::sync::Arc; -use style::parser::{LengthParsingMode, ParserContext}; +use servo_arc::Arc; +use style::parser::ParserContext; use style::shared_lock::{Locked, ToCssWithGuard}; -use style::stylesheets::{CssRuleType, SupportsRule}; -use style::supports::SupportsCondition; -use style_traits::ToCss; +use style::stylesheets::supports_rule::SupportsCondition; +use style::stylesheets::{CssRuleType, Origin, SupportsRule}; +use style_traits::{ParsingMode, ToCss}; #[dom_struct] pub struct CSSSupportsRule { cssconditionrule: CSSConditionRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] supportsrule: Arc<Locked<SupportsRule>>, } impl CSSSupportsRule { - fn new_inherited(parent_stylesheet: &CSSStyleSheet, supportsrule: Arc<Locked<SupportsRule>>) - -> CSSSupportsRule { + fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + supportsrule: Arc<Locked<SupportsRule>>, + ) -> CSSSupportsRule { let guard = parent_stylesheet.shared_lock().read(); let list = supportsrule.read_with(&guard).rules.clone(); CSSSupportsRule { @@ -39,31 +40,56 @@ impl CSSSupportsRule { } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - supportsrule: Arc<Locked<SupportsRule>>) -> Root<CSSSupportsRule> { - reflect_dom_object(box CSSSupportsRule::new_inherited(parent_stylesheet, supportsrule), - window, - CSSSupportsRuleBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + supportsrule: Arc<Locked<SupportsRule>>, + ) -> DomRoot<CSSSupportsRule> { + reflect_dom_object( + Box::new(CSSSupportsRule::new_inherited( + parent_stylesheet, + supportsrule, + )), + window, + ) } - /// https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface + /// <https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface> pub fn get_condition_text(&self) -> DOMString { let guard = self.cssconditionrule.shared_lock().read(); let rule = self.supportsrule.read_with(&guard); rule.condition.to_css_string().into() } - /// https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface + /// <https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface> pub fn set_condition_text(&self, text: DOMString) { - let mut input = Parser::new(&text); + let mut input = ParserInput::new(&text); + let mut input = Parser::new(&mut input); let cond = SupportsCondition::parse(&mut input); if let Ok(cond) = cond { let global = self.global(); let win = global.as_window(); let url = win.Document().url(); - let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Supports), - LengthParsingMode::Default); - let enabled = cond.eval(&context); + let quirks_mode = win.Document().quirks_mode(); + let context = ParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Supports), + ParsingMode::DEFAULT, + quirks_mode, + None, + None, + ); + let enabled = { + let namespaces = self + .cssconditionrule + .parent_stylesheet() + .style_stylesheet() + .contents + .namespaces + .read(); + cond.eval(&context, &namespaces) + }; let mut guard = self.cssconditionrule.shared_lock().write(); let rule = self.supportsrule.write_with(&mut guard); rule.condition = cond; @@ -74,12 +100,15 @@ impl CSSSupportsRule { impl SpecificCSSRule for CSSSupportsRule { fn ty(&self) -> u16 { - use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; CSSRuleConstants::SUPPORTS_RULE } fn get_css(&self) -> DOMString { let guard = self.cssconditionrule.shared_lock().read(); - self.supportsrule.read_with(&guard).to_css_string(&guard).into() + self.supportsrule + .read_with(&guard) + .to_css_string(&guard) + .into() } } diff --git a/components/script/dom/cssviewportrule.rs b/components/script/dom/cssviewportrule.rs index 38abf909ff0..088d1b4a75e 100644 --- a/components/script/dom/cssviewportrule.rs +++ b/components/script/dom/cssviewportrule.rs @@ -1,28 +1,30 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CSSViewportRuleBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::cssrule::{CSSRule, SpecificCSSRule}; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssrule::{CSSRule, SpecificCSSRule}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::window::Window; use dom_struct::dom_struct; -use std::sync::Arc; +use servo_arc::Arc; use style::shared_lock::{Locked, ToCssWithGuard}; -use style::viewport::ViewportRule; +use style::stylesheets::ViewportRule; #[dom_struct] pub struct CSSViewportRule { cssrule: CSSRule, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] viewportrule: Arc<Locked<ViewportRule>>, } impl CSSViewportRule { - fn new_inherited(parent_stylesheet: &CSSStyleSheet, viewportrule: Arc<Locked<ViewportRule>>) -> CSSViewportRule { + fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + viewportrule: Arc<Locked<ViewportRule>>, + ) -> CSSViewportRule { CSSViewportRule { cssrule: CSSRule::new_inherited(parent_stylesheet), viewportrule: viewportrule, @@ -30,22 +32,32 @@ impl CSSViewportRule { } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - viewportrule: Arc<Locked<ViewportRule>>) -> Root<CSSViewportRule> { - reflect_dom_object(box CSSViewportRule::new_inherited(parent_stylesheet, viewportrule), - window, - CSSViewportRuleBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + viewportrule: Arc<Locked<ViewportRule>>, + ) -> DomRoot<CSSViewportRule> { + reflect_dom_object( + Box::new(CSSViewportRule::new_inherited( + parent_stylesheet, + viewportrule, + )), + window, + ) } } impl SpecificCSSRule for CSSViewportRule { fn ty(&self) -> u16 { - use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + use crate::dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; CSSRuleConstants::VIEWPORT_RULE } fn get_css(&self) -> DOMString { let guard = self.cssrule.shared_lock().read(); - self.viewportrule.read_with(&guard).to_css_string(&guard).into() + self.viewportrule + .read_with(&guard) + .to_css_string(&guard) + .into() } } diff --git a/components/script/dom/customelementregistry.rs b/components/script/dom/customelementregistry.rs new file mode 100644 index 00000000000..dfba75a2028 --- /dev/null +++ b/components/script/dom/customelementregistry.rs @@ -0,0 +1,1218 @@ +/* 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 crate::dom::bindings::callback::{CallbackContainer, ExceptionHandling}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::CustomElementRegistryBinding::CustomElementConstructor; +use crate::dom::bindings::codegen::Bindings::CustomElementRegistryBinding::CustomElementRegistryMethods; +use crate::dom::bindings::codegen::Bindings::CustomElementRegistryBinding::ElementDefinitionOptions; +use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::conversions::{ + ConversionResult, FromJSValConvertible, StringificationBehavior, +}; +use crate::dom::bindings::error::{ + report_pending_exception, throw_dom_exception, Error, ErrorResult, Fallible, +}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::settings_stack::is_execution_stack_empty; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::domexception::{DOMErrorName, DOMException}; +use crate::dom::element::Element; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{document_from_node, window_from_node, Node, ShadowIncluding}; +use crate::dom::promise::Promise; +use crate::dom::window::Window; +use crate::microtask::Microtask; +use crate::realms::{enter_realm, InRealm}; +use crate::script_runtime::JSContext; +use crate::script_thread::ScriptThread; +use dom_struct::dom_struct; +use html5ever::{LocalName, Namespace, Prefix}; +use js::conversions::ToJSValConvertible; +use js::glue::UnwrapObjectStatic; +use js::jsapi::{HandleValueArray, Heap, IsCallable, IsConstructor}; +use js::jsapi::{JSAutoRealm, JSObject}; +use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue}; +use js::rust::wrappers::{Construct1, JS_GetProperty, SameValue}; +use js::rust::{HandleObject, HandleValue, MutableHandleValue}; +use std::cell::Cell; +use std::collections::{HashMap, VecDeque}; +use std::mem; +use std::ops::Deref; +use std::ptr; +use std::rc::Rc; + +/// <https://dom.spec.whatwg.org/#concept-element-custom-element-state> +#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)] +pub enum CustomElementState { + Undefined, + Failed, + Uncustomized, + Custom, +} + +impl Default for CustomElementState { + fn default() -> CustomElementState { + CustomElementState::Uncustomized + } +} + +/// <https://html.spec.whatwg.org/multipage/#customelementregistry> +#[dom_struct] +pub struct CustomElementRegistry { + reflector_: Reflector, + + window: Dom<Window>, + + #[ignore_malloc_size_of = "Rc"] + when_defined: DomRefCell<HashMap<LocalName, Rc<Promise>>>, + + element_definition_is_running: Cell<bool>, + + #[ignore_malloc_size_of = "Rc"] + definitions: DomRefCell<HashMap<LocalName, Rc<CustomElementDefinition>>>, +} + +impl CustomElementRegistry { + fn new_inherited(window: &Window) -> CustomElementRegistry { + CustomElementRegistry { + reflector_: Reflector::new(), + window: Dom::from_ref(window), + when_defined: DomRefCell::new(HashMap::new()), + element_definition_is_running: Cell::new(false), + definitions: DomRefCell::new(HashMap::new()), + } + } + + pub fn new(window: &Window) -> DomRoot<CustomElementRegistry> { + reflect_dom_object( + Box::new(CustomElementRegistry::new_inherited(window)), + window, + ) + } + + /// Cleans up any active promises + /// <https://github.com/servo/servo/issues/15318> + pub fn teardown(&self) { + self.when_defined.borrow_mut().clear() + } + + /// <https://html.spec.whatwg.org/multipage/#look-up-a-custom-element-definition> + pub fn lookup_definition( + &self, + local_name: &LocalName, + is: Option<&LocalName>, + ) -> Option<Rc<CustomElementDefinition>> { + self.definitions + .borrow() + .values() + .find(|definition| { + // Step 4-5 + definition.local_name == *local_name && + (definition.name == *local_name || Some(&definition.name) == is) + }) + .cloned() + } + + pub fn lookup_definition_by_constructor( + &self, + constructor: HandleObject, + ) -> Option<Rc<CustomElementDefinition>> { + self.definitions + .borrow() + .values() + .find(|definition| definition.constructor.callback() == constructor.get()) + .cloned() + } + + /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define> + /// Steps 10.1, 10.2 + #[allow(unsafe_code)] + fn check_prototype( + &self, + constructor: HandleObject, + prototype: MutableHandleValue, + ) -> ErrorResult { + let global_scope = self.window.upcast::<GlobalScope>(); + unsafe { + // Step 10.1 + if !JS_GetProperty( + *global_scope.get_cx(), + constructor, + b"prototype\0".as_ptr() as *const _, + prototype, + ) { + return Err(Error::JSFailed); + } + + // Step 10.2 + if !prototype.is_object() { + return Err(Error::Type( + "constructor.prototype is not an object".to_owned(), + )); + } + } + Ok(()) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define> + /// Steps 10.3, 10.4 + #[allow(unsafe_code)] + unsafe fn get_callbacks(&self, prototype: HandleObject) -> Fallible<LifecycleCallbacks> { + let cx = self.window.get_cx(); + + // Step 4 + Ok(LifecycleCallbacks { + connected_callback: get_callback(cx, prototype, b"connectedCallback\0")?, + disconnected_callback: get_callback(cx, prototype, b"disconnectedCallback\0")?, + adopted_callback: get_callback(cx, prototype, b"adoptedCallback\0")?, + attribute_changed_callback: get_callback(cx, prototype, b"attributeChangedCallback\0")?, + }) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define> + /// Step 10.6 + #[allow(unsafe_code)] + fn get_observed_attributes(&self, constructor: HandleObject) -> Fallible<Vec<DOMString>> { + let cx = self.window.get_cx(); + rooted!(in(*cx) let mut observed_attributes = UndefinedValue()); + if unsafe { + !JS_GetProperty( + *cx, + constructor, + b"observedAttributes\0".as_ptr() as *const _, + observed_attributes.handle_mut(), + ) + } { + return Err(Error::JSFailed); + } + + if observed_attributes.is_undefined() { + return Ok(Vec::new()); + } + + let conversion = unsafe { + FromJSValConvertible::from_jsval( + *cx, + observed_attributes.handle(), + StringificationBehavior::Default, + ) + }; + match conversion { + Ok(ConversionResult::Success(attributes)) => Ok(attributes), + Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into())), + _ => Err(Error::JSFailed), + } + } +} + +/// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define> +/// Step 10.4 +#[allow(unsafe_code)] +fn get_callback( + cx: JSContext, + prototype: HandleObject, + name: &[u8], +) -> Fallible<Option<Rc<Function>>> { + rooted!(in(*cx) let mut callback = UndefinedValue()); + unsafe { + // Step 10.4.1 + if !JS_GetProperty( + *cx, + prototype, + name.as_ptr() as *const _, + callback.handle_mut(), + ) { + return Err(Error::JSFailed); + } + + // Step 10.4.2 + if !callback.is_undefined() { + if !callback.is_object() || !IsCallable(callback.to_object()) { + return Err(Error::Type("Lifecycle callback is not callable".to_owned())); + } + Ok(Some(Function::new(cx, callback.to_object()))) + } else { + Ok(None) + } + } +} + +impl CustomElementRegistryMethods for CustomElementRegistry { + #[allow(unsafe_code, unrooted_must_root)] + /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-define> + fn Define( + &self, + name: DOMString, + constructor_: Rc<CustomElementConstructor>, + options: &ElementDefinitionOptions, + ) -> ErrorResult { + let cx = self.window.get_cx(); + rooted!(in(*cx) let constructor = constructor_.callback()); + let name = LocalName::from(&*name); + + // Step 1 + // We must unwrap the constructor as all wrappers are constructable if they are callable. + rooted!(in(*cx) let unwrapped_constructor = unsafe { UnwrapObjectStatic(constructor.get()) }); + + if unwrapped_constructor.is_null() { + // We do not have permission to access the unwrapped constructor. + return Err(Error::Security); + } + + if unsafe { !IsConstructor(unwrapped_constructor.get()) } { + return Err(Error::Type( + "Second argument of CustomElementRegistry.define is not a constructor".to_owned(), + )); + } + + // Step 2 + if !is_valid_custom_element_name(&name) { + return Err(Error::Syntax); + } + + // Step 3 + if self.definitions.borrow().contains_key(&name) { + return Err(Error::NotSupported); + } + + // Step 4 + if self + .definitions + .borrow() + .iter() + .any(|(_, ref def)| def.constructor == constructor_) + { + return Err(Error::NotSupported); + } + + // Step 6 + let extends = &options.extends; + + // Steps 5, 7 + let local_name = if let Some(ref extended_name) = *extends { + // Step 7.1 + if is_valid_custom_element_name(extended_name) { + return Err(Error::NotSupported); + } + + // Step 7.2 + if !is_extendable_element_interface(extended_name) { + return Err(Error::NotSupported); + } + + LocalName::from(&**extended_name) + } else { + // Step 7.3 + name.clone() + }; + + // Step 8 + if self.element_definition_is_running.get() { + return Err(Error::NotSupported); + } + + // Step 9 + self.element_definition_is_running.set(true); + + // Steps 10.1 - 10.2 + rooted!(in(*cx) let mut prototype = UndefinedValue()); + { + let _ac = JSAutoRealm::new(*cx, constructor.get()); + if let Err(error) = self.check_prototype(constructor.handle(), prototype.handle_mut()) { + self.element_definition_is_running.set(false); + return Err(error); + } + }; + + // Steps 10.3 - 10.4 + rooted!(in(*cx) let proto_object = prototype.to_object()); + let callbacks = { + let _ac = JSAutoRealm::new(*cx, proto_object.get()); + match unsafe { self.get_callbacks(proto_object.handle()) } { + Ok(callbacks) => callbacks, + Err(error) => { + self.element_definition_is_running.set(false); + return Err(error); + }, + } + }; + + // Step 10.5 - 10.6 + let observed_attributes = if callbacks.attribute_changed_callback.is_some() { + let _ac = JSAutoRealm::new(*cx, constructor.get()); + match self.get_observed_attributes(constructor.handle()) { + Ok(attributes) => attributes, + Err(error) => { + self.element_definition_is_running.set(false); + return Err(error); + }, + } + } else { + Vec::new() + }; + + self.element_definition_is_running.set(false); + + // Step 11 + let definition = Rc::new(CustomElementDefinition::new( + name.clone(), + local_name.clone(), + constructor_, + observed_attributes, + callbacks, + )); + + // Step 12 + self.definitions + .borrow_mut() + .insert(name.clone(), definition.clone()); + + // Step 13 + let document = self.window.Document(); + + // Steps 14-15 + for candidate in document + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::Yes) + .filter_map(DomRoot::downcast::<Element>) + { + let is = candidate.get_is(); + if *candidate.local_name() == local_name && + *candidate.namespace() == ns!(html) && + (extends.is_none() || is.as_ref() == Some(&name)) + { + ScriptThread::enqueue_upgrade_reaction(&*candidate, definition.clone()); + } + } + + // Step 16, 16.3 + if let Some(promise) = self.when_defined.borrow_mut().remove(&name) { + unsafe { + rooted!(in(*cx) let mut constructor = UndefinedValue()); + definition + .constructor + .to_jsval(*cx, constructor.handle_mut()); + promise.resolve_native(&constructor.get()); + } + } + Ok(()) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-get> + #[allow(unsafe_code)] + fn Get(&self, cx: JSContext, name: DOMString) -> JSVal { + match self.definitions.borrow().get(&LocalName::from(&*name)) { + Some(definition) => unsafe { + rooted!(in(*cx) let mut constructor = UndefinedValue()); + definition + .constructor + .to_jsval(*cx, constructor.handle_mut()); + constructor.get() + }, + None => UndefinedValue(), + } + } + + /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-whendefined> + #[allow(unsafe_code)] + fn WhenDefined(&self, name: DOMString, comp: InRealm) -> Rc<Promise> { + let global_scope = self.window.upcast::<GlobalScope>(); + let name = LocalName::from(&*name); + + // Step 1 + if !is_valid_custom_element_name(&name) { + let promise = Promise::new_in_current_realm(&global_scope, comp); + promise.reject_native(&DOMException::new(&global_scope, DOMErrorName::SyntaxError)); + return promise; + } + + // Step 2 + if let Some(definition) = self.definitions.borrow().get(&LocalName::from(&*name)) { + unsafe { + let cx = global_scope.get_cx(); + rooted!(in(*cx) let mut constructor = UndefinedValue()); + definition + .constructor + .to_jsval(*cx, constructor.handle_mut()); + let promise = Promise::new_in_current_realm(&global_scope, comp); + promise.resolve_native(&constructor.get()); + return promise; + } + } + + // Step 3 + let mut map = self.when_defined.borrow_mut(); + + // Steps 4, 5 + let promise = map.get(&name).cloned().unwrap_or_else(|| { + let promise = Promise::new_in_current_realm(&global_scope, comp); + map.insert(name, promise.clone()); + promise + }); + + // Step 6 + promise + } + /// https://html.spec.whatwg.org/multipage/#dom-customelementregistry-upgrade + fn Upgrade(&self, node: &Node) { + // Spec says to make a list first and then iterate the list, but + // try-to-upgrade only queues upgrade reactions and doesn't itself + // modify the tree, so that's not an observable distinction. + node.traverse_preorder(ShadowIncluding::Yes).for_each(|n| { + if let Some(element) = n.downcast::<Element>() { + try_upgrade_element(element); + } + }); + } +} + +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub struct LifecycleCallbacks { + #[ignore_malloc_size_of = "Rc"] + connected_callback: Option<Rc<Function>>, + + #[ignore_malloc_size_of = "Rc"] + disconnected_callback: Option<Rc<Function>>, + + #[ignore_malloc_size_of = "Rc"] + adopted_callback: Option<Rc<Function>>, + + #[ignore_malloc_size_of = "Rc"] + attribute_changed_callback: Option<Rc<Function>>, +} + +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub enum ConstructionStackEntry { + Element(DomRoot<Element>), + AlreadyConstructedMarker, +} + +/// <https://html.spec.whatwg.org/multipage/#custom-element-definition> +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub struct CustomElementDefinition { + pub name: LocalName, + + pub local_name: LocalName, + + #[ignore_malloc_size_of = "Rc"] + pub constructor: Rc<CustomElementConstructor>, + + pub observed_attributes: Vec<DOMString>, + + pub callbacks: LifecycleCallbacks, + + pub construction_stack: DomRefCell<Vec<ConstructionStackEntry>>, +} + +impl CustomElementDefinition { + fn new( + name: LocalName, + local_name: LocalName, + constructor: Rc<CustomElementConstructor>, + observed_attributes: Vec<DOMString>, + callbacks: LifecycleCallbacks, + ) -> CustomElementDefinition { + CustomElementDefinition { + name: name, + local_name: local_name, + constructor: constructor, + observed_attributes: observed_attributes, + callbacks: callbacks, + construction_stack: Default::default(), + } + } + + /// <https://html.spec.whatwg.org/multipage/#autonomous-custom-element> + pub fn is_autonomous(&self) -> bool { + self.name == self.local_name + } + + /// https://dom.spec.whatwg.org/#concept-create-element Step 6.1 + #[allow(unsafe_code)] + pub fn create_element( + &self, + document: &Document, + prefix: Option<Prefix>, + ) -> Fallible<DomRoot<Element>> { + let window = document.window(); + let cx = window.get_cx(); + // Step 2 + rooted!(in(*cx) let constructor = ObjectValue(self.constructor.callback())); + rooted!(in(*cx) let mut element = ptr::null_mut::<JSObject>()); + { + // Go into the constructor's realm + let _ac = JSAutoRealm::new(*cx, self.constructor.callback()); + let args = HandleValueArray::new(); + if unsafe { !Construct1(*cx, constructor.handle(), &args, element.handle_mut()) } { + return Err(Error::JSFailed); + } + } + + // https://heycam.github.io/webidl/#construct-a-callback-function + // https://html.spec.whatwg.org/multipage/#clean-up-after-running-script + if is_execution_stack_empty() { + window + .upcast::<GlobalScope>() + .perform_a_microtask_checkpoint(); + } + + rooted!(in(*cx) let element_val = ObjectValue(element.get())); + let element: DomRoot<Element> = + match unsafe { DomRoot::from_jsval(*cx, element_val.handle(), ()) } { + Ok(ConversionResult::Success(element)) => element, + Ok(ConversionResult::Failure(..)) => { + return Err(Error::Type( + "Constructor did not return a DOM node".to_owned(), + )); + }, + _ => return Err(Error::JSFailed), + }; + + // Step 3 + if !element.is::<HTMLElement>() { + return Err(Error::Type( + "Constructor did not return a DOM node".to_owned(), + )); + } + + // Steps 4-9 + if element.HasAttributes() || + element.upcast::<Node>().children_count() > 0 || + element.upcast::<Node>().has_parent() || + &*element.upcast::<Node>().owner_doc() != document || + *element.namespace() != ns!(html) || + *element.local_name() != self.local_name + { + return Err(Error::NotSupported); + } + + // Step 10 + element.set_prefix(prefix); + + // Step 11 + // Element's `is` is None by default + + Ok(element) + } +} + +/// <https://html.spec.whatwg.org/multipage/#concept-upgrade-an-element> +#[allow(unsafe_code)] +pub fn upgrade_element(definition: Rc<CustomElementDefinition>, element: &Element) { + // Step 1 + let state = element.get_custom_element_state(); + if state != CustomElementState::Undefined && state != CustomElementState::Uncustomized { + return; + } + + // Step 2 + element.set_custom_element_definition(Rc::clone(&definition)); + + // Step 3 + element.set_custom_element_state(CustomElementState::Failed); + + // Step 4 + for attr in element.attrs().iter() { + let local_name = attr.local_name().clone(); + let value = DOMString::from(&**attr.value()); + let namespace = attr.namespace().clone(); + ScriptThread::enqueue_callback_reaction( + element, + CallbackReaction::AttributeChanged(local_name, None, Some(value), namespace), + Some(definition.clone()), + ); + } + + // Step 5 + if element.is_connected() { + ScriptThread::enqueue_callback_reaction( + element, + CallbackReaction::Connected, + Some(definition.clone()), + ); + } + + // Step 6 + definition + .construction_stack + .borrow_mut() + .push(ConstructionStackEntry::Element(DomRoot::from_ref(element))); + + // Steps 7-8, successful case + let result = run_upgrade_constructor(&definition.constructor, element); + + // "regardless of whether the above steps threw an exception" step + definition.construction_stack.borrow_mut().pop(); + + // Step 8 exception handling + if let Err(error) = result { + // Step 8.exception.1 + element.clear_custom_element_definition(); + + // Step 8.exception.2 + element.clear_reaction_queue(); + + // Step 8.exception.3 + let global = GlobalScope::current().expect("No current global"); + let cx = global.get_cx(); + unsafe { + let ar = enter_realm(&*global); + throw_dom_exception(cx, &global, error); + report_pending_exception(*cx, true, InRealm::Entered(&ar)); + } + + return; + } + + // TODO Step 9: "If element is a form-associated custom element..." + + // Step 10 + element.set_custom_element_state(CustomElementState::Custom); +} + +/// <https://html.spec.whatwg.org/multipage/#concept-upgrade-an-element> +/// Steps 8.1-8.3 +#[allow(unsafe_code)] +fn run_upgrade_constructor( + constructor: &Rc<CustomElementConstructor>, + element: &Element, +) -> ErrorResult { + let window = window_from_node(element); + let cx = window.get_cx(); + rooted!(in(*cx) let constructor_val = ObjectValue(constructor.callback())); + rooted!(in(*cx) let mut element_val = UndefinedValue()); + unsafe { + element.to_jsval(*cx, element_val.handle_mut()); + } + rooted!(in(*cx) let mut construct_result = ptr::null_mut::<JSObject>()); + { + // Step 8.1 TODO when shadow DOM exists + + // Go into the constructor's realm + let _ac = JSAutoRealm::new(*cx, constructor.callback()); + let args = HandleValueArray::new(); + // Step 8.2 + if unsafe { + !Construct1( + *cx, + constructor_val.handle(), + &args, + construct_result.handle_mut(), + ) + } { + return Err(Error::JSFailed); + } + + // https://heycam.github.io/webidl/#construct-a-callback-function + // https://html.spec.whatwg.org/multipage/#clean-up-after-running-script + if is_execution_stack_empty() { + window + .upcast::<GlobalScope>() + .perform_a_microtask_checkpoint(); + } + + // Step 8.3 + let mut same = false; + rooted!(in(*cx) let construct_result_val = ObjectValue(construct_result.get())); + if unsafe { + !SameValue( + *cx, + construct_result_val.handle(), + element_val.handle(), + &mut same, + ) + } { + return Err(Error::JSFailed); + } + if !same { + return Err(Error::Type( + "Returned element is not SameValue as the upgraded element".to_string(), + )); + } + } + Ok(()) +} + +/// <https://html.spec.whatwg.org/multipage/#concept-try-upgrade> +pub fn try_upgrade_element(element: &Element) { + // Step 1 + let document = document_from_node(element); + let namespace = element.namespace(); + let local_name = element.local_name(); + let is = element.get_is(); + if let Some(definition) = + document.lookup_custom_element_definition(namespace, local_name, is.as_ref()) + { + // Step 2 + ScriptThread::enqueue_upgrade_reaction(element, definition); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub enum CustomElementReaction { + Upgrade(#[ignore_malloc_size_of = "Rc"] Rc<CustomElementDefinition>), + Callback( + #[ignore_malloc_size_of = "Rc"] Rc<Function>, + #[ignore_malloc_size_of = "mozjs"] Box<[Heap<JSVal>]>, + ), +} + +impl CustomElementReaction { + /// <https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions> + #[allow(unsafe_code)] + pub fn invoke(&self, element: &Element) { + // Step 2.1 + match *self { + CustomElementReaction::Upgrade(ref definition) => { + upgrade_element(definition.clone(), element) + }, + CustomElementReaction::Callback(ref callback, ref arguments) => { + // We're rooted, so it's safe to hand out a handle to objects in Heap + let arguments = arguments + .iter() + .map(|arg| unsafe { HandleValue::from_raw(arg.handle()) }) + .collect(); + let _ = callback.Call_(&*element, arguments, ExceptionHandling::Report); + }, + } + } +} + +pub enum CallbackReaction { + Connected, + Disconnected, + Adopted(DomRoot<Document>, DomRoot<Document>), + AttributeChanged(LocalName, Option<DOMString>, Option<DOMString>, Namespace), +} + +/// <https://html.spec.whatwg.org/multipage/#processing-the-backup-element-queue> +#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)] +enum BackupElementQueueFlag { + Processing, + NotProcessing, +} + +/// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack> +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct CustomElementReactionStack { + stack: DomRefCell<Vec<ElementQueue>>, + backup_queue: ElementQueue, + processing_backup_element_queue: Cell<BackupElementQueueFlag>, +} + +impl CustomElementReactionStack { + pub fn new() -> CustomElementReactionStack { + CustomElementReactionStack { + stack: DomRefCell::new(Vec::new()), + backup_queue: ElementQueue::new(), + processing_backup_element_queue: Cell::new(BackupElementQueueFlag::NotProcessing), + } + } + + pub fn push_new_element_queue(&self) { + self.stack.borrow_mut().push(ElementQueue::new()); + } + + pub fn pop_current_element_queue(&self) { + rooted_vec!(let mut stack); + mem::swap(&mut *stack, &mut *self.stack.borrow_mut()); + + if let Some(current_queue) = stack.last() { + current_queue.invoke_reactions(); + } + stack.pop(); + + mem::swap(&mut *self.stack.borrow_mut(), &mut *stack); + self.stack.borrow_mut().append(&mut *stack); + } + + /// <https://html.spec.whatwg.org/multipage/#enqueue-an-element-on-the-appropriate-element-queue> + /// Step 4 + pub fn invoke_backup_element_queue(&self) { + // Step 4.1 + self.backup_queue.invoke_reactions(); + + // Step 4.2 + self.processing_backup_element_queue + .set(BackupElementQueueFlag::NotProcessing); + } + + /// <https://html.spec.whatwg.org/multipage/#enqueue-an-element-on-the-appropriate-element-queue> + pub fn enqueue_element(&self, element: &Element) { + if let Some(current_queue) = self.stack.borrow().last() { + // Step 2 + current_queue.append_element(element); + } else { + // Step 1.1 + self.backup_queue.append_element(element); + + // Step 1.2 + if self.processing_backup_element_queue.get() == BackupElementQueueFlag::Processing { + return; + } + + // Step 1.3 + self.processing_backup_element_queue + .set(BackupElementQueueFlag::Processing); + + // Step 4 + ScriptThread::enqueue_microtask(Microtask::CustomElementReaction); + } + } + + /// <https://html.spec.whatwg.org/multipage/#enqueue-a-custom-element-callback-reaction> + #[allow(unsafe_code)] + pub fn enqueue_callback_reaction( + &self, + element: &Element, + reaction: CallbackReaction, + definition: Option<Rc<CustomElementDefinition>>, + ) { + // Step 1 + let definition = match definition.or_else(|| element.get_custom_element_definition()) { + Some(definition) => definition, + None => return, + }; + + // Step 2 + let (callback, args) = match reaction { + CallbackReaction::Connected => { + (definition.callbacks.connected_callback.clone(), Vec::new()) + }, + CallbackReaction::Disconnected => ( + definition.callbacks.disconnected_callback.clone(), + Vec::new(), + ), + CallbackReaction::Adopted(ref old_doc, ref new_doc) => { + let args = vec![Heap::default(), Heap::default()]; + args[0].set(ObjectValue(old_doc.reflector().get_jsobject().get())); + args[1].set(ObjectValue(new_doc.reflector().get_jsobject().get())); + (definition.callbacks.adopted_callback.clone(), args) + }, + CallbackReaction::AttributeChanged(local_name, old_val, val, namespace) => { + // Step 4 + if !definition + .observed_attributes + .iter() + .any(|attr| *attr == *local_name) + { + return; + } + + let cx = element.global().get_cx(); + // We might be here during HTML parsing, rather than + // during Javscript execution, and so we typically aren't + // already in a realm here. + let _ac = JSAutoRealm::new(*cx, element.global().reflector().get_jsobject().get()); + + let local_name = DOMString::from(&*local_name); + rooted!(in(*cx) let mut name_value = UndefinedValue()); + unsafe { + local_name.to_jsval(*cx, name_value.handle_mut()); + } + + rooted!(in(*cx) let mut old_value = NullValue()); + if let Some(old_val) = old_val { + unsafe { + old_val.to_jsval(*cx, old_value.handle_mut()); + } + } + + rooted!(in(*cx) let mut value = NullValue()); + if let Some(val) = val { + unsafe { + val.to_jsval(*cx, value.handle_mut()); + } + } + + rooted!(in(*cx) let mut namespace_value = NullValue()); + if namespace != ns!() { + let namespace = DOMString::from(&*namespace); + unsafe { + namespace.to_jsval(*cx, namespace_value.handle_mut()); + } + } + + let args = vec![ + Heap::default(), + Heap::default(), + Heap::default(), + Heap::default(), + ]; + args[0].set(name_value.get()); + args[1].set(old_value.get()); + args[2].set(value.get()); + args[3].set(namespace_value.get()); + + ( + definition.callbacks.attribute_changed_callback.clone(), + args, + ) + }, + }; + + // Step 3 + let callback = match callback { + Some(callback) => callback, + None => return, + }; + + // Step 5 + element.push_callback_reaction(callback, args.into_boxed_slice()); + + // Step 6 + self.enqueue_element(element); + } + + /// <https://html.spec.whatwg.org/multipage/#enqueue-a-custom-element-upgrade-reaction> + pub fn enqueue_upgrade_reaction( + &self, + element: &Element, + definition: Rc<CustomElementDefinition>, + ) { + // Step 1 + element.push_upgrade_reaction(definition); + // Step 2 + self.enqueue_element(element); + } +} + +/// <https://html.spec.whatwg.org/multipage/#element-queue> +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +struct ElementQueue { + queue: DomRefCell<VecDeque<Dom<Element>>>, +} + +impl ElementQueue { + fn new() -> ElementQueue { + ElementQueue { + queue: Default::default(), + } + } + + /// <https://html.spec.whatwg.org/multipage/#invoke-custom-element-reactions> + fn invoke_reactions(&self) { + // Steps 1-2 + while let Some(element) = self.next_element() { + element.invoke_reactions() + } + self.queue.borrow_mut().clear(); + } + + fn next_element(&self) -> Option<DomRoot<Element>> { + self.queue + .borrow_mut() + .pop_front() + .as_ref() + .map(Dom::deref) + .map(DomRoot::from_ref) + } + + fn append_element(&self, element: &Element) { + self.queue.borrow_mut().push_back(Dom::from_ref(element)); + } +} + +/// <https://html.spec.whatwg.org/multipage/#valid-custom-element-name> +pub fn is_valid_custom_element_name(name: &str) -> bool { + // Custom elment names must match: + // PotentialCustomElementName ::= [a-z] (PCENChar)* '-' (PCENChar)* + + let mut chars = name.chars(); + if !chars.next().map_or(false, |c| c >= 'a' && c <= 'z') { + return false; + } + + let mut has_dash = false; + + for c in chars { + if c == '-' { + has_dash = true; + continue; + } + + if !is_potential_custom_element_char(c) { + return false; + } + } + + if !has_dash { + return false; + } + + if name == "annotation-xml" || + name == "color-profile" || + name == "font-face" || + name == "font-face-src" || + name == "font-face-uri" || + name == "font-face-format" || + name == "font-face-name" || + name == "missing-glyph" + { + return false; + } + + true +} + +/// Check if this character is a PCENChar +/// <https://html.spec.whatwg.org/multipage/#prod-pcenchar> +fn is_potential_custom_element_char(c: char) -> bool { + c == '-' || + c == '.' || + c == '_' || + c == '\u{B7}' || + (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= '\u{C0}' && c <= '\u{D6}') || + (c >= '\u{D8}' && c <= '\u{F6}') || + (c >= '\u{F8}' && c <= '\u{37D}') || + (c >= '\u{37F}' && c <= '\u{1FFF}') || + (c >= '\u{200C}' && c <= '\u{200D}') || + (c >= '\u{203F}' && c <= '\u{2040}') || + (c >= '\u{2070}' && c <= '\u{2FEF}') || + (c >= '\u{3001}' && c <= '\u{D7FF}') || + (c >= '\u{F900}' && c <= '\u{FDCF}') || + (c >= '\u{FDF0}' && c <= '\u{FFFD}') || + (c >= '\u{10000}' && c <= '\u{EFFFF}') +} + +fn is_extendable_element_interface(element: &str) -> bool { + element == "a" || + element == "abbr" || + element == "acronym" || + element == "address" || + element == "area" || + element == "article" || + element == "aside" || + element == "audio" || + element == "b" || + element == "base" || + element == "bdi" || + element == "bdo" || + element == "big" || + element == "blockquote" || + element == "body" || + element == "br" || + element == "button" || + element == "canvas" || + element == "caption" || + element == "center" || + element == "cite" || + element == "code" || + element == "col" || + element == "colgroup" || + element == "data" || + element == "datalist" || + element == "dd" || + element == "del" || + element == "details" || + element == "dfn" || + element == "dialog" || + element == "dir" || + element == "div" || + element == "dl" || + element == "dt" || + element == "em" || + element == "embed" || + element == "fieldset" || + element == "figcaption" || + element == "figure" || + element == "font" || + element == "footer" || + element == "form" || + element == "frame" || + element == "frameset" || + element == "h1" || + element == "h2" || + element == "h3" || + element == "h4" || + element == "h5" || + element == "h6" || + element == "head" || + element == "header" || + element == "hgroup" || + element == "hr" || + element == "html" || + element == "i" || + element == "iframe" || + element == "img" || + element == "input" || + element == "ins" || + element == "kbd" || + element == "label" || + element == "legend" || + element == "li" || + element == "link" || + element == "listing" || + element == "main" || + element == "map" || + element == "mark" || + element == "marquee" || + element == "menu" || + element == "meta" || + element == "meter" || + element == "nav" || + element == "nobr" || + element == "noframes" || + element == "noscript" || + element == "object" || + element == "ol" || + element == "optgroup" || + element == "option" || + element == "output" || + element == "p" || + element == "param" || + element == "picture" || + element == "plaintext" || + element == "pre" || + element == "progress" || + element == "q" || + element == "rp" || + element == "rt" || + element == "ruby" || + element == "s" || + element == "samp" || + element == "script" || + element == "section" || + element == "select" || + element == "small" || + element == "source" || + element == "span" || + element == "strike" || + element == "strong" || + element == "style" || + element == "sub" || + element == "summary" || + element == "sup" || + element == "table" || + element == "tbody" || + element == "td" || + element == "template" || + element == "textarea" || + element == "tfoot" || + element == "th" || + element == "thead" || + element == "time" || + element == "title" || + element == "tr" || + element == "tt" || + element == "track" || + element == "u" || + element == "ul" || + element == "var" || + element == "video" || + element == "wbr" || + element == "xmp" +} diff --git a/components/script/dom/customevent.rs b/components/script/dom/customevent.rs index 7b522cde3ed..7dadacee375 100644 --- a/components/script/dom/customevent.rs +++ b/components/script/dom/customevent.rs @@ -1,28 +1,30 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CustomEventBinding; -use dom::bindings::codegen::Bindings::CustomEventBinding::CustomEventMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::bindings::trace::RootedTraceableBox; -use dom::event::Event; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::CustomEventBinding; +use crate::dom::bindings::codegen::Bindings::CustomEventBinding::CustomEventMethods; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{Heap, HandleValue, JSContext}; +use js::jsapi::Heap; use js::jsval::JSVal; +use js::rust::HandleValue; use servo_atoms::Atom; // https://dom.spec.whatwg.org/#interface-customevent #[dom_struct] pub struct CustomEvent { event: Event, - #[ignore_heap_size_of = "Defined in rust-mozjs"] + #[ignore_malloc_size_of = "Defined in rust-mozjs"] detail: Heap<JSVal>, } @@ -34,39 +36,43 @@ impl CustomEvent { } } - pub fn new_uninitialized(global: &GlobalScope) -> Root<CustomEvent> { - reflect_dom_object(box CustomEvent::new_inherited(), - global, - CustomEventBinding::Wrap) + pub fn new_uninitialized(global: &GlobalScope) -> DomRoot<CustomEvent> { + reflect_dom_object(Box::new(CustomEvent::new_inherited()), global) } - pub fn new(global: &GlobalScope, - type_: Atom, - bubbles: bool, - cancelable: bool, - detail: HandleValue) - -> Root<CustomEvent> { + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + detail: HandleValue, + ) -> DomRoot<CustomEvent> { let ev = CustomEvent::new_uninitialized(global); ev.init_custom_event(type_, bubbles, cancelable, detail); ev } - #[allow(unsafe_code)] - pub fn Constructor(global: &GlobalScope, - type_: DOMString, - init: RootedTraceableBox<CustomEventBinding::CustomEventInit>) - -> Fallible<Root<CustomEvent>> { - Ok(CustomEvent::new(global, - Atom::from(type_), - init.parent.bubbles, - init.parent.cancelable, - init.detail.handle())) + #[allow(unsafe_code, non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + type_: DOMString, + init: RootedTraceableBox<CustomEventBinding::CustomEventInit>, + ) -> Fallible<DomRoot<CustomEvent>> { + Ok(CustomEvent::new( + global, + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + init.detail.handle(), + )) } - fn init_custom_event(&self, - type_: Atom, - can_bubble: bool, - cancelable: bool, - detail: HandleValue) { + fn init_custom_event( + &self, + type_: Atom, + can_bubble: bool, + cancelable: bool, + detail: HandleValue, + ) { let event = self.upcast::<Event>(); if event.dispatching() { return; @@ -78,20 +84,20 @@ impl CustomEvent { } impl CustomEventMethods for CustomEvent { - #[allow(unsafe_code)] // https://dom.spec.whatwg.org/#dom-customevent-detail - unsafe fn Detail(&self, _cx: *mut JSContext) -> JSVal { + fn Detail(&self, _cx: JSContext) -> JSVal { self.detail.get() } - #[allow(unsafe_code)] // https://dom.spec.whatwg.org/#dom-customevent-initcustomevent - unsafe fn InitCustomEvent(&self, - _cx: *mut JSContext, - type_: DOMString, - can_bubble: bool, - cancelable: bool, - detail: HandleValue) { + fn InitCustomEvent( + &self, + _cx: JSContext, + type_: DOMString, + can_bubble: bool, + cancelable: bool, + detail: HandleValue, + ) { self.init_custom_event(Atom::from(type_), can_bubble, cancelable, detail) } diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index 80b7982d35b..e1c67cfafee 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -1,60 +1,79 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use devtools; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::devtools; +use crate::dom::abstractworker::{SimpleWorkerErrorHandler, WorkerScriptMsg}; +use crate::dom::abstractworkerglobalscope::{run_worker_event_loop, WorkerEventLoopMethods}; +use crate::dom::abstractworkerglobalscope::{SendableWorkerScriptChan, WorkerThreadWorkerChan}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding; +use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::PostMessageOptions; +use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType; +use crate::dom::bindings::error::{ErrorInfo, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{DomRoot, RootCollection, ThreadLocalStackRoots}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::errorevent::ErrorEvent; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::identityhub::Identities; +use crate::dom::messageevent::MessageEvent; +use crate::dom::worker::{TrustedWorkerAddress, Worker}; +use crate::dom::workerglobalscope::WorkerGlobalScope; +use crate::fetch::load_whole_resource; +use crate::realms::{enter_realm, AlreadyInRealm, InRealm}; +use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent; +use crate::script_runtime::{ + new_child_runtime, CommonScriptMsg, ContextForRequestInterrupt, JSContext as SafeJSContext, + Runtime, ScriptChan, ScriptPort, +}; +use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; +use crate::task_source::networking::NetworkingTaskSource; +use crate::task_source::TaskSourceName; +use crossbeam_channel::{unbounded, Receiver, Sender}; use devtools_traits::DevtoolScriptControlMsg; -use dom::abstractworker::{SharedRt, SimpleWorkerErrorHandler, WorkerScriptMsg}; -use dom::abstractworkerglobalscope::{SendableWorkerScriptChan, WorkerThreadWorkerChan}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding; -use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::error::{ErrorInfo, ErrorResult}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{Root, RootCollection}; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::bindings::structuredclone::StructuredCloneData; -use dom::globalscope::GlobalScope; -use dom::messageevent::MessageEvent; -use dom::worker::{TrustedWorkerAddress, WorkerErrorHandler, WorkerMessageHandler}; -use dom::workerglobalscope::WorkerGlobalScope; use dom_struct::dom_struct; -use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::ipc::IpcReceiver; use ipc_channel::router::ROUTER; -use js::jsapi::{HandleValue, JS_SetInterruptCallback}; -use js::jsapi::{JSAutoCompartment, JSContext}; +use js::jsapi::JS_AddInterruptCallback; +use js::jsapi::{Heap, JSContext, JSObject}; use js::jsval::UndefinedValue; -use js::rust::Runtime; -use msg::constellation_msg::FrameId; -use net_traits::{IpcSend, load_whole_resource}; -use net_traits::request::{CredentialsMode, Destination, RequestInit, Type as RequestType}; -use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, StackRootTLS, get_reports, new_rt_and_cx}; -use script_runtime::ScriptThreadEventCategory::WorkerEvent; -use script_traits::{TimerEvent, TimerSource, WorkerGlobalScopeInit, WorkerScriptLoadOrigin}; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; +use msg::constellation_msg::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId}; +use net_traits::image_cache::ImageCache; +use net_traits::request::{CredentialsMode, Destination, ParserMetadata}; +use net_traits::request::{Referrer, RequestBuilder, RequestMode}; +use net_traits::IpcSend; +use parking_lot::Mutex; +use script_traits::{WorkerGlobalScopeInit, WorkerScriptLoadOrigin}; use servo_rand::random; use servo_url::{MutableOrigin, ServoUrl}; use std::mem::replace; -use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicBool; -use std::sync::mpsc::{Receiver, RecvError, Select, Sender, channel}; -use std::thread; -use style::thread_state; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; +use style::thread_state::{self, ThreadState}; /// Set the `worker` field of a related DedicatedWorkerGlobalScope object to a particular /// value for the duration of this object's lifetime. This ensures that the related Worker /// object only lives as long as necessary (ie. while events are being executed), while /// providing a reference that can be cloned freely. -struct AutoWorkerReset<'a> { +pub struct AutoWorkerReset<'a> { workerscope: &'a DedicatedWorkerGlobalScope, old_worker: Option<TrustedWorkerAddress>, } impl<'a> AutoWorkerReset<'a> { - fn new(workerscope: &'a DedicatedWorkerGlobalScope, - worker: TrustedWorkerAddress) - -> AutoWorkerReset<'a> { + fn new( + workerscope: &'a DedicatedWorkerGlobalScope, + worker: TrustedWorkerAddress, + ) -> AutoWorkerReset<'a> { AutoWorkerReset { workerscope: workerscope, old_worker: replace(&mut *workerscope.worker.borrow_mut(), Some(worker)), @@ -68,82 +87,224 @@ impl<'a> Drop for AutoWorkerReset<'a> { } } -enum MixedMessage { - FromWorker((TrustedWorkerAddress, WorkerScriptMsg)), - FromScheduler((TrustedWorkerAddress, TimerEvent)), - FromDevtools(DevtoolScriptControlMsg) +/// Messages sent from the owning global. +pub enum DedicatedWorkerControlMsg { + /// Shutdown the worker. + Exit, +} + +pub enum DedicatedWorkerScriptMsg { + /// Standard message from a worker. + CommonWorker(TrustedWorkerAddress, WorkerScriptMsg), + /// Wake-up call from the task queue. + WakeUp, +} + +pub enum MixedMessage { + FromWorker(DedicatedWorkerScriptMsg), + FromDevtools(DevtoolScriptControlMsg), + FromControl(DedicatedWorkerControlMsg), +} + +impl QueuedTaskConversion for DedicatedWorkerScriptMsg { + fn task_source_name(&self) -> Option<&TaskSourceName> { + let common_worker_msg = match self { + DedicatedWorkerScriptMsg::CommonWorker(_, common_worker_msg) => common_worker_msg, + _ => return None, + }; + let script_msg = match common_worker_msg { + WorkerScriptMsg::Common(ref script_msg) => script_msg, + _ => return None, + }; + match script_msg { + CommonScriptMsg::Task(_category, _boxed, _pipeline_id, source_name) => { + Some(&source_name) + }, + _ => None, + } + } + + fn pipeline_id(&self) -> Option<PipelineId> { + // Workers always return None, since the pipeline_id is only used to check for document activity, + // and this check does not apply to worker event-loops. + None + } + + fn into_queued_task(self) -> Option<QueuedTask> { + let (worker, common_worker_msg) = match self { + DedicatedWorkerScriptMsg::CommonWorker(worker, common_worker_msg) => { + (worker, common_worker_msg) + }, + _ => return None, + }; + let script_msg = match common_worker_msg { + WorkerScriptMsg::Common(script_msg) => script_msg, + _ => return None, + }; + let (category, boxed, pipeline_id, task_source) = match script_msg { + CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => { + (category, boxed, pipeline_id, task_source) + }, + _ => return None, + }; + Some((Some(worker), category, boxed, pipeline_id, task_source)) + } + + fn from_queued_task(queued_task: QueuedTask) -> Self { + let (worker, category, boxed, pipeline_id, task_source) = queued_task; + let script_msg = CommonScriptMsg::Task(category, boxed, pipeline_id, task_source); + DedicatedWorkerScriptMsg::CommonWorker(worker.unwrap(), WorkerScriptMsg::Common(script_msg)) + } + + fn inactive_msg() -> Self { + // Inactive is only relevant in the context of a browsing-context event-loop. + panic!("Workers should never receive messages marked as inactive"); + } + + fn wake_up_msg() -> Self { + DedicatedWorkerScriptMsg::WakeUp + } + + fn is_wake_up(&self) -> bool { + match self { + DedicatedWorkerScriptMsg::WakeUp => true, + _ => false, + } + } } +unsafe_no_jsmanaged_fields!(TaskQueue<DedicatedWorkerScriptMsg>); + // https://html.spec.whatwg.org/multipage/#dedicatedworkerglobalscope #[dom_struct] pub struct DedicatedWorkerGlobalScope { workerglobalscope: WorkerGlobalScope, - #[ignore_heap_size_of = "Defined in std"] - receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>, - #[ignore_heap_size_of = "Defined in std"] - own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, - #[ignore_heap_size_of = "Defined in std"] - timer_event_port: Receiver<(TrustedWorkerAddress, TimerEvent)>, - #[ignore_heap_size_of = "Trusted<T> has unclear ownership like JS<T>"] - worker: DOMRefCell<Option<TrustedWorkerAddress>>, - #[ignore_heap_size_of = "Can't measure trait objects"] + #[ignore_malloc_size_of = "Defined in std"] + task_queue: TaskQueue<DedicatedWorkerScriptMsg>, + #[ignore_malloc_size_of = "Defined in std"] + own_sender: Sender<DedicatedWorkerScriptMsg>, + #[ignore_malloc_size_of = "Trusted<T> has unclear ownership like Dom<T>"] + worker: DomRefCell<Option<TrustedWorkerAddress>>, + #[ignore_malloc_size_of = "Can't measure trait objects"] /// Sender to the parent thread. - parent_sender: Box<ScriptChan + Send>, + parent_sender: Box<dyn ScriptChan + Send>, + #[ignore_malloc_size_of = "Arc"] + image_cache: Arc<dyn ImageCache>, + browsing_context: Option<BrowsingContextId>, + /// A receiver of control messages, + /// currently only used to signal shutdown. + #[ignore_malloc_size_of = "Channels are hard"] + control_receiver: Receiver<DedicatedWorkerControlMsg>, +} + +impl WorkerEventLoopMethods for DedicatedWorkerGlobalScope { + type WorkerMsg = DedicatedWorkerScriptMsg; + type ControlMsg = DedicatedWorkerControlMsg; + type Event = MixedMessage; + + fn task_queue(&self) -> &TaskQueue<DedicatedWorkerScriptMsg> { + &self.task_queue + } + + fn handle_event(&self, event: MixedMessage) -> bool { + self.handle_mixed_message(event) + } + + fn handle_worker_post_event(&self, worker: &TrustedWorkerAddress) -> Option<AutoWorkerReset> { + let ar = AutoWorkerReset::new(&self, worker.clone()); + Some(ar) + } + + fn from_control_msg(&self, msg: DedicatedWorkerControlMsg) -> MixedMessage { + MixedMessage::FromControl(msg) + } + + fn from_worker_msg(&self, msg: DedicatedWorkerScriptMsg) -> MixedMessage { + MixedMessage::FromWorker(msg) + } + + fn from_devtools_msg(&self, msg: DevtoolScriptControlMsg) -> MixedMessage { + MixedMessage::FromDevtools(msg) + } + + fn control_receiver(&self) -> &Receiver<DedicatedWorkerControlMsg> { + &self.control_receiver + } } impl DedicatedWorkerGlobalScope { - fn new_inherited(init: WorkerGlobalScopeInit, - worker_url: ServoUrl, - from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, - runtime: Runtime, - parent_sender: Box<ScriptChan + Send>, - own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, - receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>, - timer_event_chan: IpcSender<TimerEvent>, - timer_event_port: Receiver<(TrustedWorkerAddress, TimerEvent)>, - closing: Arc<AtomicBool>) - -> DedicatedWorkerGlobalScope { + fn new_inherited( + init: WorkerGlobalScopeInit, + worker_name: DOMString, + worker_type: WorkerType, + worker_url: ServoUrl, + from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, + runtime: Runtime, + parent_sender: Box<dyn ScriptChan + Send>, + own_sender: Sender<DedicatedWorkerScriptMsg>, + receiver: Receiver<DedicatedWorkerScriptMsg>, + closing: Arc<AtomicBool>, + image_cache: Arc<dyn ImageCache>, + browsing_context: Option<BrowsingContextId>, + gpu_id_hub: Arc<Mutex<Identities>>, + control_receiver: Receiver<DedicatedWorkerControlMsg>, + ) -> DedicatedWorkerGlobalScope { DedicatedWorkerGlobalScope { - workerglobalscope: WorkerGlobalScope::new_inherited(init, - worker_url, - runtime, - from_devtools_receiver, - timer_event_chan, - Some(closing)), - receiver: receiver, + workerglobalscope: WorkerGlobalScope::new_inherited( + init, + worker_name, + worker_type, + worker_url, + runtime, + from_devtools_receiver, + closing, + gpu_id_hub, + ), + task_queue: TaskQueue::new(receiver, own_sender.clone()), own_sender: own_sender, - timer_event_port: timer_event_port, parent_sender: parent_sender, - worker: DOMRefCell::new(None), + worker: DomRefCell::new(None), + image_cache: image_cache, + browsing_context, + control_receiver, } } #[allow(unsafe_code)] - pub fn new(init: WorkerGlobalScopeInit, - worker_url: ServoUrl, - from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, - runtime: Runtime, - parent_sender: Box<ScriptChan + Send>, - own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, - receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>, - timer_event_chan: IpcSender<TimerEvent>, - timer_event_port: Receiver<(TrustedWorkerAddress, TimerEvent)>, - closing: Arc<AtomicBool>) - -> Root<DedicatedWorkerGlobalScope> { + pub fn new( + init: WorkerGlobalScopeInit, + worker_name: DOMString, + worker_type: WorkerType, + worker_url: ServoUrl, + from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, + runtime: Runtime, + parent_sender: Box<dyn ScriptChan + Send>, + own_sender: Sender<DedicatedWorkerScriptMsg>, + receiver: Receiver<DedicatedWorkerScriptMsg>, + closing: Arc<AtomicBool>, + image_cache: Arc<dyn ImageCache>, + browsing_context: Option<BrowsingContextId>, + gpu_id_hub: Arc<Mutex<Identities>>, + control_receiver: Receiver<DedicatedWorkerControlMsg>, + ) -> DomRoot<DedicatedWorkerGlobalScope> { let cx = runtime.cx(); - let scope = box DedicatedWorkerGlobalScope::new_inherited(init, - worker_url, - from_devtools_receiver, - runtime, - parent_sender, - own_sender, - receiver, - timer_event_chan, - timer_event_port, - closing); - unsafe { - DedicatedWorkerGlobalScopeBinding::Wrap(cx, scope) - } + let scope = Box::new(DedicatedWorkerGlobalScope::new_inherited( + init, + worker_name, + worker_type, + worker_url, + from_devtools_receiver, + runtime, + parent_sender, + own_sender, + receiver, + closing, + image_cache, + browsing_context, + gpu_id_hub, + control_receiver, + )); + unsafe { DedicatedWorkerGlobalScopeBinding::Wrap(SafeJSContext::from_ptr(cx), scope) } } pub fn origin(&self) -> MutableOrigin { @@ -151,233 +312,318 @@ impl DedicatedWorkerGlobalScope { } #[allow(unsafe_code)] - pub fn run_worker_scope(init: WorkerGlobalScopeInit, - worker_url: ServoUrl, - from_devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>, - worker_rt_for_mainthread: Arc<Mutex<Option<SharedRt>>>, - worker: TrustedWorkerAddress, - parent_sender: Box<ScriptChan + Send>, - own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, - receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>, - worker_load_origin: WorkerScriptLoadOrigin, - closing: Arc<AtomicBool>) { + // https://html.spec.whatwg.org/multipage/#run-a-worker + pub fn run_worker_scope( + init: WorkerGlobalScopeInit, + worker_url: ServoUrl, + from_devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>, + worker: TrustedWorkerAddress, + parent_sender: Box<dyn ScriptChan + Send>, + own_sender: Sender<DedicatedWorkerScriptMsg>, + receiver: Receiver<DedicatedWorkerScriptMsg>, + worker_load_origin: WorkerScriptLoadOrigin, + worker_name: String, + worker_type: WorkerType, + closing: Arc<AtomicBool>, + image_cache: Arc<dyn ImageCache>, + browsing_context: Option<BrowsingContextId>, + gpu_id_hub: Arc<Mutex<Identities>>, + control_receiver: Receiver<DedicatedWorkerControlMsg>, + context_sender: Sender<ContextForRequestInterrupt>, + ) -> JoinHandle<()> { let serialized_worker_url = worker_url.to_string(); let name = format!("WebWorker for {}", serialized_worker_url); - let top_level_frame_id = FrameId::installed(); - - thread::Builder::new().name(name).spawn(move || { - thread_state::initialize(thread_state::SCRIPT | thread_state::IN_WORKER); + let top_level_browsing_context_id = TopLevelBrowsingContextId::installed(); + let current_global = GlobalScope::current().expect("No current global object"); + let origin = current_global.origin().immutable().clone(); + let referrer = current_global.get_referrer(); + let parent = current_global.runtime_handle(); + let current_global_https_state = current_global.get_https_state(); + + thread::Builder::new() + .name(name) + .spawn(move || { + thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER); + + if let Some(top_level_browsing_context_id) = top_level_browsing_context_id { + TopLevelBrowsingContextId::install(top_level_browsing_context_id); + } - if let Some(top_level_frame_id) = top_level_frame_id { - FrameId::install(top_level_frame_id); - } + let roots = RootCollection::new(); + let _stack_roots = ThreadLocalStackRoots::new(&roots); + + let WorkerScriptLoadOrigin { + referrer_url, + referrer_policy, + pipeline_id, + } = worker_load_origin; + + let referrer = referrer_url + .map(|url| Referrer::ReferrerUrl(url)) + .unwrap_or(referrer); + + let request = RequestBuilder::new(worker_url.clone(), referrer) + .destination(Destination::Worker) + .mode(RequestMode::SameOrigin) + .credentials_mode(CredentialsMode::CredentialsSameOrigin) + .parser_metadata(ParserMetadata::NotParserInserted) + .use_url_credentials(true) + .pipeline_id(Some(pipeline_id)) + .referrer_policy(referrer_policy) + .origin(origin); + + let runtime = unsafe { + let task_source = NetworkingTaskSource( + Box::new(WorkerThreadWorkerChan { + sender: own_sender.clone(), + worker: worker.clone(), + }), + pipeline_id, + ); + new_child_runtime(parent, Some(task_source)) + }; + + let _ = context_sender.send(ContextForRequestInterrupt::new(runtime.cx())); + + let (devtools_mpsc_chan, devtools_mpsc_port) = unbounded(); + ROUTER.route_ipc_receiver_to_crossbeam_sender( + from_devtools_receiver, + devtools_mpsc_chan, + ); + + let global = DedicatedWorkerGlobalScope::new( + init, + DOMString::from_string(worker_name), + worker_type, + worker_url, + devtools_mpsc_port, + runtime, + parent_sender.clone(), + own_sender, + receiver, + closing, + image_cache, + browsing_context, + gpu_id_hub, + control_receiver, + ); + // FIXME(njn): workers currently don't have a unique ID suitable for using in reporter + // registration (#6631), so we instead use a random number and cross our fingers. + let scope = global.upcast::<WorkerGlobalScope>(); + let global_scope = global.upcast::<GlobalScope>(); + + global_scope.set_https_state(current_global_https_state); + + let (metadata, bytes) = match load_whole_resource( + request, + &global_scope.resource_threads().sender(), + &global_scope, + ) { + Err(_) => { + println!("error loading script {}", serialized_worker_url); + parent_sender + .send(CommonScriptMsg::Task( + WorkerEvent, + Box::new(SimpleWorkerErrorHandler::new(worker)), + Some(pipeline_id), + TaskSourceName::DOMManipulation, + )) + .unwrap(); + return; + }, + Ok((metadata, bytes)) => (metadata, bytes), + }; + scope.set_url(metadata.final_url); + global_scope.set_https_state(metadata.https_state); + let source = String::from_utf8_lossy(&bytes); + + unsafe { + // Handle interrupt requests + JS_AddInterruptCallback(*scope.get_cx(), Some(interrupt_callback)); + } - let roots = RootCollection::new(); - let _stack_roots_tls = StackRootTLS::new(&roots); - - let WorkerScriptLoadOrigin { referrer_url, referrer_policy, pipeline_id } = worker_load_origin; - - let request = RequestInit { - url: worker_url.clone(), - type_: RequestType::Script, - destination: Destination::Worker, - credentials_mode: CredentialsMode::Include, - use_url_credentials: true, - origin: worker_url, - pipeline_id: pipeline_id, - referrer_url: referrer_url, - referrer_policy: referrer_policy, - .. RequestInit::default() - }; - - let (metadata, bytes) = match load_whole_resource(request, - &init.resource_threads.sender()) { - Err(_) => { - println!("error loading script {}", serialized_worker_url); - parent_sender.send(CommonScriptMsg::RunnableMsg(WorkerEvent, - box SimpleWorkerErrorHandler::new(worker))).unwrap(); + if scope.is_closing() { return; } - Ok((metadata, bytes)) => (metadata, bytes) - }; - let url = metadata.final_url; - let source = String::from_utf8_lossy(&bytes); - - let runtime = unsafe { new_rt_and_cx() }; - *worker_rt_for_mainthread.lock().unwrap() = Some(SharedRt::new(&runtime)); - - let (devtools_mpsc_chan, devtools_mpsc_port) = channel(); - ROUTER.route_ipc_receiver_to_mpsc_sender(from_devtools_receiver, devtools_mpsc_chan); - - let (timer_tx, timer_rx) = channel(); - let (timer_ipc_chan, timer_ipc_port) = ipc::channel().unwrap(); - let worker_for_route = worker.clone(); - ROUTER.add_route(timer_ipc_port.to_opaque(), box move |message| { - let event = message.to().unwrap(); - timer_tx.send((worker_for_route.clone(), event)).unwrap(); - }); - - let global = DedicatedWorkerGlobalScope::new( - init, url, devtools_mpsc_port, runtime, - parent_sender.clone(), own_sender, receiver, - timer_ipc_chan, timer_rx, closing); - // FIXME(njn): workers currently don't have a unique ID suitable for using in reporter - // registration (#6631), so we instead use a random number and cross our fingers. - let scope = global.upcast::<WorkerGlobalScope>(); - - unsafe { - // Handle interrupt requests - JS_SetInterruptCallback(scope.runtime(), Some(interrupt_callback)); - } - if scope.is_closing() { - return; - } - - { - let _ar = AutoWorkerReset::new(&global, worker.clone()); - scope.execute_script(DOMString::from(source)); - } - - let reporter_name = format!("dedicated-worker-reporter-{}", random::<u64>()); - scope.upcast::<GlobalScope>().mem_profiler_chan().run_with_memory_reporting(|| { - // https://html.spec.whatwg.org/multipage/#event-loop-processing-model - // Step 1 - while let Ok(event) = global.receive_event() { - if scope.is_closing() { - break; - } - // Step 3 - global.handle_event(event); - // Step 6 + { let _ar = AutoWorkerReset::new(&global, worker.clone()); - global.upcast::<WorkerGlobalScope>().perform_a_microtask_checkpoint(); + scope.execute_script(DOMString::from(source)); } - }, reporter_name, parent_sender, CommonScriptMsg::CollectReports); - }).expect("Thread spawning failed"); + + let reporter_name = format!("dedicated-worker-reporter-{}", random::<u64>()); + scope + .upcast::<GlobalScope>() + .mem_profiler_chan() + .run_with_memory_reporting( + || { + // Step 29, Run the responsible event loop specified + // by inside settings until it is destroyed. + // The worker processing model remains on this step + // until the event loop is destroyed, + // which happens after the closing flag is set to true. + while !scope.is_closing() { + run_worker_event_loop(&*global, Some(&worker)); + } + }, + reporter_name, + parent_sender, + CommonScriptMsg::CollectReports, + ); + scope.clear_js_runtime(); + }) + .expect("Thread spawning failed") + } + + pub fn image_cache(&self) -> Arc<dyn ImageCache> { + self.image_cache.clone() } - pub fn script_chan(&self) -> Box<ScriptChan + Send> { - box WorkerThreadWorkerChan { + pub fn script_chan(&self) -> Box<dyn ScriptChan + Send> { + Box::new(WorkerThreadWorkerChan { sender: self.own_sender.clone(), worker: self.worker.borrow().as_ref().unwrap().clone(), - } + }) } - pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) { - let (tx, rx) = channel(); - let chan = box SendableWorkerScriptChan { + pub fn new_script_pair(&self) -> (Box<dyn ScriptChan + Send>, Box<dyn ScriptPort + Send>) { + let (tx, rx) = unbounded(); + let chan = Box::new(SendableWorkerScriptChan { sender: tx, worker: self.worker.borrow().as_ref().unwrap().clone(), - }; - (chan, box rx) - } - - pub fn process_event(&self, msg: CommonScriptMsg) { - self.handle_script_event(WorkerScriptMsg::Common(msg)); - } - - #[allow(unsafe_code)] - fn receive_event(&self) -> Result<MixedMessage, RecvError> { - let scope = self.upcast::<WorkerGlobalScope>(); - let worker_port = &self.receiver; - let timer_event_port = &self.timer_event_port; - let devtools_port = scope.from_devtools_receiver(); - - let sel = Select::new(); - let mut worker_handle = sel.handle(worker_port); - let mut timer_event_handle = sel.handle(timer_event_port); - let mut devtools_handle = sel.handle(devtools_port); - unsafe { - worker_handle.add(); - timer_event_handle.add(); - if scope.from_devtools_sender().is_some() { - devtools_handle.add(); - } - } - let ret = sel.wait(); - if ret == worker_handle.id() { - Ok(MixedMessage::FromWorker(try!(worker_port.recv()))) - } else if ret == timer_event_handle.id() { - Ok(MixedMessage::FromScheduler(try!(timer_event_port.recv()))) - } else if ret == devtools_handle.id() { - Ok(MixedMessage::FromDevtools(try!(devtools_port.recv()))) - } else { - panic!("unexpected select result!") - } + }); + (chan, Box::new(rx)) } fn handle_script_event(&self, msg: WorkerScriptMsg) { match msg { - WorkerScriptMsg::DOMMessage(data) => { + WorkerScriptMsg::DOMMessage { origin, data } => { let scope = self.upcast::<WorkerGlobalScope>(); let target = self.upcast(); - let _ac = JSAutoCompartment::new(scope.get_cx(), - scope.reflector().get_jsobject().get()); - rooted!(in(scope.get_cx()) let mut message = UndefinedValue()); - data.read(scope.upcast(), message.handle_mut()); - MessageEvent::dispatch_jsval(target, scope.upcast(), message.handle()); + let _ac = enter_realm(self); + rooted!(in(*scope.get_cx()) let mut message = UndefinedValue()); + if let Ok(ports) = structuredclone::read(scope.upcast(), data, message.handle_mut()) + { + MessageEvent::dispatch_jsval( + target, + scope.upcast(), + message.handle(), + Some(&origin.ascii_serialization()), + None, + ports, + ); + } else { + MessageEvent::dispatch_error(target, scope.upcast()); + } }, - WorkerScriptMsg::Common(CommonScriptMsg::RunnableMsg(_, runnable)) => { - runnable.handler() + WorkerScriptMsg::Common(msg) => { + self.upcast::<WorkerGlobalScope>().process_event(msg); }, - WorkerScriptMsg::Common(CommonScriptMsg::CollectReports(reports_chan)) => { - let scope = self.upcast::<WorkerGlobalScope>(); - let cx = scope.get_cx(); - let path_seg = format!("url({})", scope.get_url()); - let reports = get_reports(cx, path_seg); - reports_chan.send(reports); - } } } - fn handle_event(&self, event: MixedMessage) { - match event { - MixedMessage::FromDevtools(msg) => { - match msg { - DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) => - devtools::handle_evaluate_js(self.upcast(), string, sender), - DevtoolScriptControlMsg::GetCachedMessages(pipe_id, message_types, sender) => - devtools::handle_get_cached_messages(pipe_id, message_types, sender), - DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, bool_val) => - devtools::handle_wants_live_notifications(self.upcast(), bool_val), - _ => debug!("got an unusable devtools control message inside the worker!"), - } + fn handle_mixed_message(&self, msg: MixedMessage) -> bool { + // FIXME(#26324): `self.worker` is None in devtools messages. + match msg { + MixedMessage::FromDevtools(msg) => match msg { + DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) => { + devtools::handle_evaluate_js(self.upcast(), string, sender) + }, + DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, bool_val) => { + devtools::handle_wants_live_notifications(self.upcast(), bool_val) + }, + _ => debug!("got an unusable devtools control message inside the worker!"), }, - MixedMessage::FromScheduler((linked_worker, timer_event)) => { - match timer_event { - TimerEvent(TimerSource::FromWorker, id) => { - let _ar = AutoWorkerReset::new(self, linked_worker); - let scope = self.upcast::<WorkerGlobalScope>(); - scope.handle_fire_timer(id); - }, - TimerEvent(_, _) => { - panic!("A worker received a TimerEvent from a window.") - } - } - } - MixedMessage::FromWorker((linked_worker, msg)) => { + MixedMessage::FromWorker(DedicatedWorkerScriptMsg::CommonWorker( + linked_worker, + msg, + )) => { let _ar = AutoWorkerReset::new(self, linked_worker); self.handle_script_event(msg); - } + }, + MixedMessage::FromWorker(DedicatedWorkerScriptMsg::WakeUp) => {}, + MixedMessage::FromControl(DedicatedWorkerControlMsg::Exit) => { + return false; + }, } + true } + // https://html.spec.whatwg.org/multipage/#runtime-script-errors-2 + #[allow(unsafe_code)] pub fn forward_error_to_worker_object(&self, error_info: ErrorInfo) { let worker = self.worker.borrow().as_ref().unwrap().clone(); - // TODO: Should use the DOM manipulation task source. + let pipeline_id = self.upcast::<GlobalScope>().pipeline_id(); + let task = Box::new(task!(forward_error_to_worker_object: move || { + let worker = worker.root(); + let global = worker.global(); + + // Step 1. + let event = ErrorEvent::new( + &global, + atom!("error"), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable, + error_info.message.as_str().into(), + error_info.filename.as_str().into(), + error_info.lineno, + error_info.column, + HandleValue::null(), + ); + let event_status = + event.upcast::<Event>().fire(worker.upcast::<EventTarget>()); + + // Step 2. + if event_status == EventStatus::NotCanceled { + global.report_an_error(error_info, HandleValue::null()); + } + })); self.parent_sender - .send(CommonScriptMsg::RunnableMsg(WorkerEvent, - box WorkerErrorHandler::new(worker, error_info))) + .send(CommonScriptMsg::Task( + WorkerEvent, + task, + Some(pipeline_id), + TaskSourceName::DOMManipulation, + )) .unwrap(); } + + // https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage + fn post_message_impl( + &self, + cx: SafeJSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + let data = structuredclone::write(cx, message, Some(transfer))?; + let worker = self.worker.borrow().as_ref().unwrap().clone(); + let global_scope = self.upcast::<GlobalScope>(); + let pipeline_id = global_scope.pipeline_id(); + let task = Box::new(task!(post_worker_message: move || { + Worker::handle_message(worker, data); + })); + self.parent_sender + .send(CommonScriptMsg::Task( + WorkerEvent, + task, + Some(pipeline_id), + TaskSourceName::DOMManipulation, + )) + .expect("Sending to parent failed"); + Ok(()) + } + + pub(crate) fn browsing_context(&self) -> Option<BrowsingContextId> { + self.browsing_context + } } #[allow(unsafe_code)] unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool { + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + let global = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)); let worker = - Root::downcast::<WorkerGlobalScope>(GlobalScope::from_context(cx)) - .expect("global is not a worker scope"); + DomRoot::downcast::<WorkerGlobalScope>(global).expect("global is not a worker scope"); assert!(worker.is::<DedicatedWorkerGlobalScope>()); // A false response causes the script to terminate @@ -385,16 +631,32 @@ unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool { } impl DedicatedWorkerGlobalScopeMethods for DedicatedWorkerGlobalScope { - #[allow(unsafe_code)] - // https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage - unsafe fn PostMessage(&self, cx: *mut JSContext, message: HandleValue) -> ErrorResult { - let data = try!(StructuredCloneData::write(cx, message)); - let worker = self.worker.borrow().as_ref().unwrap().clone(); - self.parent_sender - .send(CommonScriptMsg::RunnableMsg(WorkerEvent, - box WorkerMessageHandler::new(worker, data))) - .unwrap(); - Ok(()) + /// https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage + fn PostMessage( + &self, + cx: SafeJSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + self.post_message_impl(cx, message, transfer) + } + + /// https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage + fn PostMessage_( + &self, + cx: SafeJSContext, + message: HandleValue, + options: RootedTraceableBox<PostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .transfer + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); + self.post_message_impl(cx, message, guard) } // https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-close diff --git a/components/script/dom/dissimilaroriginlocation.rs b/components/script/dom/dissimilaroriginlocation.rs index 7ea2e3efc3c..d1c466bb781 100644 --- a/components/script/dom/dissimilaroriginlocation.rs +++ b/components/script/dom/dissimilaroriginlocation.rs @@ -1,18 +1,16 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DissimilarOriginLocationBinding; -use dom::bindings::codegen::Bindings::DissimilarOriginLocationBinding::DissimilarOriginLocationMethods; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::Reflector; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::bindings::str::USVString; -use dom::dissimilaroriginwindow::DissimilarOriginWindow; +use crate::dom::bindings::codegen::Bindings::DissimilarOriginLocationBinding::DissimilarOriginLocationMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::str::USVString; +use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow; use dom_struct::dom_struct; -use servo_url::MutableOrigin; /// Represents a dissimilar-origin `Location` that exists in another script thread. /// @@ -27,7 +25,7 @@ pub struct DissimilarOriginLocation { reflector: Reflector, /// The window associated with this location. - window: JS<DissimilarOriginWindow>, + window: Dom<DissimilarOriginWindow>, } impl DissimilarOriginLocation { @@ -35,19 +33,15 @@ impl DissimilarOriginLocation { fn new_inherited(window: &DissimilarOriginWindow) -> DissimilarOriginLocation { DissimilarOriginLocation { reflector: Reflector::new(), - window: JS::from_ref(window), + window: Dom::from_ref(window), } } - pub fn new(window: &DissimilarOriginWindow) -> Root<DissimilarOriginLocation> { - reflect_dom_object(box DissimilarOriginLocation::new_inherited(window), - window, - DissimilarOriginLocationBinding::Wrap) - } - - #[allow(dead_code)] - pub fn origin(&self) -> &MutableOrigin { - self.window.origin() + pub fn new(window: &DissimilarOriginWindow) -> DomRoot<DissimilarOriginLocation> { + reflect_dom_object( + Box::new(DissimilarOriginLocation::new_inherited(window)), + window, + ) } } diff --git a/components/script/dom/dissimilaroriginwindow.rs b/components/script/dom/dissimilaroriginwindow.rs index 0c367d8d21f..adba29c23fb 100644 --- a/components/script/dom/dissimilaroriginwindow.rs +++ b/components/script/dom/dissimilaroriginwindow.rs @@ -1,25 +1,25 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding; -use dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding::DissimilarOriginWindowMethods; -use dom::bindings::error::{Error, ErrorResult}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::bindings::structuredclone::StructuredCloneData; -use dom::browsingcontext::BrowsingContext; -use dom::dissimilaroriginlocation::DissimilarOriginLocation; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding; +use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding::DissimilarOriginWindowMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowPostMessageOptions; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::USVString; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::dissimilaroriginlocation::DissimilarOriginLocation; +use crate::dom::globalscope::GlobalScope; +use crate::dom::windowproxy::WindowProxy; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use ipc_channel::ipc; -use js::jsapi::{JSContext, HandleValue}; +use js::jsapi::{Heap, JSObject}; use js::jsval::{JSVal, UndefinedValue}; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; use msg::constellation_msg::PipelineId; -use script_traits::ScriptMsg as ConstellationMsg; -use servo_url::ImmutableOrigin; -use servo_url::MutableOrigin; +use script_traits::{ScriptMsg, StructuredSerializedData}; use servo_url::ServoUrl; /// Represents a dissimilar-origin `Window` that exists in another script thread. @@ -28,7 +28,7 @@ use servo_url::ServoUrl; /// directly, but some of its accessors (for example `window.parent`) /// still need to function. /// -/// In `browsingcontext.rs`, we create a custom window proxy for these windows, +/// In `windowproxy.rs`, we create a custom window proxy for these windows, /// that throws security exceptions for most accessors. This is not a replacement /// for XOWs, but provides belt-and-braces security. #[dom_struct] @@ -36,80 +36,86 @@ pub struct DissimilarOriginWindow { /// The global for this window. globalscope: GlobalScope, - /// The browsing context this window is part of. - browsing_context: JS<BrowsingContext>, + /// The window proxy for this window. + window_proxy: Dom<WindowProxy>, /// The location of this window, initialized lazily. - location: MutNullableJS<DissimilarOriginLocation>, + location: MutNullableDom<DissimilarOriginLocation>, } impl DissimilarOriginWindow { #[allow(unsafe_code)] - pub fn new(global_to_clone_from: &GlobalScope, browsing_context: &BrowsingContext) -> Root<DissimilarOriginWindow> { + pub fn new(global_to_clone_from: &GlobalScope, window_proxy: &WindowProxy) -> DomRoot<Self> { let cx = global_to_clone_from.get_cx(); - // Any timer events fired on this window are ignored. - let (timer_event_chan, _) = ipc::channel().unwrap(); - let win = box DissimilarOriginWindow { - globalscope: GlobalScope::new_inherited(PipelineId::new(), - global_to_clone_from.devtools_chan().cloned(), - global_to_clone_from.mem_profiler_chan().clone(), - global_to_clone_from.time_profiler_chan().clone(), - global_to_clone_from.constellation_chan().clone(), - global_to_clone_from.scheduler_chan().clone(), - global_to_clone_from.resource_threads().clone(), - timer_event_chan, - global_to_clone_from.origin().clone()), - browsing_context: JS::from_ref(browsing_context), - location: MutNullableJS::new(None), - }; + let win = Box::new(Self { + globalscope: GlobalScope::new_inherited( + PipelineId::new(), + global_to_clone_from.devtools_chan().cloned(), + global_to_clone_from.mem_profiler_chan().clone(), + global_to_clone_from.time_profiler_chan().clone(), + global_to_clone_from.script_to_constellation_chan().clone(), + global_to_clone_from.scheduler_chan().clone(), + global_to_clone_from.resource_threads().clone(), + global_to_clone_from.origin().clone(), + global_to_clone_from.creation_url().clone(), + // FIXME(nox): The microtask queue is probably not important + // here, but this whole DOM interface is a hack anyway. + global_to_clone_from.microtask_queue().clone(), + global_to_clone_from.is_headless(), + global_to_clone_from.get_user_agent(), + global_to_clone_from.wgpu_id_hub(), + Some(global_to_clone_from.is_secure_context()), + ), + window_proxy: Dom::from_ref(window_proxy), + location: Default::default(), + }); unsafe { DissimilarOriginWindowBinding::Wrap(cx, win) } } - #[allow(dead_code)] - pub fn origin(&self) -> &MutableOrigin { - self.globalscope.origin() + pub fn window_proxy(&self) -> DomRoot<WindowProxy> { + DomRoot::from_ref(&*self.window_proxy) } } impl DissimilarOriginWindowMethods for DissimilarOriginWindow { // https://html.spec.whatwg.org/multipage/#dom-window - fn Window(&self) -> Root<BrowsingContext> { - Root::from_ref(&*self.browsing_context) + fn Window(&self) -> DomRoot<WindowProxy> { + self.window_proxy() } // https://html.spec.whatwg.org/multipage/#dom-self - fn Self_(&self) -> Root<BrowsingContext> { - Root::from_ref(&*self.browsing_context) + fn Self_(&self) -> DomRoot<WindowProxy> { + self.window_proxy() } // https://html.spec.whatwg.org/multipage/#dom-frames - fn Frames(&self) -> Root<BrowsingContext> { + fn Frames(&self) -> DomRoot<WindowProxy> { println!("calling cross origin frames"); - Root::from_ref(&*self.browsing_context) + self.window_proxy() } // https://html.spec.whatwg.org/multipage/#dom-parent - fn GetParent(&self) -> Option<Root<BrowsingContext>> { + fn GetParent(&self) -> Option<DomRoot<WindowProxy>> { // Steps 1-3. - if self.browsing_context.is_discarded() { + if self.window_proxy.is_browsing_context_discarded() { return None; } // Step 4. - if let Some(parent) = self.browsing_context.parent() { - return Some(Root::from_ref(parent)); + if let Some(parent) = self.window_proxy.parent() { + return Some(DomRoot::from_ref(parent)); } // Step 5. - Some(Root::from_ref(&*self.browsing_context)) + Some(DomRoot::from_ref(&*self.window_proxy)) } // https://html.spec.whatwg.org/multipage/#dom-top - fn GetTop(&self) -> Option<Root<BrowsingContext>> { + fn GetTop(&self) -> Option<DomRoot<WindowProxy>> { // Steps 1-3. - if self.browsing_context.is_discarded() { + if self.window_proxy.is_browsing_context_discarded() { return None; } // Steps 4-5. - Some(Root::from_ref(self.browsing_context.top())) + Some(DomRoot::from_ref(self.window_proxy.top())) } // https://html.spec.whatwg.org/multipage/#dom-length @@ -129,41 +135,45 @@ impl DissimilarOriginWindowMethods for DissimilarOriginWindow { false } - #[allow(unsafe_code)] - // https://html.spec.whatwg.org/multipage/#dom-window-postmessage - unsafe fn PostMessage(&self, cx: *mut JSContext, message: HandleValue, origin: DOMString) -> ErrorResult { - // Step 3-5. - let origin = match &origin[..] { - "*" => None, - "/" => { - // TODO: Should be the origin of the incumbent settings object. - None - }, - url => match ServoUrl::parse(&url) { - Ok(url) => Some(url.origin()), - Err(_) => return Err(Error::Syntax), - } - }; - - // Step 1-2, 6-8. - // TODO(#12717): Should implement the `transfer` argument. - let data = try!(StructuredCloneData::write(cx, message)); - - // Step 9. - self.post_message(origin, data); - Ok(()) + /// https://html.spec.whatwg.org/multipage/#dom-window-postmessage + fn PostMessage( + &self, + cx: JSContext, + message: HandleValue, + target_origin: USVString, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + self.post_message_impl(&target_origin, cx, message, transfer) + } + + /// https://html.spec.whatwg.org/multipage/#dom-window-postmessage-options + fn PostMessage_( + &self, + cx: JSContext, + message: HandleValue, + options: RootedTraceableBox<WindowPostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .parent + .transfer + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let transfer = CustomAutoRooterGuard::new(*cx, &mut rooted); + + self.post_message_impl(&options.targetOrigin, cx, message, transfer) } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-opener - unsafe fn Opener(&self, _: *mut JSContext) -> JSVal { + fn Opener(&self, _: JSContext) -> JSVal { // TODO: Implement x-origin opener UndefinedValue() } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-opener - unsafe fn SetOpener(&self, _: *mut JSContext, _: HandleValue) { + fn SetOpener(&self, _: JSContext, _: HandleValue) { // TODO: Implement x-origin opener } @@ -178,14 +188,61 @@ impl DissimilarOriginWindowMethods for DissimilarOriginWindow { } // https://html.spec.whatwg.org/multipage/#dom-location - fn Location(&self) -> Root<DissimilarOriginLocation> { - self.location.or_init(|| DissimilarOriginLocation::new(self)) + fn Location(&self) -> DomRoot<DissimilarOriginLocation> { + self.location + .or_init(|| DissimilarOriginLocation::new(self)) } } impl DissimilarOriginWindow { - pub fn post_message(&self, origin: Option<ImmutableOrigin>, data: StructuredCloneData) { - let msg = ConstellationMsg::PostMessage(self.browsing_context.frame_id(), origin, data.move_to_arraybuffer()); - let _ = self.upcast::<GlobalScope>().constellation_chan().send(msg); + /// https://html.spec.whatwg.org/multipage/#window-post-message-steps + fn post_message_impl( + &self, + target_origin: &USVString, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + // Step 6-7. + let data = structuredclone::write(cx, message, Some(transfer))?; + + self.post_message(target_origin, data) + } + + /// https://html.spec.whatwg.org/multipage/#window-post-message-steps + pub fn post_message( + &self, + target_origin: &USVString, + data: StructuredSerializedData, + ) -> ErrorResult { + // Step 1. + let target = self.window_proxy.browsing_context_id(); + // Step 2. + let incumbent = match GlobalScope::incumbent() { + None => panic!("postMessage called with no incumbent global"), + Some(incumbent) => incumbent, + }; + + let source_origin = incumbent.origin().immutable().clone(); + + // Step 3-5. + let target_origin = match target_origin.0[..].as_ref() { + "*" => None, + "/" => Some(source_origin.clone()), + url => match ServoUrl::parse(&url) { + Ok(url) => Some(url.origin().clone()), + Err(_) => return Err(Error::Syntax), + }, + }; + let msg = ScriptMsg::PostMessage { + target, + source: incumbent.pipeline_id(), + source_origin, + target_origin, + data: data, + }; + // Step 8 + let _ = incumbent.script_to_constellation_chan().send(msg); + Ok(()) } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index f5ffd973936..28b820f8919 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -1,148 +1,179 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use cookie_rs; -use core::nonzero::NonZero; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::animation_timeline::AnimationTimeline; +use crate::animations::Animations; +use crate::document_loader::{DocumentLoader, LoadType}; +use crate::dom::attr::Attr; +use crate::dom::beforeunloadevent::BeforeUnloadEvent; +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::{ref_filter_map, DomRefCell, Ref, RefMut}; +use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEventBinding::BeforeUnloadEventMethods; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ + DocumentMethods, DocumentReadyState, +}; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementBinding::HTMLIFrameElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; +use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter; +use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods; +use crate::dom::bindings::codegen::Bindings::TouchBinding::TouchMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::{ + FrameRequestCallback, ScrollBehavior, WindowMethods, +}; +use crate::dom::bindings::codegen::UnionTypes::{NodeOrString, StringOrElementCreationOptions}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::xmlname::XMLName::InvalidXMLName; +use crate::dom::bindings::xmlname::{ + namespace_from_domstring, validate_and_extract, xml_name_type, +}; +use crate::dom::cdatasection::CDATASection; +use crate::dom::comment::Comment; +use crate::dom::compositionevent::CompositionEvent; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::customelementregistry::CustomElementDefinition; +use crate::dom::customevent::CustomEvent; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::documentorshadowroot::{DocumentOrShadowRoot, StyleSheetInDocument}; +use crate::dom::documenttype::DocumentType; +use crate::dom::domimplementation::DOMImplementation; +use crate::dom::element::CustomElementCreationMode; +use crate::dom::element::{ + Element, ElementCreator, ElementPerformFullscreenEnter, ElementPerformFullscreenExit, +}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventDefault, EventStatus}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::focusevent::FocusEvent; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpucanvascontext::{GPUCanvasContext, WebGPUContextId}; +use crate::dom::hashchangeevent::HashChangeEvent; +use crate::dom::htmlanchorelement::HTMLAnchorElement; +use crate::dom::htmlareaelement::HTMLAreaElement; +use crate::dom::htmlbaseelement::HTMLBaseElement; +use crate::dom::htmlbodyelement::HTMLBodyElement; +use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlembedelement::HTMLEmbedElement; +use crate::dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement}; +use crate::dom::htmlheadelement::HTMLHeadElement; +use crate::dom::htmlhtmlelement::HTMLHtmlElement; +use crate::dom::htmliframeelement::HTMLIFrameElement; +use crate::dom::htmlimageelement::HTMLImageElement; +use crate::dom::htmlinputelement::HTMLInputElement; +use crate::dom::htmlscriptelement::{HTMLScriptElement, ScriptResult}; +use crate::dom::htmltextareaelement::HTMLTextAreaElement; +use crate::dom::htmltitleelement::HTMLTitleElement; +use crate::dom::keyboardevent::KeyboardEvent; +use crate::dom::location::Location; +use crate::dom::messageevent::MessageEvent; +use crate::dom::mouseevent::MouseEvent; +use crate::dom::node::{self, document_from_node, window_from_node, CloneChildrenFlag}; +use crate::dom::node::{Node, NodeDamage, NodeFlags, ShadowIncluding}; +use crate::dom::nodeiterator::NodeIterator; +use crate::dom::nodelist::NodeList; +use crate::dom::pagetransitionevent::PageTransitionEvent; +use crate::dom::processinginstruction::ProcessingInstruction; +use crate::dom::promise::Promise; +use crate::dom::range::Range; +use crate::dom::selection::Selection; +use crate::dom::servoparser::ServoParser; +use crate::dom::shadowroot::ShadowRoot; +use crate::dom::storageevent::StorageEvent; +use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner}; +use crate::dom::text::Text; +use crate::dom::touch::Touch; +use crate::dom::touchevent::TouchEvent; +use crate::dom::touchlist::TouchList; +use crate::dom::treewalker::TreeWalker; +use crate::dom::uievent::UIEvent; +use crate::dom::virtualmethods::vtable_for; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::dom::wheelevent::WheelEvent; +use crate::dom::window::{ReflowReason, Window}; +use crate::dom::windowproxy::WindowProxy; +use crate::fetch::FetchCanceller; +use crate::realms::{AlreadyInRealm, InRealm}; +use crate::script_runtime::JSContext; +use crate::script_runtime::{CommonScriptMsg, ScriptThreadEventCategory}; +use crate::script_thread::{MainThreadScriptMsg, ScriptThread}; +use crate::stylesheet_set::StylesheetSetRef; +use crate::task::TaskBox; +use crate::task_source::{TaskSource, TaskSourceName}; +use crate::timers::OneshotTimerCallback; +use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg}; +use content_security_policy::{self as csp, CspList}; +use cookie::Cookie; use devtools_traits::ScriptToDevtoolsControlMsg; -use document_loader::{DocumentLoader, LoadType}; -use dom::activation::{ActivationSource, synthetic_click_activation}; -use dom::attr::Attr; -use dom::beforeunloadevent::BeforeUnloadEvent; -use dom::bindings::callback::ExceptionHandling; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods; -use dom::bindings::codegen::Bindings::DocumentBinding; -use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; -use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; -use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementBinding::HTMLIFrameElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter; -use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods; -use dom::bindings::codegen::Bindings::TouchBinding::TouchMethods; -use dom::bindings::codegen::Bindings::WindowBinding::{FrameRequestCallback, ScrollBehavior, WindowMethods}; -use dom::bindings::codegen::UnionTypes::NodeOrString; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; -use dom::bindings::js::{JS, LayoutJS, MutNullableJS, Root}; -use dom::bindings::js::RootedReference; -use dom::bindings::num::Finite; -use dom::bindings::refcounted::{Trusted, TrustedPromise}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml_name_type}; -use dom::bindings::xmlname::XMLName::InvalidXMLName; -use dom::browsingcontext::BrowsingContext; -use dom::closeevent::CloseEvent; -use dom::comment::Comment; -use dom::customevent::CustomEvent; -use dom::documentfragment::DocumentFragment; -use dom::documenttype::DocumentType; -use dom::domimplementation::DOMImplementation; -use dom::element::{Element, ElementCreator, ElementPerformFullscreenEnter, ElementPerformFullscreenExit}; -use dom::errorevent::ErrorEvent; -use dom::event::{Event, EventBubbles, EventCancelable, EventDefault, EventStatus}; -use dom::eventtarget::EventTarget; -use dom::focusevent::FocusEvent; -use dom::forcetouchevent::ForceTouchEvent; -use dom::globalscope::GlobalScope; -use dom::hashchangeevent::HashChangeEvent; -use dom::htmlanchorelement::HTMLAnchorElement; -use dom::htmlappletelement::HTMLAppletElement; -use dom::htmlareaelement::HTMLAreaElement; -use dom::htmlbaseelement::HTMLBaseElement; -use dom::htmlbodyelement::HTMLBodyElement; -use dom::htmlcollection::{CollectionFilter, HTMLCollection}; -use dom::htmlelement::HTMLElement; -use dom::htmlembedelement::HTMLEmbedElement; -use dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement}; -use dom::htmlheadelement::HTMLHeadElement; -use dom::htmlhtmlelement::HTMLHtmlElement; -use dom::htmliframeelement::HTMLIFrameElement; -use dom::htmlimageelement::HTMLImageElement; -use dom::htmlscriptelement::{HTMLScriptElement, ScriptResult}; -use dom::htmltitleelement::HTMLTitleElement; -use dom::keyboardevent::KeyboardEvent; -use dom::location::Location; -use dom::messageevent::MessageEvent; -use dom::mouseevent::MouseEvent; -use dom::node::{self, CloneChildrenFlag, Node, NodeDamage, window_from_node, IS_IN_DOC, LayoutNodeHelpers}; -use dom::node::VecPreOrderInsertionHelper; -use dom::nodeiterator::NodeIterator; -use dom::nodelist::NodeList; -use dom::pagetransitionevent::PageTransitionEvent; -use dom::popstateevent::PopStateEvent; -use dom::processinginstruction::ProcessingInstruction; -use dom::progressevent::ProgressEvent; -use dom::promise::Promise; -use dom::range::Range; -use dom::servoparser::ServoParser; -use dom::storageevent::StorageEvent; -use dom::stylesheetlist::StyleSheetList; -use dom::text::Text; -use dom::touch::Touch; -use dom::touchevent::TouchEvent; -use dom::touchlist::TouchList; -use dom::treewalker::TreeWalker; -use dom::uievent::UIEvent; -use dom::webglcontextevent::WebGLContextEvent; -use dom::window::{ReflowReason, Window}; use dom_struct::dom_struct; -use encoding::EncodingRef; -use encoding::all::UTF_8; -use euclid::point::Point2D; -use html5ever_atoms::{LocalName, QualName}; -use hyper::header::{Header, SetCookie}; +use embedder_traits::EmbedderMsg; +use encoding_rs::{Encoding, UTF_8}; +use euclid::default::{Point2D, Rect, Size2D}; +use html5ever::{LocalName, Namespace, QualName}; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; -use js::jsapi::{JSContext, JSObject, JSRuntime}; -use js::jsapi::JS_GetRuntime; -use msg::constellation_msg::{ALT, CONTROL, SHIFT, SUPER}; -use msg::constellation_msg::{FrameId, Key, KeyModifiers, KeyState}; -use net_traits::{FetchResponseMsg, IpcSend, ReferrerPolicy}; -use net_traits::CookieSource::NonHTTP; -use net_traits::CoreResourceMsg::{GetCookiesForUrl, SetCookiesForUrl}; +use js::jsapi::JSObject; +use keyboard_types::{Code, Key, KeyState}; +use metrics::{ + InteractiveFlag, InteractiveMetrics, InteractiveWindow, ProfilerMetadataFactory, + ProgressiveWebMetric, +}; +use mime::{self, Mime}; +use msg::constellation_msg::BrowsingContextId; use net_traits::pub_domains::is_pub_domain; -use net_traits::request::RequestInit; +use net_traits::request::RequestBuilder; use net_traits::response::HttpsState; +use net_traits::CookieSource::NonHTTP; +use net_traits::CoreResourceMsg::{GetCookiesForUrl, SetCookiesForUrl}; +use net_traits::{FetchResponseMsg, IpcSend, ReferrerPolicy}; use num_traits::ToPrimitive; -use script_layout_interface::message::{Msg, ReflowQueryType}; -use script_runtime::{CommonScriptMsg, ScriptThreadEventCategory}; -use script_thread::{MainThreadScriptMsg, Runnable, ScriptThread}; -use script_traits::{AnimationState, CompositorEvent, DocumentActivity}; -use script_traits::{MouseButton, MouseEventType, MozBrowserEvent}; -use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TouchpadPressurePhase}; -use script_traits::{TouchEventType, TouchId}; -use script_traits::UntrustedNodeAddress; +use percent_encoding::percent_decode; +use profile_traits::ipc as profile_ipc; +use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType}; +use script_layout_interface::message::{Msg, PendingRestyle, ReflowGoal}; +use script_layout_interface::TrustedNodeAddress; +use script_traits::{AnimationState, DocumentActivity, MouseButton, MouseEventType}; +use script_traits::{ + MsDuration, ScriptMsg, TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta, +}; +use servo_arc::Arc; use servo_atoms::Atom; -use servo_config::prefs::PREFS; +use servo_config::pref; +use servo_media::{ClientContextId, ServoMedia}; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; -use std::ascii::AsciiExt; -use std::borrow::ToOwned; -use std::cell::{Cell, Ref, RefMut}; -use std::collections::{HashMap, HashSet, VecDeque}; +use std::borrow::Cow; +use std::cell::Cell; +use std::cmp::Ordering; use std::collections::hash_map::Entry::{Occupied, Vacant}; +use std::collections::{HashMap, HashSet, VecDeque}; use std::default::Default; -use std::iter::once; use std::mem; +use std::ptr::NonNull; use std::rc::Rc; -use std::sync::Arc; +use std::slice::from_ref; use std::time::{Duration, Instant}; use style::attr::AttrValue; -use style::context::{QuirksMode, ReflowGoal}; -use style::restyle_hints::{RestyleHint, RESTYLE_SELF, RESTYLE_STYLE_ATTRIBUTE}; -use style::selector_parser::{RestyleDamage, Snapshot}; +use style::context::QuirksMode; +use style::invalidation::element::restyle_hints::RestyleHint; +use style::media_queries::{Device, MediaType}; +use style::selector_parser::Snapshot; use style::shared_lock::SharedRwLock as StyleSharedRwLock; -use style::str::{HTML_SPACE_CHARACTERS, split_html_space_chars, str_join}; -use style::stylesheets::Stylesheet; -use task_source::TaskSource; -use time; -use timers::OneshotTimerCallback; +use style::str::{split_html_space_chars, str_join}; +use style::stylesheet_set::DocumentStylesheetSet; +use style::stylesheets::{Origin, OriginSet, Stylesheet}; use url::Host; -use url::percent_encoding::percent_decode; -use webrender_traits::ClipId; +use uuid::Uuid; +use webrender_api::units::DeviceIntRect; /// The number of times we are allowed to see spurious `requestAnimationFrame()` calls before /// falling back to fake ones. @@ -158,155 +189,160 @@ pub enum TouchEventResult { Forwarded, } -#[derive(Clone, Copy, Debug, HeapSizeOf, JSTraceable, PartialEq)] -pub enum IsHTMLDocument { - HTMLDocument, - NonHTMLDocument, +#[derive(Clone, Copy, PartialEq)] +pub enum FireMouseEventType { + Move, + Over, + Out, + Enter, + Leave, } -#[derive(JSTraceable, HeapSizeOf)] -#[must_root] -pub struct StylesheetInDocument { - pub node: JS<Node>, - #[ignore_heap_size_of = "Arc"] - pub stylesheet: Arc<Stylesheet>, +impl FireMouseEventType { + pub fn as_str(&self) -> &str { + match self { + &FireMouseEventType::Move => "mousemove", + &FireMouseEventType::Over => "mouseover", + &FireMouseEventType::Out => "mouseout", + &FireMouseEventType::Enter => "mouseenter", + &FireMouseEventType::Leave => "mouseleave", + } + } } -#[derive(Debug, HeapSizeOf)] -pub struct PendingRestyle { - /// If this element had a state or attribute change since the last restyle, track - /// the original condition of the element. - pub snapshot: Option<Snapshot>, - - /// Any explicit restyles hints that have been accumulated for this element. - pub hint: RestyleHint, - - /// Any explicit restyles damage that have been accumulated for this element. - pub damage: RestyleDamage, +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] +pub enum IsHTMLDocument { + HTMLDocument, + NonHTMLDocument, } -impl PendingRestyle { - pub fn new() -> Self { - PendingRestyle { - snapshot: None, - hint: RestyleHint::empty(), - damage: RestyleDamage::empty(), - } - } +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +enum FocusTransaction { + /// No focus operation is in effect. + NotInTransaction, + /// A focus operation is in effect. + /// Contains the element that has most recently requested focus for itself. + InTransaction(Option<Dom<Element>>), } -/// https://dom.spec.whatwg.org/#document +/// <https://dom.spec.whatwg.org/#document> #[dom_struct] pub struct Document { node: Node, - window: JS<Window>, - implementation: MutNullableJS<DOMImplementation>, - location: MutNullableJS<Location>, - content_type: DOMString, + document_or_shadow_root: DocumentOrShadowRoot, + window: Dom<Window>, + implementation: MutNullableDom<DOMImplementation>, + #[ignore_malloc_size_of = "type from external crate"] + content_type: Mime, last_modified: Option<String>, - encoding: Cell<EncodingRef>, + encoding: Cell<&'static Encoding>, has_browsing_context: bool, is_html_document: bool, activity: Cell<DocumentActivity>, - url: DOMRefCell<ServoUrl>, + url: DomRefCell<ServoUrl>, + #[ignore_malloc_size_of = "defined in selectors"] quirks_mode: Cell<QuirksMode>, /// Caches for the getElement methods - id_map: DOMRefCell<HashMap<Atom, Vec<JS<Element>>>>, - tag_map: DOMRefCell<HashMap<LocalName, JS<HTMLCollection>>>, - tagns_map: DOMRefCell<HashMap<QualName, JS<HTMLCollection>>>, - classes_map: DOMRefCell<HashMap<Vec<Atom>, JS<HTMLCollection>>>, - images: MutNullableJS<HTMLCollection>, - embeds: MutNullableJS<HTMLCollection>, - links: MutNullableJS<HTMLCollection>, - forms: MutNullableJS<HTMLCollection>, - scripts: MutNullableJS<HTMLCollection>, - anchors: MutNullableJS<HTMLCollection>, - applets: MutNullableJS<HTMLCollection>, + id_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>, + name_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>, + tag_map: DomRefCell<HashMap<LocalName, Dom<HTMLCollection>>>, + tagns_map: DomRefCell<HashMap<QualName, Dom<HTMLCollection>>>, + classes_map: DomRefCell<HashMap<Vec<Atom>, Dom<HTMLCollection>>>, + images: MutNullableDom<HTMLCollection>, + embeds: MutNullableDom<HTMLCollection>, + links: MutNullableDom<HTMLCollection>, + forms: MutNullableDom<HTMLCollection>, + scripts: MutNullableDom<HTMLCollection>, + anchors: MutNullableDom<HTMLCollection>, + applets: MutNullableDom<HTMLCollection>, /// Lock use for style attributes and author-origin stylesheet objects in this document. /// Can be acquired once for accessing many objects. style_shared_lock: StyleSharedRwLock, /// List of stylesheets associated with nodes in this document. |None| if the list needs to be refreshed. - stylesheets: DOMRefCell<Option<Vec<StylesheetInDocument>>>, - /// Whether the list of stylesheets has changed since the last reflow was triggered. - stylesheets_changed_since_reflow: Cell<bool>, - stylesheet_list: MutNullableJS<StyleSheetList>, + stylesheets: DomRefCell<DocumentStylesheetSet<StyleSheetInDocument>>, + stylesheet_list: MutNullableDom<StyleSheetList>, ready_state: Cell<DocumentReadyState>, /// Whether the DOMContentLoaded event has already been dispatched. domcontentloaded_dispatched: Cell<bool>, - /// The element that has most recently requested focus for itself. - possibly_focused: MutNullableJS<Element>, + /// The state of this document's focus transaction. + focus_transaction: DomRefCell<FocusTransaction>, /// The element that currently has the document focus context. - focused: MutNullableJS<Element>, + focused: MutNullableDom<Element>, /// The script element that is currently executing. - current_script: MutNullableJS<HTMLScriptElement>, - /// https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script - pending_parsing_blocking_script: DOMRefCell<Option<PendingScript>>, + current_script: MutNullableDom<HTMLScriptElement>, + /// <https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script> + pending_parsing_blocking_script: DomRefCell<Option<PendingScript>>, /// Number of stylesheets that block executing the next parser-inserted script script_blocking_stylesheets_count: Cell<u32>, /// https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-when-the-document-has-finished-parsing deferred_scripts: PendingInOrderScriptVec, - /// https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible + /// <https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible> asap_in_order_scripts_list: PendingInOrderScriptVec, - /// https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible - asap_scripts_set: DOMRefCell<Vec<JS<HTMLScriptElement>>>, - /// https://html.spec.whatwg.org/multipage/#concept-n-noscript + /// <https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible> + asap_scripts_set: DomRefCell<Vec<Dom<HTMLScriptElement>>>, + /// <https://html.spec.whatwg.org/multipage/#concept-n-noscript> /// True if scripting is enabled for all scripts in this document scripting_enabled: bool, - /// https://html.spec.whatwg.org/multipage/#animation-frame-callback-identifier + /// <https://html.spec.whatwg.org/multipage/#animation-frame-callback-identifier> /// Current identifier of animation frame callback animation_frame_ident: Cell<u32>, - /// https://html.spec.whatwg.org/multipage/#list-of-animation-frame-callbacks + /// <https://html.spec.whatwg.org/multipage/#list-of-animation-frame-callbacks> /// List of animation frame callbacks - animation_frame_list: DOMRefCell<Vec<(u32, Option<AnimationFrameCallback>)>>, + animation_frame_list: DomRefCell<Vec<(u32, Option<AnimationFrameCallback>)>>, /// Whether we're in the process of running animation callbacks. /// /// Tracking this is not necessary for correctness. Instead, it is an optimization to avoid /// sending needless `ChangeRunningAnimationsState` messages to the compositor. running_animation_callbacks: Cell<bool>, /// Tracks all outstanding loads related to this document. - loader: DOMRefCell<DocumentLoader>, + loader: DomRefCell<DocumentLoader>, /// The current active HTML parser, to allow resuming after interruptions. - current_parser: MutNullableJS<ServoParser>, + current_parser: MutNullableDom<ServoParser>, /// When we should kick off a reflow. This happens during parsing. reflow_timeout: Cell<Option<u64>>, /// The cached first `base` element with an `href` attribute. - base_element: MutNullableJS<HTMLBaseElement>, + base_element: MutNullableDom<HTMLBaseElement>, /// This field is set to the document itself for inert documents. - /// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document - appropriate_template_contents_owner_document: MutNullableJS<Document>, + /// <https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document> + appropriate_template_contents_owner_document: MutNullableDom<Document>, /// Information on elements needing restyle to ship over to the layout thread when the /// time comes. - pending_restyles: DOMRefCell<HashMap<JS<Element>, PendingRestyle>>, + pending_restyles: DomRefCell<HashMap<Dom<Element>, PendingRestyle>>, /// This flag will be true if layout suppressed a reflow attempt that was /// needed in order for the page to be painted. needs_paint: Cell<bool>, - /// http://w3c.github.io/touch-events/#dfn-active-touch-point - active_touch_points: DOMRefCell<Vec<JS<Touch>>>, + /// <http://w3c.github.io/touch-events/#dfn-active-touch-point> + active_touch_points: DomRefCell<Vec<Dom<Touch>>>, /// Navigation Timing properties: - /// https://w3c.github.io/navigation-timing/#sec-PerformanceNavigationTiming + /// <https://w3c.github.io/navigation-timing/#sec-PerformanceNavigationTiming> dom_loading: Cell<u64>, dom_interactive: Cell<u64>, dom_content_loaded_event_start: Cell<u64>, dom_content_loaded_event_end: Cell<u64>, dom_complete: Cell<u64>, + top_level_dom_complete: Cell<u64>, load_event_start: Cell<u64>, load_event_end: Cell<u64>, - /// https://html.spec.whatwg.org/multipage/#concept-document-https-state + unload_event_start: Cell<u64>, + unload_event_end: Cell<u64>, + /// <https://html.spec.whatwg.org/multipage/#concept-document-https-state> https_state: Cell<HttpsState>, - touchpad_pressure_phase: Cell<TouchpadPressurePhase>, /// The document's origin. origin: MutableOrigin, /// https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-states referrer_policy: Cell<Option<ReferrerPolicy>>, - /// https://html.spec.whatwg.org/multipage/#dom-document-referrer + /// <https://html.spec.whatwg.org/multipage/#dom-document-referrer> referrer: Option<String>, - /// https://html.spec.whatwg.org/multipage/#target-element - target_element: MutNullableJS<Element>, - /// https://w3c.github.io/uievents/#event-type-dblclick - #[ignore_heap_size_of = "Defined in std"] - last_click_info: DOMRefCell<Option<(Instant, Point2D<f32>)>>, - /// https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter + /// <https://html.spec.whatwg.org/multipage/#target-element> + target_element: MutNullableDom<Element>, + /// <https://w3c.github.io/uievents/#event-type-dblclick> + #[ignore_malloc_size_of = "Defined in std"] + last_click_info: DomRefCell<Option<(Instant, Point2D<f32>)>>, + /// <https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter> ignore_destructive_writes_counter: Cell<u32>, + /// <https://html.spec.whatwg.org/multipage/#ignore-opens-during-unload-counter> + ignore_opens_during_unload_counter: Cell<u32>, /// The number of spurious `requestAnimationFrame()` requests we've received. /// /// A rAF request is considered spurious if nothing was actually reflowed. @@ -319,16 +355,67 @@ pub struct Document { /// See also: https://github.com/servo/servo/issues/10110 dom_count: Cell<u32>, /// Entry node for fullscreen. - fullscreen_element: MutNullableJS<Element>, + fullscreen_element: MutNullableDom<Element>, /// Map from ID to set of form control elements that have that ID as /// their 'form' content attribute. Used to reset form controls /// whenever any element with the same ID as the form attribute /// is inserted or removed from the document. /// See https://html.spec.whatwg.org/multipage/#form-owner - form_id_listener_map: DOMRefCell<HashMap<Atom, HashSet<JS<Element>>>>, + form_id_listener_map: DomRefCell<HashMap<Atom, HashSet<Dom<Element>>>>, + interactive_time: DomRefCell<InteractiveMetrics>, + tti_window: DomRefCell<InteractiveWindow>, + /// RAII canceller for Fetch + canceller: FetchCanceller, + /// https://html.spec.whatwg.org/multipage/#throw-on-dynamic-markup-insertion-counter + throw_on_dynamic_markup_insertion_counter: Cell<u64>, + /// https://html.spec.whatwg.org/multipage/#page-showing + page_showing: Cell<bool>, + /// Whether the document is salvageable. + salvageable: Cell<bool>, + /// Whether the document was aborted with an active parser + active_parser_was_aborted: Cell<bool>, + /// Whether the unload event has already been fired. + fired_unload: Cell<bool>, + /// List of responsive images + responsive_images: DomRefCell<Vec<Dom<HTMLImageElement>>>, + /// Number of redirects for the document load + redirect_count: Cell<u16>, + /// Number of outstanding requests to prevent JS or layout from running. + script_and_layout_blockers: Cell<u32>, + /// List of tasks to execute as soon as last script/layout blocker is removed. + #[ignore_malloc_size_of = "Measuring trait objects is hard"] + delayed_tasks: DomRefCell<Vec<Box<dyn TaskBox>>>, + /// https://html.spec.whatwg.org/multipage/#completely-loaded + completely_loaded: Cell<bool>, + /// Set of shadow roots connected to the document tree. + shadow_roots: DomRefCell<HashSet<Dom<ShadowRoot>>>, + /// Whether any of the shadow roots need the stylesheets flushed. + shadow_roots_styles_changed: Cell<bool>, + /// List of registered media controls. + /// We need to keep this list to allow the media controls to + /// access the "privileged" document.servoGetMediaControls(id) API, + /// where `id` needs to match any of the registered ShadowRoots + /// hosting the media controls UI. + media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>, + /// List of all WebGL context IDs that need flushing. + dirty_webgl_contexts: DomRefCell<HashMap<WebGLContextId, Dom<WebGLRenderingContext>>>, + /// List of all WebGPU context IDs that need flushing. + dirty_webgpu_contexts: DomRefCell<HashMap<WebGPUContextId, Dom<GPUCanvasContext>>>, + /// https://html.spec.whatwg.org/multipage/#concept-document-csp-list + #[ignore_malloc_size_of = "Defined in rust-content-security-policy"] + csp_list: DomRefCell<Option<CspList>>, + /// https://w3c.github.io/slection-api/#dfn-selection + selection: MutNullableDom<Selection>, + /// A timeline for animations which is used for synchronizing animations. + /// https://drafts.csswg.org/web-animations/#timeline + animation_timeline: DomRefCell<AnimationTimeline>, + /// Animations for this Document + animations: DomRefCell<Animations>, + /// The nearest inclusive ancestors to all the nodes that require a restyle. + dirty_root: MutNullableDom<Element>, } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] struct ImagesFilter; impl CollectionFilter for ImagesFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { @@ -336,7 +423,7 @@ impl CollectionFilter for ImagesFilter { } } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] struct EmbedsFilter; impl CollectionFilter for EmbedsFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { @@ -344,16 +431,16 @@ impl CollectionFilter for EmbedsFilter { } } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] struct LinksFilter; impl CollectionFilter for LinksFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { (elem.is::<HTMLAnchorElement>() || elem.is::<HTMLAreaElement>()) && - elem.has_attribute(&local_name!("href")) + elem.has_attribute(&local_name!("href")) } } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] struct FormsFilter; impl CollectionFilter for FormsFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { @@ -361,7 +448,7 @@ impl CollectionFilter for FormsFilter { } } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] struct ScriptsFilter; impl CollectionFilter for ScriptsFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { @@ -369,7 +456,7 @@ impl CollectionFilter for ScriptsFilter { } } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] struct AnchorsFilter; impl CollectionFilter for AnchorsFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { @@ -377,33 +464,141 @@ impl CollectionFilter for AnchorsFilter { } } -#[derive(JSTraceable, HeapSizeOf)] -struct AppletsFilter; -impl CollectionFilter for AppletsFilter { - fn filter(&self, elem: &Element, _root: &Node) -> bool { - elem.is::<HTMLAppletElement>() - } +enum ElementLookupResult { + None, + One(DomRoot<Element>), + Many, } +#[allow(non_snake_case)] impl Document { + pub fn note_node_with_dirty_descendants(&self, node: &Node) { + debug_assert!(*node.owner_doc() == *self); + if !node.is_connected() { + return; + } + + let parent = match node.inclusive_ancestors(ShadowIncluding::Yes).nth(1) { + Some(parent) => parent, + None => { + // There is no parent so this is the Document node, so we + // behave as if we were called with the document element. + let document_element = match self.GetDocumentElement() { + Some(element) => element, + None => return, + }; + if let Some(dirty_root) = self.dirty_root.get() { + // There was an existing dirty root so we mark its + // ancestors as dirty until the document element. + for ancestor in dirty_root + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::Yes) + { + if ancestor.is::<Element>() { + ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true); + } + } + } + self.dirty_root.set(Some(&document_element)); + return; + }, + }; + + if parent.is::<Element>() { + if !parent.is_styled() { + return; + } + + if parent.is_display_none() { + return; + } + } + + let element_parent: DomRoot<Element>; + let element = match node.downcast::<Element>() { + Some(element) => element, + None => { + // Current node is not an element, it's probably a text node, + // we try to get its element parent. + match DomRoot::downcast::<Element>(parent) { + Some(parent) => { + element_parent = parent; + &element_parent + }, + None => { + // Parent is not an element so it must be a document, + // and this is not an element either, so there is + // nothing to do. + return; + }, + } + }, + }; + + let dirty_root = match self.dirty_root.get() { + None => { + element + .upcast::<Node>() + .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true); + self.dirty_root.set(Some(element)); + return; + }, + Some(root) => root, + }; + + for ancestor in element + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::Yes) + { + if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) { + return; + } + if ancestor.is::<Element>() { + ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true); + } + } + + let new_dirty_root = element + .upcast::<Node>() + .common_ancestor(dirty_root.upcast(), ShadowIncluding::Yes) + .expect("Couldn't find common ancestor"); + + let mut has_dirty_descendants = true; + for ancestor in dirty_root + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::Yes) + { + ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, has_dirty_descendants); + has_dirty_descendants &= *ancestor != *new_dirty_root; + } + self.dirty_root + .set(Some(new_dirty_root.downcast::<Element>().unwrap())); + } + + pub fn take_dirty_root(&self) -> Option<DomRoot<Element>> { + self.dirty_root.take() + } + #[inline] pub fn loader(&self) -> Ref<DocumentLoader> { self.loader.borrow() } #[inline] - pub fn mut_loader(&self) -> RefMut<DocumentLoader> { + pub fn loader_mut(&self) -> RefMut<DocumentLoader> { self.loader.borrow_mut() } #[inline] - pub fn has_browsing_context(&self) -> bool { self.has_browsing_context } + pub fn has_browsing_context(&self) -> bool { + self.has_browsing_context + } - /// https://html.spec.whatwg.org/multipage/#concept-document-bc + /// <https://html.spec.whatwg.org/multipage/#concept-document-bc> #[inline] - pub fn browsing_context(&self) -> Option<Root<BrowsingContext>> { + pub fn browsing_context(&self) -> Option<DomRoot<WindowProxy>> { if self.has_browsing_context { - self.window.maybe_browsing_context() + self.window.undiscarded_window_proxy() } else { None } @@ -421,7 +616,6 @@ impl Document { pub fn set_https_state(&self, https_state: HttpsState) { self.https_state.set(https_state); - self.trigger_mozbrowser_event(MozBrowserEvent::SecurityChange(https_state)); } pub fn is_fully_active(&self) -> bool { @@ -435,22 +629,68 @@ impl Document { pub fn set_activity(&self, activity: DocumentActivity) { // This function should only be called on documents with a browsing context assert!(self.has_browsing_context); + if activity == self.activity.get() { + return; + } + // Set the document's activity level, reflow if necessary, and suspend or resume timers. - if activity != self.activity.get() { - self.activity.set(activity); - if activity == DocumentActivity::FullyActive { - self.title_changed(); - self.dirty_all_nodes(); - self.window().reflow( - ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::CachedPageNeededReflow - ); - self.window().resume(); - } else { - self.window().suspend(); - } + self.activity.set(activity); + let media = ServoMedia::get().unwrap(); + let pipeline_id = self.window().pipeline_id(); + let client_context_id = + ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get()); + + if activity != DocumentActivity::FullyActive { + self.window().suspend(); + media.suspend(&client_context_id); + return; + } + + self.title_changed(); + self.dirty_all_nodes(); + self.window() + .reflow(ReflowGoal::Full, ReflowReason::CachedPageNeededReflow); + self.window().resume(); + media.resume(&client_context_id); + + if self.ready_state.get() != DocumentReadyState::Complete { + return; } + + // html.spec.whatwg.org/multipage/#history-traversal + // Step 4.6 + let document = Trusted::new(self); + self.window + .task_manager() + .dom_manipulation_task_source() + .queue( + task!(fire_pageshow_event: move || { + let document = document.root(); + let window = document.window(); + // Step 4.6.1 + if document.page_showing.get() { + return; + } + // Step 4.6.2 + document.page_showing.set(true); + // Step 4.6.4 + let event = PageTransitionEvent::new( + window, + atom!("pageshow"), + false, // bubbles + false, // cancelable + true, // persisted + ); + let event = event.upcast::<Event>(); + event.set_trusted(true); + // FIXME(nox): Why are errors silenced here? + let _ = window.dispatch_event_with_target_override( + &event, + ); + }), + self.window.upcast(), + ) + .unwrap(); } pub fn origin(&self) -> &MutableOrigin { @@ -468,10 +708,25 @@ impl Document { // https://html.spec.whatwg.org/multipage/#fallback-base-url pub fn fallback_base_url(&self) -> ServoUrl { - // Step 1: iframe srcdoc (#4767). - // Step 2: about:blank with a creator browsing context. - // Step 3. - self.url() + let document_url = self.url(); + if let Some(browsing_context) = self.browsing_context() { + // Step 1: If document is an iframe srcdoc document, then return the + // document base URL of document's browsing context's container document. + let container_base_url = browsing_context + .parent() + .and_then(|parent| parent.document()) + .map(|document| document.base_url()); + if document_url.as_str() == "about:srcdoc" && container_base_url.is_some() { + return container_base_url.unwrap(); + } + // Step 2: If document's URL is about:blank, and document's browsing + // context's creator base URL is non-null, then return that creator base URL. + if document_url.as_str() == "about:blank" && browsing_context.has_creator_base_url() { + return browsing_context.creator_base_url().unwrap(); + } + } + // Step 3: Return document's URL. + document_url } // https://html.spec.whatwg.org/multipage/#document-base-url @@ -488,33 +743,48 @@ impl Document { self.needs_paint.get() } - pub fn needs_reflow(&self) -> bool { + pub fn needs_reflow(&self) -> Option<ReflowTriggerCondition> { // FIXME: This should check the dirty bit on the document, // not the document element. Needs some layout changes to make // that workable. - match self.GetDocumentElement() { - Some(root) => { - root.upcast::<Node>().has_dirty_descendants() || - !self.pending_restyles.borrow().is_empty() || - self.needs_paint() - } - None => false, + if self.stylesheets.borrow().has_changed() { + return Some(ReflowTriggerCondition::StylesheetsChanged); + } + + let root = self.GetDocumentElement()?; + if root.upcast::<Node>().has_dirty_descendants() { + return Some(ReflowTriggerCondition::DirtyDescendants); } + + if !self.pending_restyles.borrow().is_empty() { + return Some(ReflowTriggerCondition::PendingRestyles); + } + + if self.needs_paint() { + return Some(ReflowTriggerCondition::PaintPostponed); + } + + None } /// Returns the first `base` element in the DOM that has an `href` attribute. - pub fn base_element(&self) -> Option<Root<HTMLBaseElement>> { + pub fn base_element(&self) -> Option<DomRoot<HTMLBaseElement>> { self.base_element.get() } /// Refresh the cached first base element in the DOM. - /// https://github.com/w3c/web-platform-tests/issues/2122 + /// <https://github.com/w3c/web-platform-tests/issues/2122> pub fn refresh_base_element(&self) { - let base = self.upcast::<Node>() - .traverse_preorder() - .filter_map(Root::downcast::<HTMLBaseElement>) - .find(|element| element.upcast::<Element>().has_attribute(&local_name!("href"))); - self.base_element.set(base.r()); + let base = self + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<HTMLBaseElement>) + .find(|element| { + element + .upcast::<Element>() + .has_attribute(&local_name!("href")) + }); + self.base_element.set(base.as_deref()); } pub fn dom_count(&self) -> u32 { @@ -541,20 +811,29 @@ impl Document { self.quirks_mode.set(mode); if mode == QuirksMode::Quirks { - self.window.layout_chan().send(Msg::SetQuirksMode).unwrap(); + match self.window.layout_chan() { + Some(chan) => chan.send(Msg::SetQuirksMode(mode)).unwrap(), + None => warn!("Layout channel unavailable"), + } } } - pub fn encoding(&self) -> EncodingRef { + pub fn encoding(&self) -> &'static Encoding { self.encoding.get() } - pub fn set_encoding(&self, encoding: EncodingRef) { + pub fn set_encoding(&self, encoding: &'static Encoding) { self.encoding.set(encoding); } - pub fn content_and_heritage_changed(&self, node: &Node, damage: NodeDamage) { - node.dirty(damage); + pub fn content_and_heritage_changed(&self, node: &Node) { + if node.is_connected() { + node.note_dirty_descendants(); + } + + // FIXME(emilio): This is very inefficient, ideally the flag above would + // be enough and incremental layout could figure out from there. + node.dirty(NodeDamage::OtherNodeDamage); } /// Reflows and disarms the timer if the reflow timer has expired. @@ -565,9 +844,8 @@ impl Document { } self.reflow_timeout.set(None); - self.window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::RefreshTick); + self.window + .reflow(ReflowGoal::Full, ReflowReason::RefreshTick); } } @@ -584,68 +862,64 @@ impl Document { } /// Remove any existing association between the provided id and any elements in this document. - pub fn unregister_named_element(&self, to_unregister: &Element, id: Atom) { - debug!("Removing named element from document {:p}: {:p} id={}", - self, - to_unregister, - id); - // Limit the scope of the borrow because id_map might be borrowed again by - // GetElementById through the following sequence of calls - // reset_form_owner_for_listeners -> reset_form_owner -> GetElementById - { - let mut id_map = self.id_map.borrow_mut(); - let is_empty = match id_map.get_mut(&id) { - None => false, - Some(elements) => { - let position = elements.iter() - .position(|element| &**element == to_unregister) - .expect("This element should be in registered."); - elements.remove(position); - elements.is_empty() - } - }; - if is_empty { - id_map.remove(&id); - } - } + pub fn unregister_element_id(&self, to_unregister: &Element, id: Atom) { + self.document_or_shadow_root + .unregister_named_element(&self.id_map, to_unregister, &id); self.reset_form_owner_for_listeners(&id); } /// Associate an element present in this document with the provided id. - pub fn register_named_element(&self, element: &Element, id: Atom) { - debug!("Adding named element to document {:p}: {:p} id={}", - self, - element, - id); - assert!(element.upcast::<Node>().is_in_doc()); - assert!(!id.is_empty()); - - let root = self.GetDocumentElement() - .expect("The element is in the document, so there must be a document \ - element."); - - // Limit the scope of the borrow because id_map might be borrowed again by - // GetElementById through the following sequence of calls - // reset_form_owner_for_listeners -> reset_form_owner -> GetElementById - { - let mut id_map = self.id_map.borrow_mut(); - let mut elements = id_map.entry(id.clone()).or_insert(Vec::new()); - elements.insert_pre_order(element, root.r().upcast::<Node>()); - } + pub fn register_element_id(&self, element: &Element, id: Atom) { + let root = self.GetDocumentElement().expect( + "The element is in the document, so there must be a document \ + element.", + ); + self.document_or_shadow_root.register_named_element( + &self.id_map, + element, + &id, + DomRoot::from_ref(root.upcast::<Node>()), + ); self.reset_form_owner_for_listeners(&id); } + /// Remove any existing association between the provided name and any elements in this document. + pub fn unregister_element_name(&self, to_unregister: &Element, name: Atom) { + self.document_or_shadow_root + .unregister_named_element(&self.name_map, to_unregister, &name); + } + + /// Associate an element present in this document with the provided name. + pub fn register_element_name(&self, element: &Element, name: Atom) { + let root = self.GetDocumentElement().expect( + "The element is in the document, so there must be a document \ + element.", + ); + self.document_or_shadow_root.register_named_element( + &self.name_map, + element, + &name, + DomRoot::from_ref(root.upcast::<Node>()), + ); + } + pub fn register_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) { let mut map = self.form_id_listener_map.borrow_mut(); let listener = listener.to_element(); - let mut set = map.entry(Atom::from(id)).or_insert(HashSet::new()); - set.insert(JS::from_ref(listener)); + let set = map.entry(Atom::from(id)).or_insert(HashSet::new()); + set.insert(Dom::from_ref(listener)); } - pub fn unregister_form_id_listener<T: ?Sized + FormControl>(&self, id: DOMString, listener: &T) { + pub fn unregister_form_id_listener<T: ?Sized + FormControl>( + &self, + id: DOMString, + listener: &T, + ) { let mut map = self.form_id_listener_map.borrow_mut(); if let Occupied(mut entry) = map.entry(Atom::from(id)) { - entry.get_mut().remove(&JS::from_ref(listener.to_element())); + entry + .get_mut() + .remove(&Dom::from_ref(listener.to_element())); if entry.get().is_empty() { entry.remove(); } @@ -653,86 +927,97 @@ impl Document { } /// Attempt to find a named element in this page's document. - /// https://html.spec.whatwg.org/multipage/#the-indicated-part-of-the-document - pub fn find_fragment_node(&self, fragid: &str) -> Option<Root<Element>> { + /// <https://html.spec.whatwg.org/multipage/#the-indicated-part-of-the-document> + pub fn find_fragment_node(&self, fragid: &str) -> Option<DomRoot<Element>> { // Step 1 is not handled here; the fragid is already obtained by the calling function // Step 2: Simply use None to indicate the top of the document. // Step 3 & 4 - percent_decode(fragid.as_bytes()).decode_utf8().ok() - // Step 5 + percent_decode(fragid.as_bytes()) + .decode_utf8() + .ok() + // Step 5 .and_then(|decoded_fragid| self.get_element_by_id(&Atom::from(decoded_fragid))) - // Step 6 + // Step 6 .or_else(|| self.get_anchor_by_name(fragid)) // Step 7 & 8 } /// Scroll to the target element, and when we do not find a target /// and the fragment is empty or "top", scroll to the top. - /// https://html.spec.whatwg.org/multipage/#scroll-to-the-fragment-identifier + /// <https://html.spec.whatwg.org/multipage/#scroll-to-the-fragment-identifier> pub fn check_and_scroll_fragment(&self, fragment: &str) { let target = self.find_fragment_node(fragment); // Step 1 - self.set_target_element(target.r()); - - let point = target.r().map(|element| { - // FIXME(#8275, pcwalton): This is pretty bogus when multiple layers are involved. - // Really what needs to happen is that this needs to go through layout to ask which - // layer the element belongs to, and have it send the scroll message to the - // compositor. - let rect = element.upcast::<Node>().bounding_content_box_or_zero(); - - // In order to align with element edges, we snap to unscaled pixel boundaries, since - // the paint thread currently does the same for drawing elements. This is important - // for pages that require pixel perfect scroll positioning for proper display - // (like Acid2). Since we don't have the device pixel ratio here, this might not be - // accurate, but should work as long as the ratio is a whole number. Once #8275 is - // fixed this should actually take into account the real device pixel ratio. - (rect.origin.x.to_nearest_px() as f32, rect.origin.y.to_nearest_px() as f32) - }).or_else(|| if fragment.is_empty() || fragment.eq_ignore_ascii_case("top") { - // FIXME(stshine): this should be the origin of the stacking context space, - // which may differ under the influence of writing mode. - Some((0.0, 0.0)) - } else { - None - }); + self.set_target_element(target.as_deref()); + + let point = target + .as_ref() + .map(|element| { + // FIXME(#8275, pcwalton): This is pretty bogus when multiple layers are involved. + // Really what needs to happen is that this needs to go through layout to ask which + // layer the element belongs to, and have it send the scroll message to the + // compositor. + let rect = element.upcast::<Node>().bounding_content_box_or_zero(); + + // In order to align with element edges, we snap to unscaled pixel boundaries, since + // the paint thread currently does the same for drawing elements. This is important + // for pages that require pixel perfect scroll positioning for proper display + // (like Acid2). Since we don't have the device pixel ratio here, this might not be + // accurate, but should work as long as the ratio is a whole number. Once #8275 is + // fixed this should actually take into account the real device pixel ratio. + ( + rect.origin.x.to_nearest_px() as f32, + rect.origin.y.to_nearest_px() as f32, + ) + }) + .or_else(|| { + if fragment.is_empty() || fragment.eq_ignore_ascii_case("top") { + // FIXME(stshine): this should be the origin of the stacking context space, + // which may differ under the influence of writing mode. + Some((0.0, 0.0)) + } else { + None + } + }); if let Some((x, y)) = point { // Step 3 let global_scope = self.window.upcast::<GlobalScope>(); - let webrender_pipeline_id = global_scope.pipeline_id().to_webrender(); - self.window.perform_a_scroll(x, - y, - ClipId::root_scroll_node(webrender_pipeline_id), - ScrollBehavior::Instant, - target.r()); + self.window.update_viewport_for_scroll(x, y); + self.window.perform_a_scroll( + x, + y, + global_scope.pipeline_id().root_scroll_id(), + ScrollBehavior::Instant, + target.as_deref(), + ); } } - fn get_anchor_by_name(&self, name: &str) -> Option<Root<Element>> { - let check_anchor = |node: &HTMLAnchorElement| { - let elem = node.upcast::<Element>(); - elem.get_attribute(&ns!(), &local_name!("name")) - .map_or(false, |attr| &**attr.value() == name) - }; - let doc_node = self.upcast::<Node>(); - doc_node.traverse_preorder() - .filter_map(Root::downcast) - .find(|node| check_anchor(&node)) - .map(Root::upcast) + fn get_anchor_by_name(&self, name: &str) -> Option<DomRoot<Element>> { + let name = Atom::from(name); + self.name_map.borrow().get(&name).and_then(|elements| { + elements + .iter() + .find(|e| e.is::<HTMLAnchorElement>()) + .map(|e| DomRoot::from_ref(&**e)) + }) } // https://html.spec.whatwg.org/multipage/#current-document-readiness pub fn set_ready_state(&self, state: DocumentReadyState) { match state { DocumentReadyState::Loading => { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserconnected - self.trigger_mozbrowser_event(MozBrowserEvent::Connected); + if self.window().is_top_level() { + self.send_to_embedder(EmbedderMsg::LoadStart); + } update_with_current_time_ms(&self.dom_loading); }, DocumentReadyState::Complete => { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadend - self.trigger_mozbrowser_event(MozBrowserEvent::LoadEnd); + if self.window().is_top_level() { + self.send_to_embedder(EmbedderMsg::LoadComplete); + } update_with_current_time_ms(&self.dom_complete); }, DocumentReadyState::Interactive => update_with_current_time_ms(&self.dom_interactive), @@ -740,7 +1025,8 @@ impl Document { self.ready_state.set(state); - self.upcast::<EventTarget>().fire_event(atom!("readystatechange")); + self.upcast::<EventTarget>() + .fire_event(atom!("readystatechange")); } /// Return whether scripting is enabled or not @@ -750,37 +1036,73 @@ impl Document { /// Return the element that currently has focus. // https://w3c.github.io/uievents/#events-focusevent-doc-focus - pub fn get_focused_element(&self) -> Option<Root<Element>> { + pub fn get_focused_element(&self) -> Option<DomRoot<Element>> { self.focused.get() } /// Initiate a new round of checking for elements requesting focus. The last element to call /// `request_focus` before `commit_focus_transaction` is called will receive focus. - pub fn begin_focus_transaction(&self) { - self.possibly_focused.set(None); + fn begin_focus_transaction(&self) { + *self.focus_transaction.borrow_mut() = FocusTransaction::InTransaction(Default::default()); + } + + /// <https://html.spec.whatwg.org/multipage/#focus-fixup-rule> + pub(crate) fn perform_focus_fixup_rule(&self, not_focusable: &Element) { + if Some(not_focusable) != self.focused.get().as_ref().map(|e| &**e) { + return; + } + self.request_focus( + self.GetBody().as_ref().map(|e| &*e.upcast()), + FocusType::Element, + ) } /// Request that the given element receive focus once the current transaction is complete. - pub fn request_focus(&self, elem: &Element) { - if elem.is_focusable_area() { - self.possibly_focused.set(Some(elem)) + /// If None is passed, then whatever element is currently focused will no longer be focused + /// once the transaction is complete. + pub(crate) fn request_focus(&self, elem: Option<&Element>, focus_type: FocusType) { + let implicit_transaction = matches!( + *self.focus_transaction.borrow(), + FocusTransaction::NotInTransaction + ); + if implicit_transaction { + self.begin_focus_transaction(); + } + if elem.map_or(true, |e| e.is_focusable_area()) { + *self.focus_transaction.borrow_mut() = + FocusTransaction::InTransaction(elem.map(Dom::from_ref)); + } + if implicit_transaction { + self.commit_focus_transaction(focus_type); } } /// Reassign the focus context to the element that last requested focus during this /// transaction, or none if no elements requested it. - pub fn commit_focus_transaction(&self, focus_type: FocusType) { - if self.focused == self.possibly_focused.get().r() { - return + fn commit_focus_transaction(&self, focus_type: FocusType) { + let possibly_focused = match *self.focus_transaction.borrow() { + FocusTransaction::NotInTransaction => unreachable!(), + FocusTransaction::InTransaction(ref elem) => { + elem.as_ref().map(|e| DomRoot::from_ref(&**e)) + }, + }; + *self.focus_transaction.borrow_mut() = FocusTransaction::NotInTransaction; + if self.focused == possibly_focused.as_ref().map(|e| &**e) { + return; } if let Some(ref elem) = self.focused.get() { let node = elem.upcast::<Node>(); elem.set_focus_state(false); // FIXME: pass appropriate relatedTarget self.fire_focus_event(FocusEventType::Blur, node, None); + + // Notify the embedder to hide the input method. + if elem.input_method_type().is_some() { + self.send_to_embedder(EmbedderMsg::HideIME); + } } - self.focused.set(self.possibly_focused.get().r()); + self.focused.set(possibly_focused.as_ref().map(|e| &**e)); if let Some(ref elem) = self.focused.get() { elem.set_focus_state(true); @@ -790,9 +1112,41 @@ impl Document { // Update the focus state for all elements in the focus chain. // https://html.spec.whatwg.org/multipage/#focus-chain if focus_type == FocusType::Element { - let global_scope = self.window.upcast::<GlobalScope>(); - let event = ConstellationMsg::Focus(global_scope.pipeline_id()); - global_scope.constellation_chan().send(event).unwrap(); + self.window().send_to_constellation(ScriptMsg::Focus); + } + + // Notify the embedder to display an input method. + if let Some(kind) = elem.input_method_type() { + let rect = elem.upcast::<Node>().bounding_content_box_or_zero(); + 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 (text, multiline) = if let Some(input) = elem.downcast::<HTMLInputElement>() { + ( + Some(( + (&input.Value()).to_string(), + input.GetSelectionEnd().unwrap_or(0) as i32, + )), + false, + ) + } else if let Some(textarea) = elem.downcast::<HTMLTextAreaElement>() { + ( + Some(( + (&textarea.Value()).to_string(), + textarea.GetSelectionEnd().unwrap_or(0) as i32, + )), + true, + ) + } else { + (None, false) + }; + self.send_to_embedder(EmbedderMsg::ShowIME( + kind, + text, + multiline, + DeviceIntRect::from_untyped(&rect), + )); } } } @@ -800,36 +1154,59 @@ impl Document { /// Handles any updates when the document's title has changed. pub fn title_changed(&self) { if self.browsing_context().is_some() { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsertitlechange - self.trigger_mozbrowser_event(MozBrowserEvent::TitleChange(String::from(self.Title()))); + self.send_title_to_embedder(); + let title = String::from(self.Title()); + self.window.send_to_constellation(ScriptMsg::TitleChanged( + self.window.pipeline_id(), + title.clone(), + )); + let global = self.window.upcast::<GlobalScope>(); + if let Some(ref chan) = global.devtools_chan() { + let _ = chan.send(ScriptToDevtoolsControlMsg::TitleChanged( + global.pipeline_id(), + title, + )); + } + } + } - self.send_title_to_compositor(); + /// Sends this document's title to the constellation. + pub fn send_title_to_embedder(&self) { + let window = self.window(); + if window.is_top_level() { + let title = Some(String::from(self.Title())); + self.send_to_embedder(EmbedderMsg::ChangePageTitle(title)); } } - /// Sends this document's title to the compositor. - pub fn send_title_to_compositor(&self) { + fn send_to_embedder(&self, msg: EmbedderMsg) { let window = self.window(); - let global_scope = window.upcast::<GlobalScope>(); - global_scope - .constellation_chan() - .send(ConstellationMsg::SetTitle(global_scope.pipeline_id(), - Some(String::from(self.Title())))) - .unwrap(); + window.send_to_embedder(msg); } pub fn dirty_all_nodes(&self) { - let root = self.upcast::<Node>(); - for node in root.traverse_preorder() { + let root = match self.GetDocumentElement() { + Some(root) => root, + None => return, + }; + for node in root + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::Yes) + { node.dirty(NodeDamage::OtherNodeDamage) } } - pub fn handle_mouse_event(&self, - js_runtime: *mut JSRuntime, - button: MouseButton, - client_point: Point2D<f32>, - mouse_event_type: MouseEventType) { + #[allow(unsafe_code)] + pub unsafe fn handle_mouse_event( + &self, + button: MouseButton, + client_point: Point2D<f32>, + mouse_event_type: MouseEventType, + node_address: Option<UntrustedNodeAddress>, + point_in_node: Option<Point2D<f32>>, + pressed_mouse_buttons: u16, + ) { let mouse_event_type_string = match mouse_event_type { MouseEventType::Click => "click".to_owned(), MouseEventType::MouseUp => "mouseup".to_owned(), @@ -837,39 +1214,17 @@ impl Document { }; debug!("{}: at {:?}", mouse_event_type_string, client_point); - let node = match self.window.hit_test_query(client_point, false) { - Some(node_address) => { - debug!("node address is {:?}", node_address); - node::from_untrusted_node_address(js_runtime, node_address) - }, + let el = node_address.and_then(|address| { + let node = node::from_untrusted_node_address(address); + node.inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<Element>) + .next() + }); + let el = match el { + Some(el) => el, None => return, }; - let el = match node.downcast::<Element>() { - Some(el) => Root::from_ref(el), - None => { - let parent = node.GetParentNode(); - match parent.and_then(Root::downcast::<Element>) { - Some(parent) => parent, - None => return, - } - }, - }; - - // If the target is an iframe, forward the event to the child document. - if let Some(iframe) = el.downcast::<HTMLIFrameElement>() { - if let Some(pipeline_id) = iframe.pipeline_id() { - let rect = iframe.upcast::<Element>().GetBoundingClientRect(); - let child_origin = Point2D::new(rect.X() as f32, rect.Y() as f32); - let child_point = client_point - child_origin; - - let event = CompositorEvent::MouseButtonEvent(mouse_event_type, button, child_point); - let event = ConstellationMsg::ForwardEvent(pipeline_id, event); - self.window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap(); - } - return; - } - let node = el.upcast::<Node>(); debug!("{} on {:?}", mouse_event_type_string, node.debug_str()); // Prevent click event if form control element is disabled. @@ -879,28 +1234,37 @@ impl Document { } self.begin_focus_transaction(); + self.request_focus(Some(&*el), FocusType::Element); } // https://w3c.github.io/uievents/#event-type-click let client_x = client_point.x as i32; let client_y = client_point.y as i32; let click_count = 1; - let event = MouseEvent::new(&self.window, - DOMString::from(mouse_event_type_string), - EventBubbles::Bubbles, - EventCancelable::Cancelable, - Some(&self.window), - click_count, - client_x, - client_y, - client_x, - client_y, // TODO: Get real screen coordinates? - false, - false, - false, - false, - 0i16, - None); + let event = MouseEvent::new( + &self.window, + DOMString::from(mouse_event_type_string), + EventBubbles::Bubbles, + EventCancelable::Cancelable, + Some(&self.window), + click_count, + client_x, + client_y, + client_x, + client_y, // TODO: Get real screen coordinates? + false, + false, + false, + false, + match &button { + MouseButton::Left => 0i16, + MouseButton::Middle => 1i16, + MouseButton::Right => 2i16, + }, + pressed_mouse_buttons, + None, + point_in_node, + ); let event = event.upcast::<Event>(); // https://w3c.github.io/uievents/#trusted-events @@ -908,7 +1272,11 @@ impl Document { // https://html.spec.whatwg.org/multipage/#run-authentic-click-activation-steps let activatable = el.as_maybe_activatable(); match mouse_event_type { - MouseEventType::Click => el.authentic_click_activation(event), + MouseEventType::Click => { + el.set_click_in_progress(true); + event.fire(node.upcast()); + el.set_click_in_progress(false); + }, MouseEventType::MouseDown => { if let Some(a) = activatable { a.enter_formal_activation_state(); @@ -929,52 +1297,61 @@ impl Document { if let MouseEventType::Click = mouse_event_type { self.commit_focus_transaction(FocusType::Element); - self.maybe_fire_dblclick(client_point, node); + self.maybe_fire_dblclick(client_point, node, pressed_mouse_buttons); } - self.window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::MouseEvent); + self.window + .reflow(ReflowGoal::Full, ReflowReason::MouseEvent); } - fn maybe_fire_dblclick(&self, click_pos: Point2D<f32>, target: &Node) { + fn maybe_fire_dblclick( + &self, + click_pos: Point2D<f32>, + target: &Node, + pressed_mouse_buttons: u16, + ) { // https://w3c.github.io/uievents/#event-type-dblclick let now = Instant::now(); let opt = self.last_click_info.borrow_mut().take(); if let Some((last_time, last_pos)) = opt { - let DBL_CLICK_TIMEOUT = Duration::from_millis(PREFS.get("dom.document.dblclick_timeout").as_u64() - .unwrap_or(300)); - let DBL_CLICK_DIST_THRESHOLD = PREFS.get("dom.document.dblclick_dist").as_u64().unwrap_or(1); + let DBL_CLICK_TIMEOUT = + Duration::from_millis(pref!(dom.document.dblclick_timeout) as u64); + let DBL_CLICK_DIST_THRESHOLD = pref!(dom.document.dblclick_dist) as u64; // Calculate distance between this click and the previous click. let line = click_pos - last_pos; let dist = (line.dot(line) as f64).sqrt(); - if now.duration_since(last_time) < DBL_CLICK_TIMEOUT && - dist < DBL_CLICK_DIST_THRESHOLD as f64 { + if now.duration_since(last_time) < DBL_CLICK_TIMEOUT && + dist < DBL_CLICK_DIST_THRESHOLD as f64 + { // A double click has occurred if this click is within a certain time and dist. of previous click. let click_count = 2; let client_x = click_pos.x as i32; let client_y = click_pos.y as i32; - let event = MouseEvent::new(&self.window, - DOMString::from("dblclick"), - EventBubbles::Bubbles, - EventCancelable::Cancelable, - Some(&self.window), - click_count, - client_x, - client_y, - client_x, - client_y, - false, - false, - false, - false, - 0i16, - None); + let event = MouseEvent::new( + &self.window, + DOMString::from("dblclick"), + EventBubbles::Bubbles, + EventCancelable::Cancelable, + Some(&self.window), + click_count, + client_x, + client_y, + client_x, + client_y, + false, + false, + false, + false, + 0i16, + pressed_mouse_buttons, + None, + None, + ); event.upcast::<Event>().fire(target.upcast()); // When a double click occurs, self.last_click_info is left as None so that a @@ -987,210 +1364,268 @@ impl Document { *self.last_click_info.borrow_mut() = Some((now, click_pos)); } - pub fn handle_touchpad_pressure_event(&self, - js_runtime: *mut JSRuntime, - client_point: Point2D<f32>, - pressure: f32, - phase_now: TouchpadPressurePhase) { - let node = match self.window.hit_test_query(client_point, false) { - Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address), - None => return - }; - - let el = match node.downcast::<Element>() { - Some(el) => Root::from_ref(el), - None => { - let parent = node.GetParentNode(); - match parent.and_then(Root::downcast::<Element>) { - Some(parent) => parent, - None => return - } - }, - }; - - // If the target is an iframe, forward the event to the child document. - if let Some(iframe) = el.downcast::<HTMLIFrameElement>() { - if let Some(pipeline_id) = iframe.pipeline_id() { - let rect = iframe.upcast::<Element>().GetBoundingClientRect(); - let child_origin = Point2D::new(rect.X() as f32, rect.Y() as f32); - let child_point = client_point - child_origin; - - let event = CompositorEvent::TouchpadPressureEvent(child_point, - pressure, - phase_now); - let event = ConstellationMsg::ForwardEvent(pipeline_id, event); - self.window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap(); - } - return; - } - - let phase_before = self.touchpad_pressure_phase.get(); - self.touchpad_pressure_phase.set(phase_now); - - if phase_before == TouchpadPressurePhase::BeforeClick && - phase_now == TouchpadPressurePhase::BeforeClick { - return; - } - - let node = el.upcast::<Node>(); - let target = node.upcast(); - - let force = match phase_now { - TouchpadPressurePhase::BeforeClick => pressure, - TouchpadPressurePhase::AfterFirstClick => 1. + pressure, - TouchpadPressurePhase::AfterSecondClick => 2. + pressure, - }; - - if phase_now != TouchpadPressurePhase::BeforeClick { - self.fire_forcetouch_event("servomouseforcechanged".to_owned(), target, force); - } - - if phase_before != TouchpadPressurePhase::AfterSecondClick && - phase_now == TouchpadPressurePhase::AfterSecondClick { - self.fire_forcetouch_event("servomouseforcedown".to_owned(), target, force); - } - - if phase_before == TouchpadPressurePhase::AfterSecondClick && - phase_now != TouchpadPressurePhase::AfterSecondClick { - self.fire_forcetouch_event("servomouseforceup".to_owned(), target, force); - } - } - - fn fire_forcetouch_event(&self, event_name: String, target: &EventTarget, force: f32) { - let force_event = ForceTouchEvent::new(&self.window, - DOMString::from(event_name), - force); - let event = force_event.upcast::<Event>(); - event.fire(target); - } - - pub fn fire_mouse_event(&self, client_point: Point2D<f32>, target: &EventTarget, event_name: String) { + pub fn fire_mouse_event( + &self, + client_point: Point2D<f32>, + target: &EventTarget, + event_name: FireMouseEventType, + can_bubble: EventBubbles, + cancelable: EventCancelable, + pressed_mouse_buttons: u16, + ) { let client_x = client_point.x.to_i32().unwrap_or(0); let client_y = client_point.y.to_i32().unwrap_or(0); - let mouse_event = MouseEvent::new(&self.window, - DOMString::from(event_name), - EventBubbles::Bubbles, - EventCancelable::Cancelable, - Some(&self.window), - 0i32, - client_x, - client_y, - client_x, - client_y, - false, - false, - false, - false, - 0i16, - None); + let mouse_event = MouseEvent::new( + &self.window, + DOMString::from(event_name.as_str()), + can_bubble, + cancelable, + Some(&self.window), + 0i32, + client_x, + client_y, + client_x, + client_y, + false, + false, + false, + false, + 0i16, + pressed_mouse_buttons, + None, + None, + ); let event = mouse_event.upcast::<Event>(); event.fire(target); } - pub fn handle_mouse_move_event(&self, - js_runtime: *mut JSRuntime, - client_point: Option<Point2D<f32>>, - prev_mouse_over_target: &MutNullableJS<Element>) { - let client_point = match client_point { - None => { - // If there's no point, there's no target under the mouse - // FIXME: dispatch mouseout here. We have no point. - prev_mouse_over_target.set(None); - return; - } - Some(client_point) => client_point, - }; - - let maybe_new_target = self.window.hit_test_query(client_point, true).and_then(|address| { - let node = node::from_untrusted_node_address(js_runtime, address); - node.inclusive_ancestors() - .filter_map(Root::downcast::<Element>) + #[allow(unsafe_code)] + pub unsafe fn handle_mouse_move_event( + &self, + client_point: Point2D<f32>, + prev_mouse_over_target: &MutNullableDom<Element>, + node_address: Option<UntrustedNodeAddress>, + pressed_mouse_buttons: u16, + ) { + let maybe_new_target = node_address.and_then(|address| { + let node = node::from_untrusted_node_address(address); + node.inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<Element>) .next() }); - // Send mousemove event to topmost target, and forward it if it's an iframe - if let Some(ref new_target) = maybe_new_target { - // If the target is an iframe, forward the event to the child document. - if let Some(iframe) = new_target.downcast::<HTMLIFrameElement>() { - if let Some(pipeline_id) = iframe.pipeline_id() { - let rect = iframe.upcast::<Element>().GetBoundingClientRect(); - let child_origin = Point2D::new(rect.X() as f32, rect.Y() as f32); - let child_point = client_point - child_origin; - - let event = CompositorEvent::MouseMoveEvent(Some(child_point)); - let event = ConstellationMsg::ForwardEvent(pipeline_id, event); - self.window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap(); - } - return; - } - - self.fire_mouse_event(client_point, new_target.upcast(), "mousemove".to_owned()); - } - - // Nothing more to do here, mousemove is sent, - // and the element under the mouse hasn't changed. - if maybe_new_target == prev_mouse_over_target.get() { - return; - } - - let old_target_is_ancestor_of_new_target = match (prev_mouse_over_target.get(), maybe_new_target.as_ref()) { - (Some(old_target), Some(new_target)) - => old_target.upcast::<Node>().is_ancestor_of(new_target.upcast::<Node>()), - _ => false, + let new_target = match maybe_new_target { + Some(ref target) => target, + None => return, }; + let target_has_changed = prev_mouse_over_target + .get() + .as_ref() + .map_or(true, |old_target| old_target != new_target); + // Here we know the target has changed, so we must update the state, - // dispatch mouseout to the previous one, mouseover to the new one, - if let Some(old_target) = prev_mouse_over_target.get() { - // If the old target is an ancestor of the new target, this can be skipped - // completely, since the node's hover state will be reseted below. - if !old_target_is_ancestor_of_new_target { - for element in old_target.upcast::<Node>() - .inclusive_ancestors() - .filter_map(Root::downcast::<Element>) { - element.set_hover_state(false); - element.set_active_state(false); + // dispatch mouseout to the previous one, mouseover to the new one. + if target_has_changed { + // Dispatch mouseout and mouseleave to previous target. + if let Some(old_target) = prev_mouse_over_target.get() { + let old_target_is_ancestor_of_new_target = old_target + .upcast::<Node>() + .is_ancestor_of(new_target.upcast::<Node>()); + + // If the old target is an ancestor of the new target, this can be skipped + // completely, since the node's hover state will be reset below. + if !old_target_is_ancestor_of_new_target { + for element in old_target + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<Element>) + { + element.set_hover_state(false); + element.set_active_state(false); + } } - } - // Remove hover state to old target and its parents - self.fire_mouse_event(client_point, old_target.upcast(), "mouseout".to_owned()); + self.fire_mouse_event( + client_point, + old_target.upcast(), + FireMouseEventType::Out, + EventBubbles::Bubbles, + EventCancelable::Cancelable, + pressed_mouse_buttons, + ); - // TODO: Fire mouseleave here only if the old target is - // not an ancestor of the new target. - } + if !old_target_is_ancestor_of_new_target { + let event_target = DomRoot::from_ref(old_target.upcast::<Node>()); + let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>())); + self.handle_mouse_enter_leave_event( + client_point, + FireMouseEventType::Leave, + moving_into, + event_target, + pressed_mouse_buttons, + ); + } + } - if let Some(ref new_target) = maybe_new_target { - for element in new_target.upcast::<Node>() - .inclusive_ancestors() - .filter_map(Root::downcast::<Element>) { + // Dispatch mouseover and mouseenter to new target. + for element in new_target + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<Element>) + { if element.hover_state() { break; } - element.set_hover_state(true); } - self.fire_mouse_event(client_point, &new_target.upcast(), "mouseover".to_owned()); + self.fire_mouse_event( + client_point, + new_target.upcast(), + FireMouseEventType::Over, + EventBubbles::Bubbles, + EventCancelable::Cancelable, + pressed_mouse_buttons, + ); + + let moving_from = prev_mouse_over_target + .get() + .map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>())); + let event_target = DomRoot::from_ref(new_target.upcast::<Node>()); + self.handle_mouse_enter_leave_event( + client_point, + FireMouseEventType::Enter, + moving_from, + event_target, + pressed_mouse_buttons, + ); + } + + // Send mousemove event to topmost target, unless it's an iframe, in which case the + // compositor should have also sent an event to the inner document. + self.fire_mouse_event( + client_point, + new_target.upcast(), + FireMouseEventType::Move, + EventBubbles::Bubbles, + EventCancelable::Cancelable, + pressed_mouse_buttons, + ); + + // If the target has changed then store the current mouse over target for next frame. + if target_has_changed { + prev_mouse_over_target.set(maybe_new_target.as_deref()); + self.window + .reflow(ReflowGoal::Full, ReflowReason::MouseEvent); + } + } + + fn handle_mouse_enter_leave_event( + &self, + client_point: Point2D<f32>, + event_type: FireMouseEventType, + related_target: Option<DomRoot<Node>>, + event_target: DomRoot<Node>, + pressed_mouse_buttons: u16, + ) { + assert!(matches!( + event_type, + FireMouseEventType::Enter | FireMouseEventType::Leave + )); + + let common_ancestor = match related_target.as_ref() { + Some(related_target) => event_target + .common_ancestor(related_target, ShadowIncluding::No) + .unwrap_or_else(|| DomRoot::from_ref(&*event_target)), + None => DomRoot::from_ref(&*event_target), + }; - // TODO: Fire mouseenter here. + // We need to create a target chain in case the event target shares + // its boundaries with its ancestors. + let mut targets = vec![]; + let mut current = Some(event_target); + while let Some(node) = current { + if node == common_ancestor { + break; + } + current = node.GetParentNode(); + targets.push(node); + } + + // The order for dispatching mouseenter events starts from the topmost + // common ancestor of the event target and the related target. + if event_type == FireMouseEventType::Enter { + targets = targets.into_iter().rev().collect(); } - // Store the current mouse over target for next frame. - prev_mouse_over_target.set(maybe_new_target.r()); + for target in targets { + self.fire_mouse_event( + client_point, + target.upcast(), + event_type, + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + pressed_mouse_buttons, + ); + } + } + + #[allow(unsafe_code)] + pub unsafe fn handle_wheel_event( + &self, + delta: WheelDelta, + client_point: Point2D<f32>, + node_address: Option<UntrustedNodeAddress>, + ) { + let wheel_event_type_string = "wheel".to_owned(); + debug!("{}: at {:?}", wheel_event_type_string, client_point); + + let el = node_address.and_then(|address| { + let node = node::from_untrusted_node_address(address); + node.inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<Element>) + .next() + }); + + let el = match el { + Some(el) => el, + None => return, + }; + + let node = el.upcast::<Node>(); + debug!("{}: on {:?}", wheel_event_type_string, node.debug_str()); + + // https://w3c.github.io/uievents/#event-wheelevents + let event = WheelEvent::new( + &self.window, + DOMString::from(wheel_event_type_string), + EventBubbles::Bubbles, + EventCancelable::Cancelable, + Some(&self.window), + 0i32, + Finite::wrap(delta.x), + Finite::wrap(delta.y), + Finite::wrap(delta.z), + delta.mode as u32, + ); - self.window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::MouseEvent); + let event = event.upcast::<Event>(); + event.set_trusted(true); + + let target = node.upcast(); + event.fire(target); } - pub fn handle_touch_event(&self, - js_runtime: *mut JSRuntime, - event_type: TouchEventType, - touch_id: TouchId, - point: Point2D<f32>) - -> TouchEventResult { + #[allow(unsafe_code)] + pub unsafe fn handle_touch_event( + &self, + event_type: TouchEventType, + touch_id: TouchId, + point: Point2D<f32>, + node_address: Option<UntrustedNodeAddress>, + ) -> TouchEventResult { let TouchId(identifier) = touch_id; let event_name = match event_type { @@ -1200,36 +1635,18 @@ impl Document { TouchEventType::Cancel => "touchcancel", }; - let node = match self.window.hit_test_query(point, false) { - Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address), - None => return TouchEventResult::Processed(false), - }; - let el = match node.downcast::<Element>() { - Some(el) => Root::from_ref(el), - None => { - let parent = node.GetParentNode(); - match parent.and_then(Root::downcast::<Element>) { - Some(parent) => parent, - None => return TouchEventResult::Processed(false), - } - }, + let el = node_address.and_then(|address| { + let node = node::from_untrusted_node_address(address); + node.inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<Element>) + .next() + }); + let el = match el { + Some(el) => el, + None => return TouchEventResult::Forwarded, }; - // If the target is an iframe, forward the event to the child document. - if let Some(iframe) = el.downcast::<HTMLIFrameElement>() { - if let Some(pipeline_id) = iframe.pipeline_id() { - let rect = iframe.upcast::<Element>().GetBoundingClientRect(); - let child_origin = Point2D::new(rect.X() as f32, rect.Y() as f32); - let child_point = point - child_origin; - - let event = CompositorEvent::TouchEvent(event_type, touch_id, child_point); - let event = ConstellationMsg::ForwardEvent(pipeline_id, event); - self.window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap(); - } - return TouchEventResult::Forwarded; - } - - let target = Root::upcast::<EventTarget>(el); + let target = DomRoot::upcast::<EventTarget>(el); let window = &*self.window; let client_x = Finite::wrap(point.x as f64); @@ -1237,72 +1654,72 @@ impl Document { let page_x = Finite::wrap(point.x as f64 + window.PageXOffset() as f64); let page_y = Finite::wrap(point.y as f64 + window.PageYOffset() as f64); - let touch = Touch::new(window, - identifier, - &target, - client_x, - client_y, // TODO: Get real screen coordinates? - client_x, - client_y, - page_x, - page_y); + let touch = Touch::new( + window, identifier, &target, client_x, + client_y, // TODO: Get real screen coordinates? + client_x, client_y, page_x, page_y, + ); match event_type { TouchEventType::Down => { // Add a new touch point - self.active_touch_points.borrow_mut().push(JS::from_ref(&*touch)); - } + self.active_touch_points + .borrow_mut() + .push(Dom::from_ref(&*touch)); + }, TouchEventType::Move => { // Replace an existing touch point let mut active_touch_points = self.active_touch_points.borrow_mut(); - match active_touch_points.iter_mut().find(|t| t.Identifier() == identifier) { - Some(t) => *t = JS::from_ref(&*touch), + match active_touch_points + .iter_mut() + .find(|t| t.Identifier() == identifier) + { + Some(t) => *t = Dom::from_ref(&*touch), None => warn!("Got a touchmove event for a non-active touch point"), } - } - TouchEventType::Up | - TouchEventType::Cancel => { + }, + TouchEventType::Up | TouchEventType::Cancel => { // Remove an existing touch point let mut active_touch_points = self.active_touch_points.borrow_mut(); - match active_touch_points.iter().position(|t| t.Identifier() == identifier) { + match active_touch_points + .iter() + .position(|t| t.Identifier() == identifier) + { Some(i) => { active_touch_points.swap_remove(i); - } + }, None => warn!("Got a touchend event for a non-active touch point"), } - } + }, } - rooted_vec!(let mut touches); - touches.extend(self.active_touch_points.borrow().iter().cloned()); rooted_vec!(let mut target_touches); - target_touches.extend(self.active_touch_points - .borrow() - .iter() - .filter(|t| t.Target() == target) - .cloned()); - rooted_vec!(let changed_touches <- once(touch)); - - let event = TouchEvent::new(window, - DOMString::from(event_name), - EventBubbles::Bubbles, - EventCancelable::Cancelable, - Some(window), - 0i32, - &TouchList::new(window, touches.r()), - &TouchList::new(window, changed_touches.r()), - &TouchList::new(window, target_touches.r()), - // FIXME: modifier keys - false, - false, - false, - false); + let touches = { + let touches = self.active_touch_points.borrow(); + target_touches.extend(touches.iter().filter(|t| t.Target() == target).cloned()); + TouchList::new(window, touches.r()) + }; + + let event = TouchEvent::new( + window, + DOMString::from(event_name), + EventBubbles::Bubbles, + EventCancelable::Cancelable, + Some(window), + 0i32, + &touches, + &TouchList::new(window, from_ref(&&*touch)), + &TouchList::new(window, target_touches.r()), + // FIXME: modifier keys + false, + false, + false, + false, + ); let event = event.upcast::<Event>(); let result = event.fire(&target); - window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::MouseEvent); + window.reflow(ReflowGoal::Full, ReflowReason::MouseEvent); match result { EventStatus::Canceled => TouchEventResult::Processed(false), @@ -1311,12 +1728,7 @@ impl Document { } /// The entry point for all key processing for web content - pub fn dispatch_key_event(&self, - ch: Option<char>, - key: Key, - state: KeyState, - modifiers: KeyModifiers, - constellation: &IpcSender<ConstellationMsg>) { + pub fn dispatch_key_event(&self, keyboard_event: ::keyboard_types::KeyboardEvent) { let focused = self.get_focused_element(); let body = self.GetBody(); @@ -1326,130 +1738,137 @@ impl Document { (&None, &None) => self.window.upcast(), }; - let ctrl = modifiers.contains(CONTROL); - let alt = modifiers.contains(ALT); - let shift = modifiers.contains(SHIFT); - let meta = modifiers.contains(SUPER); - - let is_composing = false; - let is_repeating = state == KeyState::Repeated; - let ev_type = DOMString::from(match state { - KeyState::Pressed | KeyState::Repeated => "keydown", - KeyState::Released => "keyup", - } - .to_owned()); - - let props = KeyboardEvent::key_properties(ch, key, modifiers); - - let keyevent = KeyboardEvent::new(&self.window, - ev_type, - true, - true, - Some(&self.window), - 0, - ch, - Some(key), - DOMString::from(props.key_string.clone()), - DOMString::from(props.code), - props.location, - is_repeating, - is_composing, - ctrl, - alt, - shift, - meta, - None, - props.key_code); + let keyevent = KeyboardEvent::new( + &self.window, + DOMString::from(keyboard_event.state.to_string()), + true, + true, + Some(&self.window), + 0, + keyboard_event.key.clone(), + DOMString::from(keyboard_event.code.to_string()), + keyboard_event.location as u32, + keyboard_event.repeat, + keyboard_event.is_composing, + keyboard_event.modifiers, + 0, + keyboard_event.key.legacy_keycode(), + ); let event = keyevent.upcast::<Event>(); event.fire(target); let mut cancel_state = event.get_cancel_state(); // https://w3c.github.io/uievents/#keys-cancelable-keys - if state != KeyState::Released && props.is_printable() && cancel_state != EventDefault::Prevented { + if keyboard_event.state == KeyState::Down && + is_character_value_key(&(keyboard_event.key)) && + !keyboard_event.is_composing && + cancel_state != EventDefault::Prevented + { // https://w3c.github.io/uievents/#keypress-event-order - let event = KeyboardEvent::new(&self.window, - DOMString::from("keypress"), - true, - true, - Some(&self.window), - 0, - ch, - Some(key), - DOMString::from(props.key_string), - DOMString::from(props.code), - props.location, - is_repeating, - is_composing, - ctrl, - alt, - shift, - meta, - props.char_code, - 0); + let event = KeyboardEvent::new( + &self.window, + DOMString::from("keypress"), + true, + true, + Some(&self.window), + 0, + keyboard_event.key.clone(), + DOMString::from(keyboard_event.code.to_string()), + keyboard_event.location as u32, + keyboard_event.repeat, + keyboard_event.is_composing, + keyboard_event.modifiers, + keyboard_event.key.legacy_charcode(), + 0, + ); let ev = event.upcast::<Event>(); ev.fire(target); cancel_state = ev.get_cancel_state(); } if cancel_state == EventDefault::Allowed { - constellation.send(ConstellationMsg::SendKeyEvent(ch, key, state, modifiers)).unwrap(); + let msg = EmbedderMsg::Keyboard(keyboard_event.clone()); + self.send_to_embedder(msg); // This behavior is unspecced // We are supposed to dispatch synthetic click activation for Space and/or Return, // however *when* we do it is up to us. // Here, we're dispatching it after the key event so the script has a chance to cancel it // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27337 - match key { - Key::Space if state == KeyState::Released => { - let maybe_elem = target.downcast::<Element>(); - if let Some(el) = maybe_elem { - synthetic_click_activation(el, - false, - false, - false, - false, - ActivationSource::NotFromClick) - } + if (keyboard_event.key == Key::Enter || keyboard_event.code == Code::Space) && + keyboard_event.state == KeyState::Up + { + if let Some(elem) = target.downcast::<Element>() { + elem.upcast::<Node>() + .fire_synthetic_mouse_event_not_trusted(DOMString::from("click")); } - Key::Enter if state == KeyState::Released => { - let maybe_elem = target.downcast::<Element>(); - if let Some(el) = maybe_elem { - if let Some(a) = el.as_maybe_activatable() { - a.implicit_submission(ctrl, alt, shift, meta); - } - } - } - _ => (), } } - self.window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::KeyEvent); + self.window.reflow(ReflowGoal::Full, ReflowReason::KeyEvent); + } + + pub fn ime_dismissed(&self) { + self.request_focus( + self.GetBody().as_ref().map(|e| &*e.upcast()), + FocusType::Element, + ) + } + + pub fn dispatch_composition_event( + &self, + composition_event: ::keyboard_types::CompositionEvent, + ) { + // spec: https://w3c.github.io/uievents/#compositionstart + // spec: https://w3c.github.io/uievents/#compositionupdate + // spec: https://w3c.github.io/uievents/#compositionend + // > Event.target : focused element processing the composition + let focused = self.get_focused_element(); + let target = if let Some(elem) = &focused { + elem.upcast() + } else { + // Event is only dispatched if there is a focused element. + return; + }; + + let cancelable = composition_event.state == keyboard_types::CompositionState::Start; + + let compositionevent = CompositionEvent::new( + &self.window, + DOMString::from(composition_event.state.to_string()), + true, + cancelable, + Some(&self.window), + 0, + DOMString::from(composition_event.data), + ); + let event = compositionevent.upcast::<Event>(); + event.fire(target); } // https://dom.spec.whatwg.org/#converting-nodes-into-a-node - pub fn node_from_nodes_and_strings(&self, - mut nodes: Vec<NodeOrString>) - -> Fallible<Root<Node>> { + pub fn node_from_nodes_and_strings( + &self, + mut nodes: Vec<NodeOrString>, + ) -> Fallible<DomRoot<Node>> { if nodes.len() == 1 { Ok(match nodes.pop().unwrap() { NodeOrString::Node(node) => node, - NodeOrString::String(string) => Root::upcast(self.CreateTextNode(string)), + NodeOrString::String(string) => DomRoot::upcast(self.CreateTextNode(string)), }) } else { - let fragment = Root::upcast::<Node>(self.CreateDocumentFragment()); + let fragment = DomRoot::upcast::<Node>(self.CreateDocumentFragment()); for node in nodes { match node { NodeOrString::Node(node) => { - try!(fragment.AppendChild(&node)); + fragment.AppendChild(&node)?; }, NodeOrString::String(string) => { - let node = Root::upcast::<Node>(self.CreateTextNode(string)); + let node = DomRoot::upcast::<Node>(self.CreateTextNode(string)); // No try!() here because appending a text node // should not fail. fragment.AppendChild(&node).unwrap(); - } + }, } } Ok(fragment) @@ -1457,16 +1876,20 @@ impl Document { } pub fn get_body_attribute(&self, local_name: &LocalName) -> DOMString { - match self.GetBody().and_then(Root::downcast::<HTMLBodyElement>) { - Some(ref body) => { - body.upcast::<Element>().get_string_attribute(local_name) - }, + match self + .GetBody() + .and_then(DomRoot::downcast::<HTMLBodyElement>) + { + Some(ref body) => body.upcast::<Element>().get_string_attribute(local_name), None => DOMString::new(), } } pub fn set_body_attribute(&self, local_name: &LocalName, value: DOMString) { - if let Some(ref body) = self.GetBody().and_then(Root::downcast::<HTMLBodyElement>) { + if let Some(ref body) = self + .GetBody() + .and_then(DomRoot::downcast::<HTMLBodyElement>) + { let body = body.upcast::<Element>(); let value = body.parse_attribute(&ns!(), &local_name, value); body.set_attribute(local_name, value); @@ -1493,84 +1916,70 @@ impl Document { } pub fn invalidate_stylesheets(&self) { - self.stylesheets_changed_since_reflow.set(true); - *self.stylesheets.borrow_mut() = None; + self.stylesheets.borrow_mut().force_dirty(OriginSet::all()); + // Mark the document element dirty so a reflow will be performed. + // + // FIXME(emilio): Use the DocumentStylesheetSet invalidation stuff. if let Some(element) = self.GetDocumentElement() { element.upcast::<Node>().dirty(NodeDamage::NodeStyleDamaged); } } - pub fn get_and_reset_stylesheets_changed_since_reflow(&self) -> bool { - let changed = self.stylesheets_changed_since_reflow.get(); - self.stylesheets_changed_since_reflow.set(false); - changed - } - - pub fn trigger_mozbrowser_event(&self, event: MozBrowserEvent) { - if PREFS.is_mozbrowser_enabled() { - if let Some((parent_pipeline_id, _)) = self.window.parent_info() { - let global_scope = self.window.upcast::<GlobalScope>(); - let event = ConstellationMsg::MozBrowserEvent(parent_pipeline_id, - global_scope.pipeline_id(), - event); - global_scope.constellation_chan().send(event).unwrap(); - } - } - } - - /// https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe + /// <https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe> pub fn request_animation_frame(&self, callback: AnimationFrameCallback) -> u32 { let ident = self.animation_frame_ident.get() + 1; self.animation_frame_ident.set(ident); - self.animation_frame_list.borrow_mut().push((ident, Some(callback))); - - // No need to send a `ChangeRunningAnimationsState` if we're running animation callbacks: - // we're guaranteed to already be in the "animation callbacks present" state. - // - // This reduces CPU usage by avoiding needless thread wakeups in the common case of - // repeated rAF. - // - // TODO: Should tick animation only when document is visible - if !self.running_animation_callbacks.get() { - if !self.is_faking_animation_frames() { - let global_scope = self.window.upcast::<GlobalScope>(); - let event = ConstellationMsg::ChangeRunningAnimationsState( - global_scope.pipeline_id(), - AnimationState::AnimationCallbacksPresent); - global_scope.constellation_chan().send(event).unwrap(); - } else { - let callback = FakeRequestAnimationFrameCallback { - document: Trusted::new(self), - }; - self.global() - .schedule_callback(OneshotTimerCallback::FakeRequestAnimationFrame(callback), - MsDuration::new(FAKE_REQUEST_ANIMATION_FRAME_DELAY)); - } + self.animation_frame_list + .borrow_mut() + .push((ident, Some(callback))); + + // If we are running 'fake' animation frames, we unconditionally + // set up a one-shot timer for script to execute the rAF callbacks. + if self.is_faking_animation_frames() && self.window().visible() { + warn!("Scheduling fake animation frame. Animation frames tick too fast."); + let callback = FakeRequestAnimationFrameCallback { + document: Trusted::new(self), + }; + self.global().schedule_callback( + OneshotTimerCallback::FakeRequestAnimationFrame(callback), + MsDuration::new(FAKE_REQUEST_ANIMATION_FRAME_DELAY), + ); + } else if !self.running_animation_callbacks.get() { + // No need to send a `ChangeRunningAnimationsState` if we're running animation callbacks: + // we're guaranteed to already be in the "animation callbacks present" state. + // + // This reduces CPU usage by avoiding needless thread wakeups in the common case of + // repeated rAF. + + let event = + ScriptMsg::ChangeRunningAnimationsState(AnimationState::AnimationCallbacksPresent); + self.window().send_to_constellation(event); } ident } - /// https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe + /// <https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe> pub fn cancel_animation_frame(&self, ident: u32) { let mut list = self.animation_frame_list.borrow_mut(); - if let Some(mut pair) = list.iter_mut().find(|pair| pair.0 == ident) { + if let Some(pair) = list.iter_mut().find(|pair| pair.0 == ident) { pair.1 = None; } } - /// https://html.spec.whatwg.org/multipage/#run-the-animation-frame-callbacks + /// <https://html.spec.whatwg.org/multipage/#run-the-animation-frame-callbacks> pub fn run_the_animation_frame_callbacks(&self) { rooted_vec!(let mut animation_frame_list); mem::swap( &mut *animation_frame_list, - &mut *self.animation_frame_list.borrow_mut()); + &mut *self.animation_frame_list.borrow_mut(), + ); self.running_animation_callbacks.set(true); let was_faking_animation_frames = self.is_faking_animation_frames(); - let timing = self.window.Performance().Now(); + let timing = self.global().performance().Now(); for (_, callback) in animation_frame_list.drain(..) { if let Some(callback) = callback { @@ -1580,9 +1989,24 @@ impl Document { self.running_animation_callbacks.set(false); - let spurious = !self.window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::RequestAnimationFrame); + let spurious = !self + .window + .reflow(ReflowGoal::Full, ReflowReason::RequestAnimationFrame); + + if spurious && !was_faking_animation_frames { + // If the rAF callbacks did not mutate the DOM, then the + // reflow call above means that layout will not be invoked, + // and therefore no new frame will be sent to the compositor. + // If this happens, the compositor will not tick the animation + // and the next rAF will never be called! When this happens + // for several frames, then the spurious rAF detection below + // will kick in and use a timer to tick the callbacks. However, + // for the interim frames where we are deciding whether this rAF + // is considered spurious, we need to ensure that the layout + // and compositor *do* tick the animation. + self.window + .force_reflow(ReflowGoal::Full, ReflowReason::RequestAnimationFrame, None); + } // Only send the animation change state message after running any callbacks. // This means that if the animation callback adds a new callback for @@ -1593,30 +2017,43 @@ impl Document { // constellation to stop giving us video refresh callbacks, to save energy. (A spurious // animation frame is one in which the callback did not mutate the DOM—that is, an // animation frame that wasn't actually used for animation.) - if self.animation_frame_list.borrow().is_empty() || - (!was_faking_animation_frames && self.is_faking_animation_frames()) { - mem::swap(&mut *self.animation_frame_list.borrow_mut(), - &mut *animation_frame_list); - let global_scope = self.window.upcast::<GlobalScope>(); - let event = ConstellationMsg::ChangeRunningAnimationsState( - global_scope.pipeline_id(), - AnimationState::NoAnimationCallbacksPresent); - global_scope.constellation_chan().send(event).unwrap(); + let is_empty = self.animation_frame_list.borrow().is_empty(); + if is_empty || (!was_faking_animation_frames && self.is_faking_animation_frames()) { + if is_empty { + // If the current animation frame list in the DOM instance is empty, + // we can reuse the original `Vec<T>` that we put on the stack to + // avoid allocating a new one next time an animation callback + // is queued. + mem::swap( + &mut *self.animation_frame_list.borrow_mut(), + &mut *animation_frame_list, + ); + } + let event = ScriptMsg::ChangeRunningAnimationsState( + AnimationState::NoAnimationCallbacksPresent, + ); + self.window().send_to_constellation(event); } // Update the counter of spurious animation frames. if spurious { if self.spurious_animation_frames.get() < SPURIOUS_ANIMATION_FRAME_THRESHOLD { - self.spurious_animation_frames.set(self.spurious_animation_frames.get() + 1) + self.spurious_animation_frames + .set(self.spurious_animation_frames.get() + 1) } } else { self.spurious_animation_frames.set(0) } } - pub fn fetch_async(&self, load: LoadType, - request: RequestInit, - fetch_target: IpcSender<FetchResponseMsg>) { + pub fn fetch_async( + &self, + load: LoadType, + mut request: RequestBuilder, + fetch_target: IpcSender<FetchResponseMsg>, + ) { + request.csp_list = self.get_csp_list().map(|x| x.clone()); + request.https_state = self.https_state.get(); let mut loader = self.loader.borrow_mut(); loader.fetch_async(load, request, fetch_target); } @@ -1638,13 +2075,16 @@ impl Document { self.process_deferred_scripts(); }, LoadType::PageSource(_) => { - if self.has_browsing_context { + if self.has_browsing_context && self.is_fully_active() { + // Note: if the document is not fully active, the layout thread will have exited already. + // The underlying problem might actually be that layout exits while it should be kept alive. + // See https://github.com/servo/servo/issues/22507 + // Disarm the reflow timer and trigger the initial reflow. self.reflow_timeout.set(None); self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); - self.window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::FirstLoad); + self.window + .reflow(ReflowGoal::Full, ReflowReason::FirstLoad); } // Deferred scripts have to wait for page to finish loading, @@ -1663,6 +2103,12 @@ impl Document { // asap_in_order_script_loaded. let loader = self.loader.borrow(); + + // Servo measures when the top-level content (not iframes) is loaded. + if (self.top_level_dom_complete.get() == 0) && loader.is_only_blocked_by_iframes() { + update_with_current_time_ms(&self.top_level_dom_complete); + } + if loader.is_blocked() || loader.events_inhibited() { // Step 6. return; @@ -1671,9 +2117,146 @@ impl Document { ScriptThread::mark_document_with_no_blocked_loads(self); } + // https://html.spec.whatwg.org/multipage/#prompt-to-unload-a-document + pub fn prompt_to_unload(&self, recursive_flag: bool) -> bool { + // TODO: Step 1, increase the event loop's termination nesting level by 1. + // Step 2 + self.incr_ignore_opens_during_unload_counter(); + //Step 3-5. + let beforeunload_event = BeforeUnloadEvent::new( + &self.window, + atom!("beforeunload"), + EventBubbles::Bubbles, + EventCancelable::Cancelable, + ); + let event = beforeunload_event.upcast::<Event>(); + event.set_trusted(true); + let event_target = self.window.upcast::<EventTarget>(); + let has_listeners = event.has_listeners_for(&event_target, &atom!("beforeunload")); + self.window.dispatch_event_with_target_override(&event); + // TODO: Step 6, decrease the event loop's termination nesting level by 1. + // Step 7 + if has_listeners { + self.salvageable.set(false); + } + let mut can_unload = true; + // TODO: Step 8, also check sandboxing modals flag. + let default_prevented = event.DefaultPrevented(); + let return_value_not_empty = !event + .downcast::<BeforeUnloadEvent>() + .unwrap() + .ReturnValue() + .is_empty(); + if default_prevented || return_value_not_empty { + let (chan, port) = ipc::channel().expect("Failed to create IPC channel!"); + let msg = EmbedderMsg::AllowUnload(chan); + self.send_to_embedder(msg); + can_unload = port.recv().unwrap(); + } + // Step 9 + if !recursive_flag { + for iframe in self.iter_iframes() { + // TODO: handle the case of cross origin iframes. + let document = document_from_node(&*iframe); + can_unload = document.prompt_to_unload(true); + if !document.salvageable() { + self.salvageable.set(false); + } + if !can_unload { + break; + } + } + } + // Step 10 + self.decr_ignore_opens_during_unload_counter(); + can_unload + } + + // https://html.spec.whatwg.org/multipage/#unload-a-document + pub fn unload(&self, recursive_flag: bool) { + // TODO: Step 1, increase the event loop's termination nesting level by 1. + // Step 2 + self.incr_ignore_opens_during_unload_counter(); + // Step 3-6 + if self.page_showing.get() { + self.page_showing.set(false); + let event = PageTransitionEvent::new( + &self.window, + atom!("pagehide"), + false, // bubbles + false, // cancelable + self.salvageable.get(), // persisted + ); + let event = event.upcast::<Event>(); + event.set_trusted(true); + let _ = self.window.dispatch_event_with_target_override(&event); + // TODO Step 6, document visibility steps. + } + // Step 7 + if !self.fired_unload.get() { + let event = Event::new( + &self.window.upcast(), + atom!("unload"), + EventBubbles::Bubbles, + EventCancelable::Cancelable, + ); + event.set_trusted(true); + let event_target = self.window.upcast::<EventTarget>(); + let has_listeners = event.has_listeners_for(&event_target, &atom!("unload")); + let _ = self.window.dispatch_event_with_target_override(&event); + self.fired_unload.set(true); + // Step 9 + if has_listeners { + self.salvageable.set(false); + } + } + // TODO: Step 8, decrease the event loop's termination nesting level by 1. + + // Step 13 + if !recursive_flag { + for iframe in self.iter_iframes() { + // TODO: handle the case of cross origin iframes. + let document = document_from_node(&*iframe); + document.unload(true); + if !document.salvageable() { + self.salvageable.set(false); + } + } + } + + let global_scope = self.window.upcast::<GlobalScope>(); + // Step 10, 14 + // https://html.spec.whatwg.org/multipage/#unloading-document-cleanup-steps + if !self.salvageable.get() { + // Step 1 of clean-up steps. + global_scope.close_event_sources(); + let msg = ScriptMsg::DiscardDocument; + let _ = global_scope.script_to_constellation_chan().send(msg); + } + // https://w3c.github.io/FileAPI/#lifeTime + global_scope.clean_up_all_file_resources(); + + // Step 15, End + self.decr_ignore_opens_during_unload_counter(); + } + // https://html.spec.whatwg.org/multipage/#the-end pub fn maybe_queue_document_completion(&self) { - if self.loader.borrow().is_blocked() { + // https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + let is_in_delaying_load_events_mode = match self.window.undiscarded_window_proxy() { + Some(window_proxy) => window_proxy.is_delaying_load_events_mode(), + None => false, + }; + + // Note: if the document is not fully active, the layout thread will have exited already, + // and this method will panic. + // The underlying problem might actually be that layout exits while it should be kept alive. + // See https://github.com/servo/servo/issues/22507 + let not_ready_for_load = self.loader.borrow().is_blocked() || + !self.is_fully_active() || + is_in_delaying_load_events_mode; + + if not_ready_for_load { // Step 6. return; } @@ -1684,11 +2267,90 @@ impl Document { // The rest will ever run only once per document. // Step 7. debug!("Document loads are complete."); - let handler = box DocumentProgressHandler::new(Trusted::new(self)); - self.window.dom_manipulation_task_source().queue(handler, self.window.upcast()).unwrap(); + let document = Trusted::new(self); + self.window + .task_manager() + .dom_manipulation_task_source() + .queue( + task!(fire_load_event: move || { + let document = document.root(); + let window = document.window(); + if !window.is_alive() { + return; + } + + // Step 7.1. + document.set_ready_state(DocumentReadyState::Complete); + + // Step 7.2. + if document.browsing_context().is_none() { + return; + } + let event = Event::new( + window.upcast(), + atom!("load"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); + event.set_trusted(true); + + // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventStart + update_with_current_time_ms(&document.load_event_start); + + debug!("About to dispatch load for {:?}", document.url()); + // FIXME(nox): Why are errors silenced here? + let _ = window.dispatch_event_with_target_override( + &event, + ); + + // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventEnd + update_with_current_time_ms(&document.load_event_end); + + window.reflow(ReflowGoal::Full, ReflowReason::DocumentLoaded); + + if let Some(fragment) = document.url().fragment() { + document.check_and_scroll_fragment(fragment); + } + }), + self.window.upcast(), + ) + .unwrap(); // Step 8. - // TODO: pageshow event. + let document = Trusted::new(self); + if document.root().browsing_context().is_some() { + self.window + .task_manager() + .dom_manipulation_task_source() + .queue( + task!(fire_pageshow_event: move || { + let document = document.root(); + let window = document.window(); + if document.page_showing.get() || !window.is_alive() { + return; + } + + document.page_showing.set(true); + + let event = PageTransitionEvent::new( + window, + atom!("pageshow"), + false, // bubbles + false, // cancelable + false, // persisted + ); + let event = event.upcast::<Event>(); + event.set_trusted(true); + + // FIXME(nox): Why are errors silenced here? + let _ = window.dispatch_event_with_target_override( + &event, + ); + }), + self.window.upcast(), + ) + .unwrap(); + } // Step 9. // TODO: pending application cache download process tasks. @@ -1699,16 +2361,48 @@ impl Document { // Step 11. // TODO: ready for post-load tasks. - // Step 12. - // TODO: completely loaded. + // The dom.webxr.sessionavailable pref allows webxr + // content to immediately begin a session without waiting for a user gesture. + // TODO: should this only happen on the first document loaded? + // https://immersive-web.github.io/webxr/#user-intention + // https://github.com/immersive-web/navigation/issues/10 + if pref!(dom.webxr.sessionavailable) { + if self.window.is_top_level() { + self.window.Navigator().Xr().dispatch_sessionavailable(); + } + } + + // Step 12: completely loaded. + // https://html.spec.whatwg.org/multipage/#completely-loaded + // TODO: fully implement "completely loaded". + let document = Trusted::new(self); + if document.root().browsing_context().is_some() { + self.window + .task_manager() + .dom_manipulation_task_source() + .queue( + task!(completely_loaded: move || { + let document = document.root(); + document.completely_loaded.set(true); + // Note: this will, among others, result in the "iframe-load-event-steps" being run. + // https://html.spec.whatwg.org/multipage/#iframe-load-event-steps + document.notify_constellation_load(); + }), + self.window.upcast(), + ) + .unwrap(); + } } // https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script - pub fn set_pending_parsing_blocking_script(&self, - script: &HTMLScriptElement, - load: Option<ScriptResult>) { + pub fn set_pending_parsing_blocking_script( + &self, + script: &HTMLScriptElement, + load: Option<ScriptResult>, + ) { assert!(!self.has_pending_parsing_blocking_script()); - *self.pending_parsing_blocking_script.borrow_mut() = Some(PendingScript::new_with_load(script, load)); + *self.pending_parsing_blocking_script.borrow_mut() = + Some(PendingScript::new_with_load(script, load)); } // https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script @@ -1717,7 +2411,11 @@ impl Document { } /// https://html.spec.whatwg.org/multipage/#prepare-a-script step 22.d. - pub fn pending_parsing_blocking_script_loaded(&self, element: &HTMLScriptElement, result: ScriptResult) { + pub fn pending_parsing_blocking_script_loaded( + &self, + element: &HTMLScriptElement, + result: ScriptResult, + ) { { let mut blocking_script = self.pending_parsing_blocking_script.borrow_mut(); let entry = blocking_script.as_mut().unwrap(); @@ -1731,19 +2429,24 @@ impl Document { if self.script_blocking_stylesheets_count.get() > 0 { return; } - let pair = self.pending_parsing_blocking_script + let pair = self + .pending_parsing_blocking_script .borrow_mut() .as_mut() .and_then(PendingScript::take_result); if let Some((element, result)) = pair { *self.pending_parsing_blocking_script.borrow_mut() = None; - self.get_current_parser().unwrap().resume_with_pending_parsing_blocking_script(&element, result); + self.get_current_parser() + .unwrap() + .resume_with_pending_parsing_blocking_script(&element, result); } } // https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible pub fn add_asap_script(&self, script: &HTMLScriptElement) { - self.asap_scripts_set.borrow_mut().push(JS::from_ref(script)); + self.asap_scripts_set + .borrow_mut() + .push(Dom::from_ref(script)); } /// https://html.spec.whatwg.org/multipage/#the-end step 5. @@ -1751,7 +2454,10 @@ impl Document { pub fn asap_script_loaded(&self, element: &HTMLScriptElement, result: ScriptResult) { { let mut scripts = self.asap_scripts_set.borrow_mut(); - let idx = scripts.iter().position(|entry| &**entry == element).unwrap(); + let idx = scripts + .iter() + .position(|entry| &**entry == element) + .unwrap(); scripts.swap_remove(idx); } element.execute(result); @@ -1764,11 +2470,12 @@ impl Document { /// https://html.spec.whatwg.org/multipage/#the-end step 5. /// https://html.spec.whatwg.org/multipage/#prepare-a-script step 22.c. - pub fn asap_in_order_script_loaded(&self, - element: &HTMLScriptElement, - result: ScriptResult) { + pub fn asap_in_order_script_loaded(&self, element: &HTMLScriptElement, result: ScriptResult) { self.asap_in_order_scripts_list.loaded(element, result); - while let Some((element, result)) = self.asap_in_order_scripts_list.take_next_ready_to_be_executed() { + while let Some((element, result)) = self + .asap_in_order_scripts_list + .take_next_ready_to_be_executed() + { element.execute(result); } } @@ -1795,7 +2502,8 @@ impl Document { if self.script_blocking_stylesheets_count.get() > 0 { return; } - if let Some((element, result)) = self.deferred_scripts.take_next_ready_to_be_executed() { + if let Some((element, result)) = self.deferred_scripts.take_next_ready_to_be_executed() + { element.execute(result); } else { break; @@ -1813,27 +2521,41 @@ impl Document { return; } self.domcontentloaded_dispatched.set(true); - assert!(self.ReadyState() != DocumentReadyState::Complete, - "Complete before DOMContentLoaded?"); + assert_ne!( + self.ReadyState(), + DocumentReadyState::Complete, + "Complete before DOMContentLoaded?" + ); update_with_current_time_ms(&self.dom_content_loaded_event_start); // Step 4.1. let window = self.window(); - window.dom_manipulation_task_source().queue_event(self.upcast(), atom!("DOMContentLoaded"), - EventBubbles::Bubbles, EventCancelable::NotCancelable, window); + let document = Trusted::new(self); + window + .task_manager() + .dom_manipulation_task_source() + .queue( + task!(fire_dom_content_loaded_event: move || { + let document = document.root(); + document.upcast::<EventTarget>().fire_bubbling_event(atom!("DOMContentLoaded")); + update_with_current_time_ms(&document.dom_content_loaded_event_end); + }), + window.upcast(), + ) + .unwrap(); - window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::DOMContentLoaded); - update_with_current_time_ms(&self.dom_content_loaded_event_end); + // html parsing has finished - set dom content loaded + self.interactive_time + .borrow() + .maybe_set_tti(self, InteractiveFlag::DOMContentLoaded); // Step 4.2. // TODO: client message queue. } // https://html.spec.whatwg.org/multipage/#abort-a-document - fn abort(&self) { + pub fn abort(&self) { // We need to inhibit the loader before anything else. self.loader.borrow_mut().inhibit_events(); @@ -1852,43 +2574,67 @@ impl Document { *self.asap_scripts_set.borrow_mut() = vec![]; self.asap_in_order_scripts_list.clear(); self.deferred_scripts.clear(); + let global_scope = self.window.upcast::<GlobalScope>(); + let loads_cancelled = self.loader.borrow_mut().cancel_all_loads(); + let event_sources_canceled = global_scope.close_event_sources(); + if loads_cancelled || event_sources_canceled { + // If any loads were canceled. + self.salvageable.set(false); + }; - // TODO: https://github.com/servo/servo/issues/15236 - self.window.cancel_all_tasks(); + // Also Step 2. + // Note: the spec says to discard any tasks queued for fetch. + // This cancels all tasks on the networking task source, which might be too broad. + // See https://github.com/whatwg/html/issues/3837 + self.window + .cancel_all_tasks_from_source(TaskSourceName::Networking); // Step 3. if let Some(parser) = self.get_current_parser() { + self.active_parser_was_aborted.set(true); parser.abort(); - // TODO: salvageable flag. + self.salvageable.set(false); } } pub fn notify_constellation_load(&self) { - let global_scope = self.window.upcast::<GlobalScope>(); - let pipeline_id = global_scope.pipeline_id(); - let load_event = ConstellationMsg::LoadComplete(pipeline_id); - global_scope.constellation_chan().send(load_event).unwrap(); + self.window().send_to_constellation(ScriptMsg::LoadComplete); } pub fn set_current_parser(&self, script: Option<&ServoParser>) { self.current_parser.set(script); } - pub fn get_current_parser(&self) -> Option<Root<ServoParser>> { + pub fn get_current_parser(&self) -> Option<DomRoot<ServoParser>> { self.current_parser.get() } + pub fn can_invoke_script(&self) -> bool { + match self.get_current_parser() { + Some(parser) => { + // It is safe to run script if the parser is not actively parsing, + // or if it is impossible to interact with the token stream. + parser.parser_is_not_active() || + self.throw_on_dynamic_markup_insertion_counter.get() > 0 + }, + None => true, + } + } + /// Iterate over all iframes in the document. - pub fn iter_iframes(&self) -> impl Iterator<Item=Root<HTMLIFrameElement>> { + pub fn iter_iframes(&self) -> impl Iterator<Item = DomRoot<HTMLIFrameElement>> { self.upcast::<Node>() - .traverse_preorder() - .filter_map(Root::downcast::<HTMLIFrameElement>) + .traverse_preorder(ShadowIncluding::Yes) + .filter_map(DomRoot::downcast::<HTMLIFrameElement>) } /// Find an iframe element in the document. - pub fn find_iframe(&self, frame_id: FrameId) -> Option<Root<HTMLIFrameElement>> { + pub fn find_iframe( + &self, + browsing_context_id: BrowsingContextId, + ) -> Option<DomRoot<HTMLIFrameElement>> { self.iter_iframes() - .find(|node| node.frame_id() == frame_id) + .find(|node| node.browsing_context_id() == Some(browsing_context_id)) } pub fn get_dom_loading(&self) -> u64 { @@ -1899,6 +2645,20 @@ impl Document { self.dom_interactive.get() } + pub fn set_navigation_start(&self, navigation_start: u64) { + self.interactive_time + .borrow_mut() + .set_navigation_start(navigation_start); + } + + pub fn get_interactive_metrics(&self) -> Ref<InteractiveMetrics> { + self.interactive_time.borrow() + } + + pub fn has_recorded_tti_metric(&self) -> bool { + self.get_interactive_metrics().get_tti().is_some() + } + pub fn get_dom_content_loaded_event_start(&self) -> u64 { self.dom_content_loaded_event_start.get() } @@ -1911,6 +2671,10 @@ impl Document { self.dom_complete.get() } + pub fn get_top_level_dom_complete(&self) -> u64 { + self.top_level_dom_complete.get() + } + pub fn get_load_event_start(&self) -> u64 { self.load_event_start.get() } @@ -1919,106 +2683,349 @@ impl Document { self.load_event_end.get() } + pub fn get_unload_event_start(&self) -> u64 { + self.unload_event_start.get() + } + + pub fn get_unload_event_end(&self) -> u64 { + self.unload_event_end.get() + } + + pub fn start_tti(&self) { + if self.get_interactive_metrics().needs_tti() { + self.tti_window.borrow_mut().start_window(); + } + } + + /// check tti for this document + /// if it's been 10s since this doc encountered a task over 50ms, then we consider the + /// main thread available and try to set tti + pub fn record_tti_if_necessary(&self) { + if self.has_recorded_tti_metric() { + return; + } + if self.tti_window.borrow().needs_check() { + self.get_interactive_metrics().maybe_set_tti( + self, + InteractiveFlag::TimeToInteractive(self.tti_window.borrow().get_start()), + ); + } + } + // https://html.spec.whatwg.org/multipage/#fire-a-focus-event - fn fire_focus_event(&self, focus_event_type: FocusEventType, node: &Node, related_target: Option<&EventTarget>) { + fn fire_focus_event( + &self, + focus_event_type: FocusEventType, + node: &Node, + related_target: Option<&EventTarget>, + ) { let (event_name, does_bubble) = match focus_event_type { FocusEventType::Focus => (DOMString::from("focus"), EventBubbles::DoesNotBubble), FocusEventType::Blur => (DOMString::from("blur"), EventBubbles::DoesNotBubble), }; - let event = FocusEvent::new(&self.window, - event_name, - does_bubble, - EventCancelable::NotCancelable, - Some(&self.window), - 0i32, - related_target); + let event = FocusEvent::new( + &self.window, + event_name, + does_bubble, + EventCancelable::NotCancelable, + Some(&self.window), + 0i32, + related_target, + ); let event = event.upcast::<Event>(); event.set_trusted(true); let target = node.upcast(); event.fire(target); } - /// https://html.spec.whatwg.org/multipage/#cookie-averse-document-object + /// <https://html.spec.whatwg.org/multipage/#cookie-averse-document-object> pub fn is_cookie_averse(&self) -> bool { !self.has_browsing_context || !url_has_network_scheme(&self.url()) } - pub fn nodes_from_point(&self, client_point: &Point2D<f32>) -> Vec<UntrustedNodeAddress> { - let page_point = - Point2D::new(client_point.x + self.window.PageXOffset() as f32, - client_point.y + self.window.PageYOffset() as f32); + /// <https://html.spec.whatwg.org/multipage/#look-up-a-custom-element-definition> + pub fn lookup_custom_element_definition( + &self, + namespace: &Namespace, + local_name: &LocalName, + is: Option<&LocalName>, + ) -> Option<Rc<CustomElementDefinition>> { + if !pref!(dom.custom_elements.enabled) { + return None; + } - if !self.window.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::NodesFromPoint(page_point, *client_point), - ReflowReason::Query) { - return vec!(); - }; + // Step 1 + if *namespace != ns!(html) { + return None; + } + + // Step 2 + if !self.has_browsing_context { + return None; + } - self.window.layout().nodes_from_point_response() + // Step 3 + let registry = self.window.CustomElements(); + + registry.lookup_definition(local_name, is) + } + + pub fn increment_throw_on_dynamic_markup_insertion_counter(&self) { + let counter = self.throw_on_dynamic_markup_insertion_counter.get(); + self.throw_on_dynamic_markup_insertion_counter + .set(counter + 1); + } + + pub fn decrement_throw_on_dynamic_markup_insertion_counter(&self) { + let counter = self.throw_on_dynamic_markup_insertion_counter.get(); + self.throw_on_dynamic_markup_insertion_counter + .set(counter - 1); + } + + pub fn react_to_environment_changes(&self) { + for image in self.responsive_images.borrow().iter() { + image.react_to_environment_changes(); + } + } + + pub fn register_responsive_image(&self, img: &HTMLImageElement) { + self.responsive_images.borrow_mut().push(Dom::from_ref(img)); + } + + pub fn unregister_responsive_image(&self, img: &HTMLImageElement) { + let index = self + .responsive_images + .borrow() + .iter() + .position(|x| **x == *img); + if let Some(i) = index { + self.responsive_images.borrow_mut().remove(i); + } + } + + pub fn register_media_controls(&self, controls: &ShadowRoot) -> String { + let id = Uuid::new_v4().to_string(); + self.media_controls + .borrow_mut() + .insert(id.clone(), Dom::from_ref(controls)); + id + } + + pub fn unregister_media_controls(&self, id: &str) { + if let Some(ref media_controls) = self.media_controls.borrow_mut().remove(id) { + let media_controls = DomRoot::from_ref(&**media_controls); + media_controls.Host().detach_shadow(); + } else { + debug_assert!(false, "Trying to unregister unknown media controls"); + } + } + + pub fn add_dirty_webgl_canvas(&self, context: &WebGLRenderingContext) { + self.dirty_webgl_contexts + .borrow_mut() + .entry(context.context_id()) + .or_insert_with(|| Dom::from_ref(context)); + } + + pub fn flush_dirty_webgl_canvases(&self) { + let dirty_context_ids: Vec<_> = self + .dirty_webgl_contexts + .borrow_mut() + .drain() + .filter(|(_, context)| context.onscreen()) + .map(|(id, _)| id) + .collect(); + + if dirty_context_ids.is_empty() { + return; + } + + #[allow(unused)] + let mut time = 0; + #[cfg(feature = "xr-profile")] + { + time = time::precise_time_ns(); + } + let (sender, receiver) = webgl::webgl_channel().unwrap(); + self.window + .webgl_chan() + .expect("Where's the WebGL channel?") + .send(WebGLMsg::SwapBuffers(dirty_context_ids, sender, time)) + .unwrap(); + receiver.recv().unwrap(); + } + + pub fn add_dirty_webgpu_canvas(&self, context: &GPUCanvasContext) { + self.dirty_webgpu_contexts + .borrow_mut() + .entry(context.context_id()) + .or_insert_with(|| Dom::from_ref(context)); + } + + #[allow(unrooted_must_root)] + pub fn flush_dirty_webgpu_canvases(&self) { + self.dirty_webgpu_contexts + .borrow_mut() + .drain() + .for_each(|(_, context)| context.send_swap_chain_present()); + } + + // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names + // (This takes the filter as a method so the window named getter can use it too) + pub fn supported_property_names_impl( + &self, + nameditem_filter: fn(&Node, &Atom) -> bool, + ) -> Vec<DOMString> { + // The tricky part here is making sure we return the names in + // tree order, without just resorting to a full tree walkthrough. + + let mut first_elements_with_name: HashMap<&Atom, &Dom<Element>> = HashMap::new(); + + // Get the first-in-tree-order element for each name in the name_map + let name_map = self.name_map.borrow(); + name_map.iter().for_each(|(name, value)| { + if let Some(first) = value + .iter() + .find(|n| nameditem_filter((***n).upcast::<Node>(), &name)) + { + first_elements_with_name.insert(name, first); + } + }); + + // Get the first-in-tree-order element for each name in the id_map; + // if we already had one from the name_map, figure out which of + // the two is first. + let id_map = self.id_map.borrow(); + id_map.iter().for_each(|(name, value)| { + if let Some(first) = value + .iter() + .find(|n| nameditem_filter((***n).upcast::<Node>(), &name)) + { + match first_elements_with_name.get(&name) { + None => { + first_elements_with_name.insert(name, first); + }, + Some(el) => { + if *el != first && first.upcast::<Node>().is_before(el.upcast::<Node>()) { + first_elements_with_name.insert(name, first); + } + }, + } + } + }); + + // first_elements_with_name now has our supported property names + // as keys, and the elements to order on as values. + let mut sortable_vec: Vec<(&Atom, &Dom<Element>)> = first_elements_with_name + .iter() + .map(|(k, v)| (*k, *v)) + .collect(); + sortable_vec.sort_unstable_by(|a, b| { + if a.1 == b.1 { + // This can happen if an img has an id different from its name, + // spec does not say which string to put first. + a.0.cmp(&b.0) + } else if a.1.upcast::<Node>().is_before(b.1.upcast::<Node>()) { + Ordering::Less + } else { + Ordering::Greater + } + }); + + // And now that they're sorted, we can return the keys + sortable_vec + .iter() + .map(|(k, _v)| DOMString::from(&***k)) + .collect() + } +} + +fn is_character_value_key(key: &Key) -> bool { + match key { + Key::Character(_) | Key::Enter => true, + _ => false, } } -#[derive(PartialEq, HeapSizeOf)] +#[derive(MallocSizeOf, PartialEq)] pub enum DocumentSource { FromParser, NotFromParser, } #[allow(unsafe_code)] -pub trait LayoutDocumentHelpers { - unsafe fn is_html_document_for_layout(&self) -> bool; - unsafe fn drain_pending_restyles(&self) -> Vec<(LayoutJS<Element>, PendingRestyle)>; - unsafe fn needs_paint_from_layout(&self); - unsafe fn will_paint(&self); - unsafe fn quirks_mode(&self) -> QuirksMode; - unsafe fn style_shared_lock(&self) -> &StyleSharedRwLock; +pub trait LayoutDocumentHelpers<'dom> { + fn is_html_document_for_layout(self) -> bool; + unsafe fn needs_paint_from_layout(self); + unsafe fn will_paint(self); + fn quirks_mode(self) -> QuirksMode; + fn style_shared_lock(self) -> &'dom StyleSharedRwLock; + fn shadow_roots(self) -> Vec<LayoutDom<'dom, ShadowRoot>>; + fn shadow_roots_styles_changed(self) -> bool; + unsafe fn flush_shadow_roots_stylesheets(self); } #[allow(unsafe_code)] -impl LayoutDocumentHelpers for LayoutJS<Document> { +impl<'dom> LayoutDocumentHelpers<'dom> for LayoutDom<'dom, Document> { #[inline] - unsafe fn is_html_document_for_layout(&self) -> bool { - (*self.unsafe_get()).is_html_document + fn is_html_document_for_layout(self) -> bool { + unsafe { self.unsafe_get().is_html_document } } #[inline] - #[allow(unrooted_must_root)] - unsafe fn drain_pending_restyles(&self) -> Vec<(LayoutJS<Element>, PendingRestyle)> { - let mut elements = (*self.unsafe_get()).pending_restyles.borrow_mut_for_layout(); - // Elements were in a document when they were adding to this list, but that - // may no longer be true when the next layout occurs. - let result = elements.drain() - .map(|(k, v)| (k.to_layout(), v)) - .filter(|&(ref k, _)| k.upcast::<Node>().get_flag(IS_IN_DOC)) - .collect(); - result + unsafe fn needs_paint_from_layout(self) { + (*self.unsafe_get()).needs_paint.set(true) } #[inline] - unsafe fn needs_paint_from_layout(&self) { - (*self.unsafe_get()).needs_paint.set(true) + unsafe fn will_paint(self) { + (*self.unsafe_get()).needs_paint.set(false) } #[inline] - unsafe fn will_paint(&self) { - (*self.unsafe_get()).needs_paint.set(false) + fn quirks_mode(self) -> QuirksMode { + unsafe { self.unsafe_get().quirks_mode.get() } } #[inline] - unsafe fn quirks_mode(&self) -> QuirksMode { - (*self.unsafe_get()).quirks_mode() + fn style_shared_lock(self) -> &'dom StyleSharedRwLock { + unsafe { self.unsafe_get().style_shared_lock() } } #[inline] - unsafe fn style_shared_lock(&self) -> &StyleSharedRwLock { - (*self.unsafe_get()).style_shared_lock() + fn shadow_roots(self) -> Vec<LayoutDom<'dom, ShadowRoot>> { + // FIXME(nox): We should just return a + // &'dom HashSet<LayoutDom<'dom, ShadowRoot>> here but not until + // I rework the ToLayout trait as mentioned in + // LayoutDom::to_layout_slice. + unsafe { + self.unsafe_get() + .shadow_roots + .borrow_for_layout() + .iter() + .map(|sr| sr.to_layout()) + .collect() + } + } + + #[inline] + fn shadow_roots_styles_changed(self) -> bool { + unsafe { self.unsafe_get().shadow_roots_styles_changed.get() } + } + + #[inline] + unsafe fn flush_shadow_roots_stylesheets(self) { + (*self.unsafe_get()).flush_shadow_roots_stylesheets() } } // https://html.spec.whatwg.org/multipage/#is-a-registrable-domain-suffix-of-or-is-equal-to // The spec says to return a bool, we actually return an Option<Host> containing // the parsed host in the successful case, to avoid having to re-parse the host. -fn get_registrable_domain_suffix_of_or_is_equal_to(host_suffix_string: &str, original_host: Host) -> Option<Host> { +fn get_registrable_domain_suffix_of_or_is_equal_to( + host_suffix_string: &str, + original_host: Host, +) -> Option<Host> { // Step 1 if host_suffix_string.is_empty() { return None; @@ -2043,10 +3050,9 @@ fn get_registrable_domain_suffix_of_or_is_equal_to(host_suffix_string: &str, ori }; // Step 4.2 - let (prefix, suffix) = match original_host.len().checked_sub(host.len()) { - Some(index) => original_host.split_at(index), - None => return None, - }; + let index = original_host.len().checked_sub(host.len())?; + let (prefix, suffix) = original_host.split_at(index); + if !prefix.ends_with(".") { return None; } @@ -2064,7 +3070,7 @@ fn get_registrable_domain_suffix_of_or_is_equal_to(host_suffix_string: &str, ori Some(host) } -/// https://url.spec.whatwg.org/#network-scheme +/// <https://url.spec.whatwg.org/#network-scheme> fn url_has_network_scheme(url: &ServoUrl) -> bool { match url.scheme() { "ftp" | "http" | "https" => true, @@ -2072,26 +3078,28 @@ fn url_has_network_scheme(url: &ServoUrl) -> bool { } } -#[derive(Copy, Clone, HeapSizeOf, JSTraceable, PartialEq, Eq)] +#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)] pub enum HasBrowsingContext { No, Yes, } impl Document { - pub fn new_inherited(window: &Window, - has_browsing_context: HasBrowsingContext, - url: Option<ServoUrl>, - origin: MutableOrigin, - is_html_document: IsHTMLDocument, - content_type: Option<DOMString>, - last_modified: Option<String>, - activity: DocumentActivity, - source: DocumentSource, - doc_loader: DocumentLoader, - referrer: Option<String>, - referrer_policy: Option<ReferrerPolicy>) - -> Document { + pub fn new_inherited( + window: &Window, + has_browsing_context: HasBrowsingContext, + url: Option<ServoUrl>, + origin: MutableOrigin, + is_html_document: IsHTMLDocument, + content_type: Option<Mime>, + last_modified: Option<String>, + activity: DocumentActivity, + source: DocumentSource, + doc_loader: DocumentLoader, + referrer: Option<String>, + referrer_policy: Option<ReferrerPolicy>, + canceller: FetchCanceller, + ) -> Document { let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap()); let (ready_state, domcontentloaded_dispatched) = if source == DocumentSource::FromParser { @@ -2100,33 +3108,44 @@ impl Document { (DocumentReadyState::Complete, true) }; + let interactive_time = + InteractiveMetrics::new(window.time_profiler_chan().clone(), url.clone()); + + let content_type = content_type.unwrap_or_else(|| { + match is_html_document { + // https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument + IsHTMLDocument::HTMLDocument => mime::TEXT_HTML, + // https://dom.spec.whatwg.org/#concept-document-content-type + IsHTMLDocument::NonHTMLDocument => "application/xml".parse().unwrap(), + } + }); + + let encoding = content_type + .get_param(mime::CHARSET) + .and_then(|charset| Encoding::for_label(charset.as_str().as_bytes())) + .unwrap_or(UTF_8); + + let has_browsing_context = has_browsing_context == HasBrowsingContext::Yes; Document { node: Node::new_document_node(), - window: JS::from_ref(window), - has_browsing_context: has_browsing_context == HasBrowsingContext::Yes, + document_or_shadow_root: DocumentOrShadowRoot::new(window), + window: Dom::from_ref(window), + has_browsing_context, implementation: Default::default(), - location: Default::default(), - content_type: match content_type { - Some(string) => string, - None => DOMString::from(match is_html_document { - // https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument - IsHTMLDocument::HTMLDocument => "text/html", - // https://dom.spec.whatwg.org/#concept-document-content-type - IsHTMLDocument::NonHTMLDocument => "application/xml", - }), - }, + content_type, last_modified: last_modified, - url: DOMRefCell::new(url), + url: DomRefCell::new(url), // https://dom.spec.whatwg.org/#concept-document-quirks quirks_mode: Cell::new(QuirksMode::NoQuirks), + id_map: DomRefCell::new(HashMap::new()), + name_map: DomRefCell::new(HashMap::new()), // https://dom.spec.whatwg.org/#concept-document-encoding - encoding: Cell::new(UTF_8), + encoding: Cell::new(encoding), is_html_document: is_html_document == IsHTMLDocument::HTMLDocument, activity: Cell::new(activity), - id_map: DOMRefCell::new(HashMap::new()), - tag_map: DOMRefCell::new(HashMap::new()), - tagns_map: DOMRefCell::new(HashMap::new()), - classes_map: DOMRefCell::new(HashMap::new()), + tag_map: DomRefCell::new(HashMap::new()), + tagns_map: DomRefCell::new(HashMap::new()), + classes_map: DomRefCell::new(HashMap::new()), images: Default::default(), embeds: Default::default(), links: Default::default(), @@ -2139,7 +3158,7 @@ impl Document { /// Per-process shared lock for author-origin stylesheets /// /// FIXME: make it per-document or per-pipeline instead: - /// https://github.com/servo/servo/issues/16027 + /// <https://github.com/servo/servo/issues/16027> /// (Need to figure out what to do with the style attribute /// of elements adopted into another document.) static ref PER_PROCESS_AUTHOR_SHARED_LOCK: StyleSharedRwLock = { @@ -2149,12 +3168,11 @@ impl Document { PER_PROCESS_AUTHOR_SHARED_LOCK.clone() //StyleSharedRwLock::new() }, - stylesheets: DOMRefCell::new(None), - stylesheets_changed_since_reflow: Cell::new(false), - stylesheet_list: MutNullableJS::new(None), + stylesheets: DomRefCell::new(DocumentStylesheetSet::new()), + stylesheet_list: MutNullableDom::new(None), ready_state: Cell::new(ready_state), domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched), - possibly_focused: Default::default(), + focus_transaction: DomRefCell::new(FocusTransaction::NotInTransaction), focused: Default::default(), current_script: Default::default(), pending_parsing_blocking_script: Default::default(), @@ -2162,85 +3180,194 @@ impl Document { deferred_scripts: Default::default(), asap_in_order_scripts_list: Default::default(), asap_scripts_set: Default::default(), - scripting_enabled: has_browsing_context == HasBrowsingContext::Yes, + scripting_enabled: has_browsing_context, animation_frame_ident: Cell::new(0), - animation_frame_list: DOMRefCell::new(vec![]), + animation_frame_list: DomRefCell::new(vec![]), running_animation_callbacks: Cell::new(false), - loader: DOMRefCell::new(doc_loader), + loader: DomRefCell::new(doc_loader), current_parser: Default::default(), reflow_timeout: Cell::new(None), base_element: Default::default(), appropriate_template_contents_owner_document: Default::default(), - pending_restyles: DOMRefCell::new(HashMap::new()), + pending_restyles: DomRefCell::new(HashMap::new()), needs_paint: Cell::new(false), - active_touch_points: DOMRefCell::new(Vec::new()), + active_touch_points: DomRefCell::new(Vec::new()), dom_loading: Cell::new(Default::default()), dom_interactive: Cell::new(Default::default()), dom_content_loaded_event_start: Cell::new(Default::default()), dom_content_loaded_event_end: Cell::new(Default::default()), dom_complete: Cell::new(Default::default()), + top_level_dom_complete: Cell::new(Default::default()), load_event_start: Cell::new(Default::default()), load_event_end: Cell::new(Default::default()), + unload_event_start: Cell::new(Default::default()), + unload_event_end: Cell::new(Default::default()), https_state: Cell::new(HttpsState::None), - touchpad_pressure_phase: Cell::new(TouchpadPressurePhase::BeforeClick), origin: origin, referrer: referrer, referrer_policy: Cell::new(referrer_policy), - target_element: MutNullableJS::new(None), - last_click_info: DOMRefCell::new(None), + target_element: MutNullableDom::new(None), + last_click_info: DomRefCell::new(None), ignore_destructive_writes_counter: Default::default(), + ignore_opens_during_unload_counter: Default::default(), spurious_animation_frames: Cell::new(0), dom_count: Cell::new(1), - fullscreen_element: MutNullableJS::new(None), + fullscreen_element: MutNullableDom::new(None), form_id_listener_map: Default::default(), + interactive_time: DomRefCell::new(interactive_time), + tti_window: DomRefCell::new(InteractiveWindow::new()), + canceller: canceller, + throw_on_dynamic_markup_insertion_counter: Cell::new(0), + page_showing: Cell::new(false), + salvageable: Cell::new(true), + active_parser_was_aborted: Cell::new(false), + fired_unload: Cell::new(false), + responsive_images: Default::default(), + redirect_count: Cell::new(0), + completely_loaded: Cell::new(false), + script_and_layout_blockers: Cell::new(0), + delayed_tasks: Default::default(), + shadow_roots: DomRefCell::new(HashSet::new()), + shadow_roots_styles_changed: Cell::new(false), + media_controls: DomRefCell::new(HashMap::new()), + dirty_webgl_contexts: DomRefCell::new(HashMap::new()), + dirty_webgpu_contexts: DomRefCell::new(HashMap::new()), + csp_list: DomRefCell::new(None), + selection: MutNullableDom::new(None), + animation_timeline: if pref!(layout.animations.test.enabled) { + DomRefCell::new(AnimationTimeline::new_for_testing()) + } else { + DomRefCell::new(AnimationTimeline::new()) + }, + animations: DomRefCell::new(Animations::new()), + dirty_root: Default::default(), } } + pub fn set_csp_list(&self, csp_list: Option<CspList>) { + *self.csp_list.borrow_mut() = csp_list; + } + + pub fn get_csp_list(&self) -> Option<Ref<CspList>> { + ref_filter_map(self.csp_list.borrow(), Option::as_ref) + } + + /// https://www.w3.org/TR/CSP/#should-block-inline + pub fn should_elements_inline_type_behavior_be_blocked( + &self, + el: &Element, + type_: csp::InlineCheckType, + source: &str, + ) -> csp::CheckResult { + let element = csp::Element { + nonce: el + .get_attribute(&ns!(), &local_name!("nonce")) + .map(|attr| Cow::Owned(attr.value().to_string())), + }; + // TODO: Instead of ignoring violations, report them. + self.get_csp_list() + .map(|c| { + c.should_elements_inline_type_behavior_be_blocked(&element, type_, source) + .0 + }) + .unwrap_or(csp::CheckResult::Allowed) + } + + /// Prevent any JS or layout from running until the corresponding call to + /// `remove_script_and_layout_blocker`. Used to isolate periods in which + /// the DOM is in an unstable state and should not be exposed to arbitrary + /// web content. Any attempts to invoke content JS or query layout during + /// that time will trigger a panic. `add_delayed_task` will cause the + /// provided task to be executed as soon as the last blocker is removed. + pub fn add_script_and_layout_blocker(&self) { + self.script_and_layout_blockers + .set(self.script_and_layout_blockers.get() + 1); + } + + /// Terminate the period in which JS or layout is disallowed from running. + /// If no further blockers remain, any delayed tasks in the queue will + /// be executed in queue order until the queue is empty. + pub fn remove_script_and_layout_blocker(&self) { + assert!(self.script_and_layout_blockers.get() > 0); + self.script_and_layout_blockers + .set(self.script_and_layout_blockers.get() - 1); + while self.script_and_layout_blockers.get() == 0 && !self.delayed_tasks.borrow().is_empty() + { + let task = self.delayed_tasks.borrow_mut().remove(0); + task.run_box(); + } + } + + /// Enqueue a task to run as soon as any JS and layout blockers are removed. + pub fn add_delayed_task<T: 'static + TaskBox>(&self, task: T) { + self.delayed_tasks.borrow_mut().push(Box::new(task)); + } + + /// Assert that the DOM is in a state that will allow running content JS or + /// performing a layout operation. + pub fn ensure_safe_to_run_script_or_layout(&self) { + assert_eq!( + self.script_and_layout_blockers.get(), + 0, + "Attempt to use script or layout while DOM not in a stable state" + ); + } + // https://dom.spec.whatwg.org/#dom-document-document - pub fn Constructor(window: &Window) -> Fallible<Root<Document>> { + #[allow(non_snake_case)] + pub fn Constructor(window: &Window) -> Fallible<DomRoot<Document>> { let doc = window.Document(); let docloader = DocumentLoader::new(&*doc.loader()); - Ok(Document::new(window, - HasBrowsingContext::No, - None, - doc.origin().clone(), - IsHTMLDocument::NonHTMLDocument, - None, - None, - DocumentActivity::Inactive, - DocumentSource::NotFromParser, - docloader, - None, - None)) - } - - pub fn new(window: &Window, - has_browsing_context: HasBrowsingContext, - url: Option<ServoUrl>, - origin: MutableOrigin, - doctype: IsHTMLDocument, - content_type: Option<DOMString>, - last_modified: Option<String>, - activity: DocumentActivity, - source: DocumentSource, - doc_loader: DocumentLoader, - referrer: Option<String>, - referrer_policy: Option<ReferrerPolicy>) - -> Root<Document> { - let document = reflect_dom_object(box Document::new_inherited(window, - has_browsing_context, - url, - origin, - doctype, - content_type, - last_modified, - activity, - source, - doc_loader, - referrer, - referrer_policy), - window, - DocumentBinding::Wrap); + Ok(Document::new( + window, + HasBrowsingContext::No, + None, + doc.origin().clone(), + IsHTMLDocument::NonHTMLDocument, + None, + None, + DocumentActivity::Inactive, + DocumentSource::NotFromParser, + docloader, + None, + None, + Default::default(), + )) + } + + pub fn new( + window: &Window, + has_browsing_context: HasBrowsingContext, + url: Option<ServoUrl>, + origin: MutableOrigin, + doctype: IsHTMLDocument, + content_type: Option<Mime>, + last_modified: Option<String>, + activity: DocumentActivity, + source: DocumentSource, + doc_loader: DocumentLoader, + referrer: Option<String>, + referrer_policy: Option<ReferrerPolicy>, + canceller: FetchCanceller, + ) -> DomRoot<Document> { + let document = reflect_dom_object( + Box::new(Document::new_inherited( + window, + has_browsing_context, + url, + origin, + doctype, + content_type, + last_modified, + activity, + source, + doc_loader, + referrer, + referrer_policy, + canceller, + )), + window, + ); { let node = document.upcast::<Node>(); node.set_owner_doc(&document); @@ -2248,34 +3375,68 @@ impl Document { document } - fn create_node_list<F: Fn(&Node) -> bool>(&self, callback: F) -> Root<NodeList> { - let doc = self.GetDocumentElement(); - let maybe_node = doc.r().map(Castable::upcast::<Node>); - let iter = maybe_node.iter() - .flat_map(|node| node.traverse_preorder()) - .filter(|node| callback(&node)); - NodeList::new_simple_list(&self.window, iter) + pub fn get_redirect_count(&self) -> u16 { + self.redirect_count.get() } - fn get_html_element(&self) -> Option<Root<HTMLHtmlElement>> { - self.GetDocumentElement().and_then(Root::downcast) + pub fn set_redirect_count(&self, count: u16) { + self.redirect_count.set(count) } - // Ensure that the stylesheets vector is populated - fn ensure_stylesheets(&self) { - let mut stylesheets = self.stylesheets.borrow_mut(); - if stylesheets.is_none() { - *stylesheets = Some(self.upcast::<Node>() - .traverse_preorder() - .filter_map(|node| { - node.get_stylesheet() - .map(|stylesheet| StylesheetInDocument { - node: JS::from_ref(&*node), - stylesheet: stylesheet, - }) - }) - .collect()); + pub fn elements_by_name_count(&self, name: &DOMString) -> u32 { + if name.is_empty() { + return 0; + } + self.count_node_list(|n| Document::is_element_in_get_by_name(n, name)) + } + + pub fn nth_element_by_name(&self, index: u32, name: &DOMString) -> Option<DomRoot<Node>> { + if name.is_empty() { + return None; + } + self.nth_in_node_list(index, |n| Document::is_element_in_get_by_name(n, name)) + } + + // Note that document.getByName does not match on the same conditions + // as the document named getter. + fn is_element_in_get_by_name(node: &Node, name: &DOMString) -> bool { + let element = match node.downcast::<Element>() { + Some(element) => element, + None => return false, }; + if element.namespace() != &ns!(html) { + return false; + } + element.get_name().map_or(false, |n| *n == **name) + } + + fn count_node_list<F: Fn(&Node) -> bool>(&self, callback: F) -> u32 { + let doc = self.GetDocumentElement(); + let maybe_node = doc.as_deref().map(Castable::upcast::<Node>); + maybe_node + .iter() + .flat_map(|node| node.traverse_preorder(ShadowIncluding::No)) + .filter(|node| callback(&node)) + .count() as u32 + } + + fn nth_in_node_list<F: Fn(&Node) -> bool>( + &self, + index: u32, + callback: F, + ) -> Option<DomRoot<Node>> { + let doc = self.GetDocumentElement(); + let maybe_node = doc.as_deref().map(Castable::upcast::<Node>); + maybe_node + .iter() + .flat_map(|node| node.traverse_preorder(ShadowIncluding::No)) + .filter(|node| callback(&node)) + .nth(index as usize) + .map(|n| DomRoot::from_ref(&*n)) + } + + fn get_html_element(&self) -> Option<DomRoot<HTMLHtmlElement>> { + self.GetDocumentElement().and_then(DomRoot::downcast) } /// Return a reference to the per-document shared lock used in stylesheets. @@ -2283,61 +3444,94 @@ impl Document { &self.style_shared_lock } - /// Returns the list of stylesheets associated with nodes in the document. - pub fn stylesheets(&self) -> Vec<Arc<Stylesheet>> { - self.ensure_stylesheets(); - self.stylesheets.borrow().as_ref().unwrap().iter() - .map(|s| s.stylesheet.clone()) - .collect() - } - - pub fn with_style_sheets_in_document<F, T>(&self, mut f: F) -> T - where F: FnMut(&[StylesheetInDocument]) -> T { - self.ensure_stylesheets(); - f(&self.stylesheets.borrow().as_ref().unwrap()) + /// Flushes the stylesheet list, and returns whether any stylesheet changed. + pub fn flush_stylesheets_for_reflow(&self) -> bool { + // NOTE(emilio): The invalidation machinery is used on the replicated + // list on the layout thread. + // + // FIXME(emilio): This really should differentiate between CSSOM changes + // and normal stylesheets additions / removals, because in the last case + // the layout thread already has that information and we could avoid + // dirtying the whole thing. + let mut stylesheets = self.stylesheets.borrow_mut(); + let have_changed = stylesheets.has_changed(); + stylesheets.flush_without_invalidation(); + have_changed } - /// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document - pub fn appropriate_template_contents_owner_document(&self) -> Root<Document> { - self.appropriate_template_contents_owner_document.or_init(|| { - let doctype = if self.is_html_document { - IsHTMLDocument::HTMLDocument - } else { - IsHTMLDocument::NonHTMLDocument - }; - let new_doc = Document::new(self.window(), - HasBrowsingContext::No, - None, - // https://github.com/whatwg/html/issues/2109 - MutableOrigin::new(ImmutableOrigin::new_opaque()), - doctype, - None, - None, - DocumentActivity::Inactive, - DocumentSource::NotFromParser, - DocumentLoader::new(&self.loader()), - None, - None); - new_doc.appropriate_template_contents_owner_document.set(Some(&new_doc)); - new_doc - }) + /// Returns a `Device` suitable for media query evaluation. + /// + /// FIXME(emilio): This really needs to be somehow more in sync with layout. + /// Feels like a hack. + pub fn device(&self) -> Device { + let window_size = self.window().window_size(); + let viewport_size = window_size.initial_viewport; + let device_pixel_ratio = window_size.device_pixel_ratio; + Device::new( + MediaType::screen(), + self.quirks_mode(), + viewport_size, + device_pixel_ratio, + ) + } + + pub fn salvageable(&self) -> bool { + self.salvageable.get() + } + + /// <https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document> + pub fn appropriate_template_contents_owner_document(&self) -> DomRoot<Document> { + self.appropriate_template_contents_owner_document + .or_init(|| { + let doctype = if self.is_html_document { + IsHTMLDocument::HTMLDocument + } else { + IsHTMLDocument::NonHTMLDocument + }; + let new_doc = Document::new( + self.window(), + HasBrowsingContext::No, + None, + // https://github.com/whatwg/html/issues/2109 + MutableOrigin::new(ImmutableOrigin::new_opaque()), + doctype, + None, + None, + DocumentActivity::Inactive, + DocumentSource::NotFromParser, + DocumentLoader::new(&self.loader()), + None, + None, + Default::default(), + ); + new_doc + .appropriate_template_contents_owner_document + .set(Some(&new_doc)); + new_doc + }) } - pub fn get_element_by_id(&self, id: &Atom) -> Option<Root<Element>> { - self.id_map.borrow().get(&id).map(|ref elements| Root::from_ref(&*(*elements)[0])) + pub fn get_element_by_id(&self, id: &Atom) -> Option<DomRoot<Element>> { + self.id_map + .borrow() + .get(&id) + .map(|ref elements| DomRoot::from_ref(&*(*elements)[0])) } pub fn ensure_pending_restyle(&self, el: &Element) -> RefMut<PendingRestyle> { let map = self.pending_restyles.borrow_mut(); - RefMut::map(map, |m| m.entry(JS::from_ref(el)).or_insert_with(PendingRestyle::new)) + RefMut::map(map, |m| { + m.entry(Dom::from_ref(el)) + .or_insert_with(PendingRestyle::new) + }) } pub fn element_state_will_change(&self, el: &Element) { let mut entry = self.ensure_pending_restyle(el); if entry.snapshot.is_none() { - entry.snapshot = Some(Snapshot::new(el.html_element_in_html_document())); + entry.snapshot = Some(Snapshot::new()); } - let mut snapshot = entry.snapshot.as_mut().unwrap(); + let snapshot = entry.snapshot.as_mut().unwrap(); if snapshot.state.is_none() { snapshot.state = Some(el.state()); } @@ -2351,25 +3545,40 @@ impl Document { // could in theory do it in the DOM I think. let mut entry = self.ensure_pending_restyle(el); if entry.snapshot.is_none() { - entry.snapshot = Some(Snapshot::new(el.html_element_in_html_document())); + entry.snapshot = Some(Snapshot::new()); } if attr.local_name() == &local_name!("style") { - entry.hint |= RESTYLE_STYLE_ATTRIBUTE; + entry.hint.insert(RestyleHint::RESTYLE_STYLE_ATTRIBUTE); } - // FIXME(emilio): This should become something like - // element.is_attribute_mapped(attr.local_name()). - if attr.local_name() == &local_name!("width") || - attr.local_name() == &local_name!("height") { - entry.hint |= RESTYLE_SELF; + if vtable_for(el.upcast()).attribute_affects_presentational_hints(attr) { + entry.hint.insert(RestyleHint::RESTYLE_SELF); } - let mut snapshot = entry.snapshot.as_mut().unwrap(); + let snapshot = entry.snapshot.as_mut().unwrap(); + if attr.local_name() == &local_name!("id") { + if snapshot.id_changed { + return; + } + snapshot.id_changed = true; + } else if attr.local_name() == &local_name!("class") { + if snapshot.class_changed { + return; + } + snapshot.class_changed = true; + } else { + snapshot.other_attributes_changed = true; + } + let local_name = style::LocalName::cast(attr.local_name()); + if !snapshot.changed_attrs.contains(local_name) { + snapshot.changed_attrs.push(local_name.clone()); + } if snapshot.attrs.is_none() { - let attrs = el.attrs() - .iter() - .map(|attr| (attr.identifier().clone(), attr.value().clone())) - .collect(); + let attrs = el + .attrs() + .iter() + .map(|attr| (attr.identifier().clone(), attr.value().clone())) + .collect(); snapshot.attrs = Some(attrs); } } @@ -2394,19 +3603,32 @@ impl Document { element.set_target_state(true); } - self.window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::ElementStateChanged); + self.window + .reflow(ReflowGoal::Full, ReflowReason::ElementStateChanged); } pub fn incr_ignore_destructive_writes_counter(&self) { - self.ignore_destructive_writes_counter.set( - self.ignore_destructive_writes_counter.get() + 1); + self.ignore_destructive_writes_counter + .set(self.ignore_destructive_writes_counter.get() + 1); } pub fn decr_ignore_destructive_writes_counter(&self) { - self.ignore_destructive_writes_counter.set( - self.ignore_destructive_writes_counter.get() - 1); + self.ignore_destructive_writes_counter + .set(self.ignore_destructive_writes_counter.get() - 1); + } + + pub fn is_prompting_or_unloading(&self) -> bool { + self.ignore_opens_during_unload_counter.get() > 0 + } + + fn incr_ignore_opens_during_unload_counter(&self) { + self.ignore_opens_during_unload_counter + .set(self.ignore_opens_during_unload_counter.get() + 1); + } + + fn decr_ignore_opens_during_unload_counter(&self) { + self.ignore_opens_during_unload_counter + .set(self.ignore_opens_during_unload_counter.get() - 1); } /// Whether we've seen so many spurious animation frames (i.e. animation frames that didn't @@ -2416,10 +3638,11 @@ impl Document { } // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen - #[allow(unrooted_must_root)] pub fn enter_fullscreen(&self, pending: &Element) -> Rc<Promise> { // Step 1 - let promise = Promise::new(self.global().r()); + let in_realm_proof = AlreadyInRealm::assert(&self.global()); + let promise = + Promise::new_in_current_realm(&self.global(), InRealm::Already(&in_realm_proof)); let mut error = false; // Step 4 @@ -2429,12 +3652,12 @@ impl Document { if pending.local_name().as_ref() != "math" { error = true; } - } + }, ns!(svg) => { if pending.local_name().as_ref() != "svg" { error = true; } - } + }, ns!(html) => (), _ => error = true, } @@ -2442,23 +3665,40 @@ impl Document { if !pending.fullscreen_element_ready_check() { error = true; } - // TODO fullscreen is supported - // TODO This algorithm is allowed to request fullscreen. + + if pref!(dom.fullscreen.test) { + // For reftests we just take over the current window, + // and don't try to really enter fullscreen. + info!("Tests don't really enter fullscreen."); + } else { + // TODO fullscreen is supported + // TODO This algorithm is allowed to request fullscreen. + warn!("Fullscreen not supported yet"); + } // Step 5 Parallel start let window = self.window(); // Step 6 if !error { - let event = ConstellationMsg::SetFullscreenState(true); - window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap(); + let event = EmbedderMsg::SetFullscreenState(true); + self.send_to_embedder(event); } + let pipeline_id = self.window().pipeline_id(); + // Step 7 let trusted_pending = Trusted::new(pending); let trusted_promise = TrustedPromise::new(promise.clone()); let handler = ElementPerformFullscreenEnter::new(trusted_pending, trusted_promise, error); - let script_msg = CommonScriptMsg::RunnableMsg(ScriptThreadEventCategory::EnterFullscreen, handler); + // NOTE: This steps should be running in parallel + // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen + let script_msg = CommonScriptMsg::Task( + ScriptThreadEventCategory::EnterFullscreen, + handler, + Some(pipeline_id), + TaskSourceName::DOMManipulation, + ); let msg = MainThreadScriptMsg::Common(script_msg); window.main_thread_script_chan().send(msg).unwrap(); @@ -2466,15 +3706,15 @@ impl Document { } // https://fullscreen.spec.whatwg.org/#exit-fullscreen - #[allow(unrooted_must_root)] pub fn exit_fullscreen(&self) -> Rc<Promise> { let global = self.global(); // Step 1 - let promise = Promise::new(global.r()); + let in_realm_proof = AlreadyInRealm::assert(&global); + let promise = Promise::new_in_current_realm(&global, InRealm::Already(&in_realm_proof)); // Step 2 if self.fullscreen_element.get().is_none() { - promise.reject_error(global.get_cx(), Error::Type(String::from("fullscreen is null"))); - return promise + promise.reject_error(Error::Type(String::from("fullscreen is null"))); + return promise; } // TODO Step 3-6 let element = self.fullscreen_element.get().unwrap(); @@ -2483,14 +3723,22 @@ impl Document { let window = self.window(); // Step 8 - let event = ConstellationMsg::SetFullscreenState(false); - window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap(); + let event = EmbedderMsg::SetFullscreenState(false); + self.send_to_embedder(event); // Step 9 - let trusted_element = Trusted::new(element.r()); + let trusted_element = Trusted::new(&*element); let trusted_promise = TrustedPromise::new(promise.clone()); let handler = ElementPerformFullscreenExit::new(trusted_element, trusted_promise); - let script_msg = CommonScriptMsg::RunnableMsg(ScriptThreadEventCategory::ExitFullscreen, handler); + let pipeline_id = Some(global.pipeline_id()); + // NOTE: This steps should be running in parallel + // https://fullscreen.spec.whatwg.org/#exit-fullscreen + let script_msg = CommonScriptMsg::Task( + ScriptThreadEventCategory::ExitFullscreen, + handler, + pipeline_id, + TaskSourceName::DOMManipulation, + ); let msg = MainThreadScriptMsg::Common(script_msg); window.main_thread_script_chan().send(msg).unwrap(); @@ -2513,9 +3761,11 @@ impl Document { true } else { // Step 3 - window.GetFrameElement().map_or(false, |el| el.has_attribute(&local_name!("allowfullscreen"))) + window.GetFrameElement().map_or(false, |el| { + el.has_attribute(&local_name!("allowfullscreen")) + }) } - } + }, } } @@ -2523,14 +3773,233 @@ impl Document { let map = self.form_id_listener_map.borrow(); if let Some(listeners) = map.get(id) { for listener in listeners { - listener.r().as_maybe_form_control() - .expect("Element must be a form control") - .reset_form_owner(); + listener + .as_maybe_form_control() + .expect("Element must be a form control") + .reset_form_owner(); } } } -} + pub fn register_shadow_root(&self, shadow_root: &ShadowRoot) { + self.shadow_roots + .borrow_mut() + .insert(Dom::from_ref(shadow_root)); + self.invalidate_shadow_roots_stylesheets(); + } + + pub fn unregister_shadow_root(&self, shadow_root: &ShadowRoot) { + let mut shadow_roots = self.shadow_roots.borrow_mut(); + shadow_roots.remove(&Dom::from_ref(shadow_root)); + } + + pub fn invalidate_shadow_roots_stylesheets(&self) { + self.shadow_roots_styles_changed.set(true); + } + + pub fn shadow_roots_styles_changed(&self) -> bool { + self.shadow_roots_styles_changed.get() + } + + pub fn flush_shadow_roots_stylesheets(&self) { + if !self.shadow_roots_styles_changed.get() { + return; + } + self.shadow_roots_styles_changed.set(false); + } + + pub fn stylesheet_count(&self) -> usize { + self.stylesheets.borrow().len() + } + + pub fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> { + let stylesheets = self.stylesheets.borrow(); + + stylesheets + .get(Origin::Author, index) + .and_then(|s| s.owner.upcast::<Node>().get_cssom_stylesheet()) + } + + /// Add a stylesheet owned by `owner` to the list of document sheets, in the + /// correct tree position. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn add_stylesheet(&self, owner: &Element, sheet: Arc<Stylesheet>) { + let stylesheets = &mut *self.stylesheets.borrow_mut(); + let insertion_point = stylesheets + .iter() + .map(|(sheet, _origin)| sheet) + .find(|sheet_in_doc| { + owner + .upcast::<Node>() + .is_before(sheet_in_doc.owner.upcast()) + }) + .cloned(); + + match self.window.layout_chan() { + Some(chan) => chan + .send(Msg::AddStylesheet( + sheet.clone(), + insertion_point.as_ref().map(|s| s.sheet.clone()), + )) + .unwrap(), + None => return warn!("Layout channel unavailable"), + } + + DocumentOrShadowRoot::add_stylesheet( + owner, + StylesheetSetRef::Document(stylesheets), + sheet, + insertion_point, + self.style_shared_lock(), + ); + } + + /// Remove a stylesheet owned by `owner` from the list of document sheets. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn remove_stylesheet(&self, owner: &Element, s: &Arc<Stylesheet>) { + match self.window.layout_chan() { + Some(chan) => chan.send(Msg::RemoveStylesheet(s.clone())).unwrap(), + None => return warn!("Layout channel unavailable"), + } + + DocumentOrShadowRoot::remove_stylesheet( + owner, + s, + StylesheetSetRef::Document(&mut *self.stylesheets.borrow_mut()), + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:determine-the-value-of-a-named-property + // Support method for steps 1-3: + // Count if there are 0, 1, or >1 elements that match the name. + // (This takes the filter as a method so the window named getter can use it too) + fn look_up_named_elements( + &self, + name: &Atom, + nameditem_filter: fn(&Node, &Atom) -> bool, + ) -> ElementLookupResult { + // We might match because of either id==name or name==name, so there + // are two sets of nodes to look through, but we don't need a + // full tree traversal. + let id_map = self.id_map.borrow(); + let name_map = self.name_map.borrow(); + let id_vec = id_map.get(&name); + let name_vec = name_map.get(&name); + + // If nothing can possibly have the name, exit fast + if id_vec.is_none() && name_vec.is_none() { + return ElementLookupResult::None; + } + + let one_from_id_map = if let Some(id_vec) = id_vec { + let mut elements = id_vec + .iter() + .filter(|n| nameditem_filter((***n).upcast::<Node>(), &name)) + .peekable(); + if let Some(first) = elements.next() { + if elements.peek().is_none() { + Some(first) + } else { + return ElementLookupResult::Many; + } + } else { + None + } + } else { + None + }; + + let one_from_name_map = if let Some(name_vec) = name_vec { + let mut elements = name_vec + .iter() + .filter(|n| nameditem_filter((***n).upcast::<Node>(), &name)) + .peekable(); + if let Some(first) = elements.next() { + if elements.peek().is_none() { + Some(first) + } else { + return ElementLookupResult::Many; + } + } else { + None + } + } else { + None + }; + + // We now have two elements, or one element, or the same + // element twice, or no elements. + match (one_from_id_map, one_from_name_map) { + (Some(one), None) | (None, Some(one)) => { + ElementLookupResult::One(DomRoot::from_ref(&one)) + }, + (Some(one), Some(other)) => { + if one == other { + ElementLookupResult::One(DomRoot::from_ref(&one)) + } else { + ElementLookupResult::Many + } + }, + (None, None) => ElementLookupResult::None, + } + } + + #[allow(unrooted_must_root)] + pub fn drain_pending_restyles(&self) -> Vec<(TrustedNodeAddress, PendingRestyle)> { + self.pending_restyles + .borrow_mut() + .drain() + .filter_map(|(elem, restyle)| { + let node = elem.upcast::<Node>(); + if !node.get_flag(NodeFlags::IS_CONNECTED) { + return None; + } + node.note_dirty_descendants(); + Some((node.to_trusted_node_address(), restyle)) + }) + .collect() + } + + pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) { + self.animation_timeline.borrow_mut().advance_specific(delta); + let current_timeline_value = self.current_animation_timeline_value(); + self.animations + .borrow() + .update_for_new_timeline_value(&self.window, current_timeline_value); + } + + pub(crate) fn update_animation_timeline(&self) { + // Only update the time if it isn't being managed by a test. + if !pref!(layout.animations.test.enabled) { + self.animation_timeline.borrow_mut().update(); + } + + // We still want to update the animations, because our timeline + // value might have been advanced previously via the TestBinding. + let current_timeline_value = self.current_animation_timeline_value(); + self.animations + .borrow() + .update_for_new_timeline_value(&self.window, current_timeline_value); + } + + pub(crate) fn current_animation_timeline_value(&self) -> f64 { + self.animation_timeline.borrow().current_value() + } + + pub(crate) fn animations(&self) -> Ref<Animations> { + self.animations.borrow() + } + + pub(crate) fn update_animations_post_reflow(&self) { + self.animations + .borrow() + .do_post_reflow_update(&self.window, self.current_animation_timeline_value()); + } + + pub(crate) fn cancel_animations_for_node(&self, node: &Node) { + self.animations.borrow().cancel_animations_for_node(node); + } +} impl Element { fn click_event_filter_by_disabled_state(&self) -> bool { @@ -2548,14 +4017,34 @@ impl Element { } } +impl ProfilerMetadataFactory for Document { + fn new_metadata(&self) -> Option<TimerMetadata> { + Some(TimerMetadata { + url: String::from(self.url().as_str()), + iframe: TimerMetadataFrameType::RootWindow, + incremental: TimerMetadataReflowType::Incremental, + }) + } +} + impl DocumentMethods for Document { + // https://w3c.github.io/editing/ActiveDocuments/execCommand.html#querycommandsupported() + fn QueryCommandSupported(&self, _command: DOMString) -> bool { + false + } + // https://drafts.csswg.org/cssom/#dom-document-stylesheets - fn StyleSheets(&self) -> Root<StyleSheetList> { - self.stylesheet_list.or_init(|| StyleSheetList::new(&self.window, JS::from_ref(&self))) + fn StyleSheets(&self) -> DomRoot<StyleSheetList> { + self.stylesheet_list.or_init(|| { + StyleSheetList::new( + &self.window, + StyleSheetListOwner::Document(Dom::from_ref(self)), + ) + }) } // https://dom.spec.whatwg.org/#dom-document-implementation - fn Implementation(&self) -> Root<DOMImplementation> { + fn Implementation(&self) -> DomRoot<DOMImplementation> { self.implementation.or_init(|| DOMImplementation::new(self)) } @@ -2565,16 +4054,12 @@ impl DocumentMethods for Document { } // https://html.spec.whatwg.org/multipage/#dom-document-activeelement - fn GetActiveElement(&self) -> Option<Root<Element>> { - // TODO: Step 2. - - match self.get_focused_element() { - Some(element) => Some(element), // Step 3. and 4. - None => match self.GetBody() { // Step 5. - Some(body) => Some(Root::upcast(body)), - None => self.GetDocumentElement(), - }, - } + fn GetActiveElement(&self) -> Option<DomRoot<Element>> { + self.document_or_shadow_root.get_active_element( + self.get_focused_element(), + self.GetBody(), + self.GetDocumentElement(), + ) } // https://html.spec.whatwg.org/multipage/#dom-document-hasfocus @@ -2622,7 +4107,8 @@ impl DocumentMethods for Document { }; // Step 5 - let host = match get_registrable_domain_suffix_of_or_is_equal_to(&*value, effective_domain) { + let host = match get_registrable_domain_suffix_of_or_is_equal_to(&*value, effective_domain) + { None => return Err(Error::Security), Some(host) => host, }; @@ -2637,7 +4123,7 @@ impl DocumentMethods for Document { fn Referrer(&self) -> DOMString { match self.referrer { Some(ref referrer) => DOMString::from(referrer.to_string()), - None => DOMString::new() + None => DOMString::new(), } } @@ -2656,34 +4142,7 @@ impl DocumentMethods for Document { // https://dom.spec.whatwg.org/#dom-document-characterset fn CharacterSet(&self) -> DOMString { - DOMString::from(match self.encoding.get().name() { - "utf-8" => "UTF-8", - "ibm866" => "IBM866", - "iso-8859-2" => "ISO-8859-2", - "iso-8859-3" => "ISO-8859-3", - "iso-8859-4" => "ISO-8859-4", - "iso-8859-5" => "ISO-8859-5", - "iso-8859-6" => "ISO-8859-6", - "iso-8859-7" => "ISO-8859-7", - "iso-8859-8" => "ISO-8859-8", - "iso-8859-8-i" => "ISO-8859-8-I", - "iso-8859-10" => "ISO-8859-10", - "iso-8859-13" => "ISO-8859-13", - "iso-8859-14" => "ISO-8859-14", - "iso-8859-15" => "ISO-8859-15", - "iso-8859-16" => "ISO-8859-16", - "koi8-r" => "KOI8-R", - "koi8-u" => "KOI8-U", - "gbk" => "GBK", - "big5" => "Big5", - "euc-jp" => "EUC-JP", - "iso-2022-jp" => "ISO-2022-JP", - "shift_jis" => "Shift_JIS", - "euc-kr" => "EUC-KR", - "utf-16be" => "UTF-16BE", - "utf-16le" => "UTF-16LE", - name => name - }) + DOMString::from(self.encoding.get().name()) } // https://dom.spec.whatwg.org/#dom-document-charset @@ -2698,75 +4157,80 @@ impl DocumentMethods for Document { // https://dom.spec.whatwg.org/#dom-document-content_type fn ContentType(&self) -> DOMString { - self.content_type.clone() + DOMString::from(self.content_type.to_string()) } // https://dom.spec.whatwg.org/#dom-document-doctype - fn GetDoctype(&self) -> Option<Root<DocumentType>> { - self.upcast::<Node>().children().filter_map(Root::downcast).next() + fn GetDoctype(&self) -> Option<DomRoot<DocumentType>> { + self.upcast::<Node>() + .children() + .filter_map(DomRoot::downcast) + .next() } // https://dom.spec.whatwg.org/#dom-document-documentelement - fn GetDocumentElement(&self) -> Option<Root<Element>> { + fn GetDocumentElement(&self) -> Option<DomRoot<Element>> { self.upcast::<Node>().child_elements().next() } // https://dom.spec.whatwg.org/#dom-document-getelementsbytagname - fn GetElementsByTagName(&self, qualified_name: DOMString) -> Root<HTMLCollection> { + fn GetElementsByTagName(&self, qualified_name: DOMString) -> DomRoot<HTMLCollection> { let qualified_name = LocalName::from(&*qualified_name); match self.tag_map.borrow_mut().entry(qualified_name.clone()) { - Occupied(entry) => Root::from_ref(entry.get()), + Occupied(entry) => DomRoot::from_ref(entry.get()), Vacant(entry) => { - let result = HTMLCollection::by_qualified_name( - &self.window, self.upcast(), qualified_name); - entry.insert(JS::from_ref(&*result)); + let result = + HTMLCollection::by_qualified_name(&self.window, self.upcast(), qualified_name); + entry.insert(Dom::from_ref(&*result)); result - } + }, } } // https://dom.spec.whatwg.org/#dom-document-getelementsbytagnamens - fn GetElementsByTagNameNS(&self, - maybe_ns: Option<DOMString>, - tag_name: DOMString) - -> Root<HTMLCollection> { + fn GetElementsByTagNameNS( + &self, + maybe_ns: Option<DOMString>, + tag_name: DOMString, + ) -> DomRoot<HTMLCollection> { let ns = namespace_from_domstring(maybe_ns); let local = LocalName::from(tag_name); - let qname = QualName::new(ns, local); + let qname = QualName::new(None, ns, local); match self.tagns_map.borrow_mut().entry(qname.clone()) { - Occupied(entry) => Root::from_ref(entry.get()), + Occupied(entry) => DomRoot::from_ref(entry.get()), Vacant(entry) => { let result = HTMLCollection::by_qual_tag_name(&self.window, self.upcast(), qname); - entry.insert(JS::from_ref(&*result)); + entry.insert(Dom::from_ref(&*result)); result - } + }, } } // https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname - fn GetElementsByClassName(&self, classes: DOMString) -> Root<HTMLCollection> { - let class_atoms: Vec<Atom> = split_html_space_chars(&classes) - .map(Atom::from) - .collect(); + fn GetElementsByClassName(&self, classes: DOMString) -> DomRoot<HTMLCollection> { + let class_atoms: Vec<Atom> = split_html_space_chars(&classes).map(Atom::from).collect(); match self.classes_map.borrow_mut().entry(class_atoms.clone()) { - Occupied(entry) => Root::from_ref(entry.get()), + Occupied(entry) => DomRoot::from_ref(entry.get()), Vacant(entry) => { - let result = HTMLCollection::by_atomic_class_name(&self.window, - self.upcast(), - class_atoms); - entry.insert(JS::from_ref(&*result)); + let result = + HTMLCollection::by_atomic_class_name(&self.window, self.upcast(), class_atoms); + entry.insert(Dom::from_ref(&*result)); result - } + }, } } // https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid - fn GetElementById(&self, id: DOMString) -> Option<Root<Element>> { + fn GetElementById(&self, id: DOMString) -> Option<DomRoot<Element>> { self.get_element_by_id(&Atom::from(id)) } // https://dom.spec.whatwg.org/#dom-document-createelement - fn CreateElement(&self, mut local_name: DOMString) -> Fallible<Root<Element>> { + fn CreateElement( + &self, + mut local_name: DOMString, + options: StringOrElementCreationOptions, + ) -> Fallible<DomRoot<Element>> { if xml_name_type(&local_name) == InvalidXMLName { debug!("Not a valid element name"); return Err(Error::InvalidCharacter); @@ -2775,29 +4239,58 @@ impl DocumentMethods for Document { local_name.make_ascii_lowercase(); } - let ns = if self.is_html_document || self.content_type == "application/xhtml+xml" { + let is_xhtml = self.content_type.type_() == mime::APPLICATION && + self.content_type.subtype().as_str() == "xhtml" && + self.content_type.suffix() == Some(mime::XML); + + let ns = if self.is_html_document || is_xhtml { ns!(html) } else { ns!() }; - let name = QualName::new(ns, LocalName::from(local_name)); - Ok(Element::create(name, None, self, ElementCreator::ScriptCreated)) + let name = QualName::new(None, ns, LocalName::from(local_name)); + let is = match options { + StringOrElementCreationOptions::String(_) => None, + StringOrElementCreationOptions::ElementCreationOptions(options) => { + options.is.as_ref().map(|is| LocalName::from(&**is)) + }, + }; + Ok(Element::create( + name, + is, + self, + ElementCreator::ScriptCreated, + CustomElementCreationMode::Synchronous, + )) } // https://dom.spec.whatwg.org/#dom-document-createelementns - fn CreateElementNS(&self, - namespace: Option<DOMString>, - qualified_name: DOMString) - -> Fallible<Root<Element>> { - let (namespace, prefix, local_name) = try!(validate_and_extract(namespace, - &qualified_name)); - let name = QualName::new(namespace, local_name); - Ok(Element::create(name, prefix, self, ElementCreator::ScriptCreated)) + fn CreateElementNS( + &self, + namespace: Option<DOMString>, + qualified_name: DOMString, + options: StringOrElementCreationOptions, + ) -> Fallible<DomRoot<Element>> { + let (namespace, prefix, local_name) = validate_and_extract(namespace, &qualified_name)?; + let name = QualName::new(prefix, namespace, local_name); + let is = match options { + StringOrElementCreationOptions::String(_) => None, + StringOrElementCreationOptions::ElementCreationOptions(options) => { + options.is.as_ref().map(|is| LocalName::from(&**is)) + }, + }; + Ok(Element::create( + name, + is, + self, + ElementCreator::ScriptCreated, + CustomElementCreationMode::Synchronous, + )) } // https://dom.spec.whatwg.org/#dom-document-createattribute - fn CreateAttribute(&self, mut local_name: DOMString) -> Fallible<Root<Attr>> { + fn CreateAttribute(&self, mut local_name: DOMString) -> Fallible<DomRoot<Attr>> { if xml_name_type(&local_name) == InvalidXMLName { debug!("Not a valid element name"); return Err(Error::InvalidCharacter); @@ -2808,47 +4301,74 @@ impl DocumentMethods for Document { let name = LocalName::from(local_name); let value = AttrValue::String("".to_owned()); - Ok(Attr::new(&self.window, name.clone(), value, name, ns!(), None, None)) + Ok(Attr::new( + &self, + name.clone(), + value, + name, + ns!(), + None, + None, + )) } // https://dom.spec.whatwg.org/#dom-document-createattributens - fn CreateAttributeNS(&self, - namespace: Option<DOMString>, - qualified_name: DOMString) - -> Fallible<Root<Attr>> { - let (namespace, prefix, local_name) = try!(validate_and_extract(namespace, - &qualified_name)); + fn CreateAttributeNS( + &self, + namespace: Option<DOMString>, + qualified_name: DOMString, + ) -> Fallible<DomRoot<Attr>> { + let (namespace, prefix, local_name) = validate_and_extract(namespace, &qualified_name)?; let value = AttrValue::String("".to_owned()); let qualified_name = LocalName::from(qualified_name); - Ok(Attr::new(&self.window, - local_name, - value, - qualified_name, - namespace, - prefix, - None)) + Ok(Attr::new( + &self, + local_name, + value, + qualified_name, + namespace, + prefix, + None, + )) } // https://dom.spec.whatwg.org/#dom-document-createdocumentfragment - fn CreateDocumentFragment(&self) -> Root<DocumentFragment> { + fn CreateDocumentFragment(&self) -> DomRoot<DocumentFragment> { DocumentFragment::new(self) } // https://dom.spec.whatwg.org/#dom-document-createtextnode - fn CreateTextNode(&self, data: DOMString) -> Root<Text> { + fn CreateTextNode(&self, data: DOMString) -> DomRoot<Text> { Text::new(data, self) } + // https://dom.spec.whatwg.org/#dom-document-createcdatasection + fn CreateCDATASection(&self, data: DOMString) -> Fallible<DomRoot<CDATASection>> { + // Step 1 + if self.is_html_document { + return Err(Error::NotSupported); + } + + // Step 2 + if data.contains("]]>") { + return Err(Error::InvalidCharacter); + } + + // Step 3 + Ok(CDATASection::new(data, self)) + } + // https://dom.spec.whatwg.org/#dom-document-createcomment - fn CreateComment(&self, data: DOMString) -> Root<Comment> { + fn CreateComment(&self, data: DOMString) -> DomRoot<Comment> { Comment::new(data, self) } // https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction - fn CreateProcessingInstruction(&self, - target: DOMString, - data: DOMString) - -> Fallible<Root<ProcessingInstruction>> { + fn CreateProcessingInstruction( + &self, + target: DOMString, + data: DOMString, + ) -> Fallible<DomRoot<ProcessingInstruction>> { // Step 1. if xml_name_type(&target) == InvalidXMLName { return Err(Error::InvalidCharacter); @@ -2864,9 +4384,9 @@ impl DocumentMethods for Document { } // https://dom.spec.whatwg.org/#dom-document-importnode - fn ImportNode(&self, node: &Node, deep: bool) -> Fallible<Root<Node>> { + fn ImportNode(&self, node: &Node, deep: bool) -> Fallible<DomRoot<Node>> { // Step 1. - if node.is::<Document>() { + if node.is::<Document>() || node.is::<ShadowRoot>() { return Err(Error::NotSupported); } @@ -2881,66 +4401,67 @@ impl DocumentMethods for Document { } // https://dom.spec.whatwg.org/#dom-document-adoptnode - fn AdoptNode(&self, node: &Node) -> Fallible<Root<Node>> { + fn AdoptNode(&self, node: &Node) -> Fallible<DomRoot<Node>> { // Step 1. if node.is::<Document>() { return Err(Error::NotSupported); } // Step 2. - Node::adopt(node, self); + if node.is::<ShadowRoot>() { + return Err(Error::HierarchyRequest); + } // Step 3. - Ok(Root::from_ref(node)) + Node::adopt(node, self); + + // Step 4. + Ok(DomRoot::from_ref(node)) } // https://dom.spec.whatwg.org/#dom-document-createevent - fn CreateEvent(&self, mut interface: DOMString) -> Fallible<Root<Event>> { + fn CreateEvent(&self, mut interface: DOMString) -> Fallible<DomRoot<Event>> { interface.make_ascii_lowercase(); match &*interface { - "beforeunloadevent" => - Ok(Root::upcast(BeforeUnloadEvent::new_uninitialized(&self.window))), - "closeevent" => - Ok(Root::upcast(CloseEvent::new_uninitialized(self.window.upcast()))), - "customevent" => - Ok(Root::upcast(CustomEvent::new_uninitialized(self.window.upcast()))), - "errorevent" => - Ok(Root::upcast(ErrorEvent::new_uninitialized(self.window.upcast()))), - "events" | "event" | "htmlevents" | "svgevents" => - Ok(Event::new_uninitialized(&self.window.upcast())), - "focusevent" => - Ok(Root::upcast(FocusEvent::new_uninitialized(&self.window))), - "hashchangeevent" => - Ok(Root::upcast(HashChangeEvent::new_uninitialized(&self.window))), - "keyboardevent" => - Ok(Root::upcast(KeyboardEvent::new_uninitialized(&self.window))), - "messageevent" => - Ok(Root::upcast(MessageEvent::new_uninitialized(self.window.upcast()))), - "mouseevent" | "mouseevents" => - Ok(Root::upcast(MouseEvent::new_uninitialized(&self.window))), - "pagetransitionevent" => - Ok(Root::upcast(PageTransitionEvent::new_uninitialized(&self.window))), - "popstateevent" => - Ok(Root::upcast(PopStateEvent::new_uninitialized(&self.window))), - "progressevent" => - Ok(Root::upcast(ProgressEvent::new_uninitialized(self.window.upcast()))), - "storageevent" => { - Ok(Root::upcast(StorageEvent::new_uninitialized(&self.window, "".into()))) + "beforeunloadevent" => Ok(DomRoot::upcast(BeforeUnloadEvent::new_uninitialized( + &self.window, + ))), + "compositionevent" | "textevent" => Ok(DomRoot::upcast( + CompositionEvent::new_uninitialized(&self.window), + )), + "customevent" => Ok(DomRoot::upcast(CustomEvent::new_uninitialized( + self.window.upcast(), + ))), + // FIXME(#25136): devicemotionevent, deviceorientationevent + // FIXME(#7529): dragevent + "events" | "event" | "htmlevents" | "svgevents" => { + Ok(Event::new_uninitialized(&self.window.upcast())) }, - "touchevent" => - Ok(Root::upcast( - TouchEvent::new_uninitialized(&self.window, - &TouchList::new(&self.window, &[]), - &TouchList::new(&self.window, &[]), - &TouchList::new(&self.window, &[]), - ) - )), - "uievent" | "uievents" => - Ok(Root::upcast(UIEvent::new_uninitialized(&self.window))), - "webglcontextevent" => - Ok(Root::upcast(WebGLContextEvent::new_uninitialized(&self.window))), - _ => - Err(Error::NotSupported), + "focusevent" => Ok(DomRoot::upcast(FocusEvent::new_uninitialized(&self.window))), + "hashchangeevent" => Ok(DomRoot::upcast(HashChangeEvent::new_uninitialized( + &self.window, + ))), + "keyboardevent" => Ok(DomRoot::upcast(KeyboardEvent::new_uninitialized( + &self.window, + ))), + "messageevent" => Ok(DomRoot::upcast(MessageEvent::new_uninitialized( + self.window.upcast(), + ))), + "mouseevent" | "mouseevents" => { + Ok(DomRoot::upcast(MouseEvent::new_uninitialized(&self.window))) + }, + "storageevent" => Ok(DomRoot::upcast(StorageEvent::new_uninitialized( + &self.window, + "".into(), + ))), + "touchevent" => Ok(DomRoot::upcast(TouchEvent::new_uninitialized( + &self.window, + &TouchList::new(&self.window, &[]), + &TouchList::new(&self.window, &[]), + &TouchList::new(&self.window, &[]), + ))), + "uievent" | "uievents" => Ok(DomRoot::upcast(UIEvent::new_uninitialized(&self.window))), + _ => Err(Error::NotSupported), } } @@ -2948,58 +4469,37 @@ impl DocumentMethods for Document { fn LastModified(&self) -> DOMString { match self.last_modified { Some(ref t) => DOMString::from(t.clone()), - None => DOMString::from(time::now().strftime("%m/%d/%Y %H:%M:%S").unwrap().to_string()), + None => DOMString::from( + time::now() + .strftime("%m/%d/%Y %H:%M:%S") + .unwrap() + .to_string(), + ), } } // https://dom.spec.whatwg.org/#dom-document-createrange - fn CreateRange(&self) -> Root<Range> { + fn CreateRange(&self) -> DomRoot<Range> { Range::new_with_doc(self) } // https://dom.spec.whatwg.org/#dom-document-createnodeiteratorroot-whattoshow-filter - fn CreateNodeIterator(&self, - root: &Node, - what_to_show: u32, - filter: Option<Rc<NodeFilter>>) - -> Root<NodeIterator> { + fn CreateNodeIterator( + &self, + root: &Node, + what_to_show: u32, + filter: Option<Rc<NodeFilter>>, + ) -> DomRoot<NodeIterator> { NodeIterator::new(self, root, what_to_show, filter) } - // https://w3c.github.io/touch-events/#idl-def-Document - fn CreateTouch(&self, - window: &Window, - target: &EventTarget, - identifier: i32, - page_x: Finite<f64>, - page_y: Finite<f64>, - screen_x: Finite<f64>, - screen_y: Finite<f64>) - -> Root<Touch> { - let client_x = Finite::wrap(*page_x - window.PageXOffset() as f64); - let client_y = Finite::wrap(*page_y - window.PageYOffset() as f64); - Touch::new(window, - identifier, - target, - screen_x, - screen_y, - client_x, - client_y, - page_x, - page_y) - } - - // https://w3c.github.io/touch-events/#idl-def-document-createtouchlist(touch...) - fn CreateTouchList(&self, touches: &[&Touch]) -> Root<TouchList> { - TouchList::new(&self.window, &touches) - } - // https://dom.spec.whatwg.org/#dom-document-createtreewalker - fn CreateTreeWalker(&self, - root: &Node, - what_to_show: u32, - filter: Option<Rc<NodeFilter>>) - -> Root<TreeWalker> { + fn CreateTreeWalker( + &self, + root: &Node, + what_to_show: u32, + filter: Option<Rc<NodeFilter>>, + ) -> DomRoot<TreeWalker> { TreeWalker::new(self, root, what_to_show, filter) } @@ -3013,11 +4513,11 @@ impl DocumentMethods for Document { .find(|node| { node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title") }) - .map(Root::upcast::<Node>) + .map(DomRoot::upcast::<Node>) } else { // Step 2. root.upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .find(|node| node.is::<HTMLTitleElement>()) } }); @@ -3044,37 +4544,44 @@ impl DocumentMethods for Document { node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title") }); match elem { - Some(elem) => Root::upcast::<Node>(elem), + Some(elem) => DomRoot::upcast::<Node>(elem), None => { - let name = QualName::new(ns!(svg), local_name!("title")); - let elem = Element::create(name, None, self, ElementCreator::ScriptCreated); + let name = QualName::new(None, ns!(svg), local_name!("title")); + let elem = Element::create( + name, + None, + self, + ElementCreator::ScriptCreated, + CustomElementCreationMode::Synchronous, + ); let parent = root.upcast::<Node>(); let child = elem.upcast::<Node>(); - parent.InsertBefore(child, parent.GetFirstChild().r()) - .unwrap() - } + parent + .InsertBefore(child, parent.GetFirstChild().as_deref()) + .unwrap() + }, } } else if root.namespace() == &ns!(html) { - let elem = root.upcast::<Node>() - .traverse_preorder() - .find(|node| node.is::<HTMLTitleElement>()); + let elem = root + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::No) + .find(|node| node.is::<HTMLTitleElement>()); match elem { Some(elem) => elem, - None => { - match self.GetHead() { - Some(head) => { - let name = QualName::new(ns!(html), local_name!("title")); - let elem = Element::create(name, - None, - self, - ElementCreator::ScriptCreated); - head.upcast::<Node>() - .AppendChild(elem.upcast()) - .unwrap() - }, - None => return, - } - } + None => match self.GetHead() { + Some(head) => { + let name = QualName::new(None, ns!(html), local_name!("title")); + let elem = Element::create( + name, + None, + self, + ElementCreator::ScriptCreated, + CustomElementCreationMode::Synchronous, + ); + head.upcast::<Node>().AppendChild(elem.upcast()).unwrap() + }, + None => return, + }, } } else { return; @@ -3084,27 +4591,35 @@ impl DocumentMethods for Document { } // https://html.spec.whatwg.org/multipage/#dom-document-head - fn GetHead(&self) -> Option<Root<HTMLHeadElement>> { - self.get_html_element() - .and_then(|root| root.upcast::<Node>().children().filter_map(Root::downcast).next()) + fn GetHead(&self) -> Option<DomRoot<HTMLHeadElement>> { + self.get_html_element().and_then(|root| { + root.upcast::<Node>() + .children() + .filter_map(DomRoot::downcast) + .next() + }) } // https://html.spec.whatwg.org/multipage/#dom-document-currentscript - fn GetCurrentScript(&self) -> Option<Root<HTMLScriptElement>> { + fn GetCurrentScript(&self) -> Option<DomRoot<HTMLScriptElement>> { self.current_script.get() } // https://html.spec.whatwg.org/multipage/#dom-document-body - fn GetBody(&self) -> Option<Root<HTMLElement>> { + fn GetBody(&self) -> Option<DomRoot<HTMLElement>> { self.get_html_element().and_then(|root| { let node = root.upcast::<Node>(); - node.children().find(|child| { - match child.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFrameSetElement)) => true, - _ => false - } - }).map(|node| Root::downcast(node).unwrap()) + node.children() + .find(|child| match child.type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLBodyElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLFrameSetElement, + )) => true, + _ => false, + }) + .map(|node| DomRoot::downcast(node).unwrap()) }) } @@ -3119,21 +4634,24 @@ impl DocumentMethods for Document { let node = new_body.upcast::<Node>(); match node.type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFrameSetElement)) => {} + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLFrameSetElement, + )) => {}, _ => return Err(Error::HierarchyRequest), } // Step 2. let old_body = self.GetBody(); - if old_body.r() == Some(new_body) { + if old_body.as_deref() == Some(new_body) { return Ok(()); } - match (self.get_html_element(), &old_body) { + match (self.GetDocumentElement(), &old_body) { // Step 3. (Some(ref root), &Some(ref child)) => { let root = root.upcast::<Node>(); - root.ReplaceChild(new_body.upcast(), child.upcast()).unwrap(); + root.ReplaceChild(new_body.upcast(), child.upcast()) + .unwrap(); }, // Step 4. @@ -3143,106 +4661,100 @@ impl DocumentMethods for Document { (Some(ref root), &None) => { let root = root.upcast::<Node>(); root.AppendChild(new_body.upcast()).unwrap(); - } + }, } Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-document-getelementsbyname - fn GetElementsByName(&self, name: DOMString) -> Root<NodeList> { - self.create_node_list(|node| { - let element = match node.downcast::<Element>() { - Some(element) => element, - None => return false, - }; - if element.namespace() != &ns!(html) { - return false; - } - element.get_attribute(&ns!(), &local_name!("name")) - .map_or(false, |attr| &**attr.value() == &*name) - }) + fn GetElementsByName(&self, name: DOMString) -> DomRoot<NodeList> { + NodeList::new_elements_by_name_list(self.window(), self, name) } // https://html.spec.whatwg.org/multipage/#dom-document-images - fn Images(&self) -> Root<HTMLCollection> { + fn Images(&self) -> DomRoot<HTMLCollection> { self.images.or_init(|| { - let filter = box ImagesFilter; + let filter = Box::new(ImagesFilter); HTMLCollection::create(&self.window, self.upcast(), filter) }) } // https://html.spec.whatwg.org/multipage/#dom-document-embeds - fn Embeds(&self) -> Root<HTMLCollection> { + fn Embeds(&self) -> DomRoot<HTMLCollection> { self.embeds.or_init(|| { - let filter = box EmbedsFilter; + let filter = Box::new(EmbedsFilter); HTMLCollection::create(&self.window, self.upcast(), filter) }) } // https://html.spec.whatwg.org/multipage/#dom-document-plugins - fn Plugins(&self) -> Root<HTMLCollection> { + fn Plugins(&self) -> DomRoot<HTMLCollection> { self.Embeds() } // https://html.spec.whatwg.org/multipage/#dom-document-links - fn Links(&self) -> Root<HTMLCollection> { + fn Links(&self) -> DomRoot<HTMLCollection> { self.links.or_init(|| { - let filter = box LinksFilter; + let filter = Box::new(LinksFilter); HTMLCollection::create(&self.window, self.upcast(), filter) }) } // https://html.spec.whatwg.org/multipage/#dom-document-forms - fn Forms(&self) -> Root<HTMLCollection> { + fn Forms(&self) -> DomRoot<HTMLCollection> { self.forms.or_init(|| { - let filter = box FormsFilter; + let filter = Box::new(FormsFilter); HTMLCollection::create(&self.window, self.upcast(), filter) }) } // https://html.spec.whatwg.org/multipage/#dom-document-scripts - fn Scripts(&self) -> Root<HTMLCollection> { + fn Scripts(&self) -> DomRoot<HTMLCollection> { self.scripts.or_init(|| { - let filter = box ScriptsFilter; + let filter = Box::new(ScriptsFilter); HTMLCollection::create(&self.window, self.upcast(), filter) }) } // https://html.spec.whatwg.org/multipage/#dom-document-anchors - fn Anchors(&self) -> Root<HTMLCollection> { + fn Anchors(&self) -> DomRoot<HTMLCollection> { self.anchors.or_init(|| { - let filter = box AnchorsFilter; + let filter = Box::new(AnchorsFilter); HTMLCollection::create(&self.window, self.upcast(), filter) }) } // https://html.spec.whatwg.org/multipage/#dom-document-applets - fn Applets(&self) -> Root<HTMLCollection> { - // FIXME: This should be return OBJECT elements containing applets. - self.applets.or_init(|| { - let filter = box AppletsFilter; - HTMLCollection::create(&self.window, self.upcast(), filter) - }) + fn Applets(&self) -> DomRoot<HTMLCollection> { + self.applets + .or_init(|| HTMLCollection::always_empty(&self.window, self.upcast())) } // https://html.spec.whatwg.org/multipage/#dom-document-location - fn GetLocation(&self) -> Option<Root<Location>> { - self.browsing_context().map(|_| self.location.or_init(|| Location::new(&self.window))) + fn GetLocation(&self) -> Option<DomRoot<Location>> { + if self.is_fully_active() { + Some(self.window.Location()) + } else { + None + } } // https://dom.spec.whatwg.org/#dom-parentnode-children - fn Children(&self) -> Root<HTMLCollection> { + fn Children(&self) -> DomRoot<HTMLCollection> { HTMLCollection::children(&self.window, self.upcast()) } // https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild - fn GetFirstElementChild(&self) -> Option<Root<Element>> { + fn GetFirstElementChild(&self) -> Option<DomRoot<Element>> { self.upcast::<Node>().child_elements().next() } // https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild - fn GetLastElementChild(&self) -> Option<Root<Element>> { - self.upcast::<Node>().rev_children().filter_map(Root::downcast).next() + fn GetLastElementChild(&self) -> Option<DomRoot<Element>> { + self.upcast::<Node>() + .rev_children() + .filter_map(DomRoot::downcast) + .next() } // https://dom.spec.whatwg.org/#dom-parentnode-childelementcount @@ -3260,14 +4772,19 @@ impl DocumentMethods for Document { self.upcast::<Node>().append(nodes) } + // https://dom.spec.whatwg.org/#dom-parentnode-replacechildren + fn ReplaceChildren(&self, nodes: Vec<NodeOrString>) -> ErrorResult { + self.upcast::<Node>().replace_children(nodes) + } + // https://dom.spec.whatwg.org/#dom-parentnode-queryselector - fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> { + fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> { let root = self.upcast::<Node>(); root.query_selector(selectors) } // https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall - fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<Root<NodeList>> { + fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<DomRoot<NodeList>> { let root = self.upcast::<Node>(); root.query_selector_all(selectors) } @@ -3278,9 +4795,9 @@ impl DocumentMethods for Document { } // https://html.spec.whatwg.org/multipage/#dom-document-defaultview - fn GetDefaultView(&self) -> Option<Root<Window>> { + fn GetDefaultView(&self) -> Option<DomRoot<Window>> { if self.has_browsing_context { - Some(Root::from_ref(&*self.window)) + Some(DomRoot::from_ref(&*self.window)) } else { None } @@ -3297,8 +4814,9 @@ impl DocumentMethods for Document { } let url = self.url(); - let (tx, rx) = ipc::channel().unwrap(); - let _ = self.window + let (tx, rx) = profile_ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); + let _ = self + .window .upcast::<GlobalScope>() .resource_threads() .send(GetCookiesForUrl(url, tx, NonHTTP)); @@ -3316,15 +4834,17 @@ impl DocumentMethods for Document { return Err(Error::Security); } - if let Ok(cookie_header) = SetCookie::parse_header(&vec![cookie.to_string().into_bytes()]) { - let cookies = cookie_header.0.into_iter().filter_map(|cookie| { - cookie_rs::Cookie::parse(cookie).ok().map(Serde) - }).collect(); - let _ = self.window - .upcast::<GlobalScope>() - .resource_threads() - .send(SetCookiesForUrl(self.url(), cookies, NonHTTP)); - } + let cookies = if let Some(cookie) = Cookie::parse(cookie.to_string()).ok().map(Serde) { + vec![cookie] + } else { + vec![] + }; + + let _ = self + .window + .upcast::<GlobalScope>() + .resource_threads() + .send(SetCookiesForUrl(self.url(), cookies, NonHTTP)); Ok(()) } @@ -3350,92 +4870,56 @@ impl DocumentMethods for Document { #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:dom-document-nameditem-filter - unsafe fn NamedGetter(&self, _cx: *mut JSContext, name: DOMString) -> Option<NonZero<*mut JSObject>> { - #[derive(JSTraceable, HeapSizeOf)] + fn NamedGetter(&self, _cx: JSContext, name: DOMString) -> Option<NonNull<JSObject>> { + #[derive(JSTraceable, MallocSizeOf)] struct NamedElementFilter { name: Atom, } impl CollectionFilter for NamedElementFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { - filter_by_name(&self.name, elem.upcast()) - } - } - // https://html.spec.whatwg.org/multipage/#dom-document-nameditem-filter - fn filter_by_name(name: &Atom, node: &Node) -> bool { - let html_elem_type = match node.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_, - _ => return false, - }; - let elem = match node.downcast::<Element>() { - Some(elem) => elem, - None => return false, - }; - match html_elem_type { - HTMLElementTypeId::HTMLAppletElement => { - match elem.get_attribute(&ns!(), &local_name!("name")) { - Some(ref attr) if attr.value().as_atom() == name => true, - _ => { - match elem.get_attribute(&ns!(), &local_name!("id")) { - Some(ref attr) => attr.value().as_atom() == name, - None => false, - } - }, - } - }, - HTMLElementTypeId::HTMLFormElement => { - match elem.get_attribute(&ns!(), &local_name!("name")) { - Some(ref attr) => attr.value().as_atom() == name, - None => false, - } - }, - HTMLElementTypeId::HTMLImageElement => { - match elem.get_attribute(&ns!(), &local_name!("name")) { - Some(ref attr) => { - if attr.value().as_atom() == name { - true - } else { - match elem.get_attribute(&ns!(), &local_name!("id")) { - Some(ref attr) => attr.value().as_atom() == name, - None => false, - } - } - }, - None => false, - } - }, - // TODO: Handle <embed>, <iframe> and <object>. - _ => false, + elem.upcast::<Node>().is_document_named_item(&self.name) } } + let name = Atom::from(name); - let root = self.upcast::<Node>(); - { - // Step 1. - let mut elements = root.traverse_preorder() - .filter(|node| filter_by_name(&name, &node)) - .peekable(); - if let Some(first) = elements.next() { - if elements.peek().is_none() { - // TODO: Step 2. - // Step 3. - return Some(NonZero::new(first.reflector().get_jsobject().get())); - } - } else { + + match self.look_up_named_elements(&name, Node::is_document_named_item) { + ElementLookupResult::None => { return None; - } - } - // Step 4. - let filter = NamedElementFilter { - name: name, + }, + ElementLookupResult::One(element) => { + if let Some(nested_proxy) = element + .downcast::<HTMLIFrameElement>() + .and_then(|iframe| iframe.GetContentWindow()) + { + unsafe { + return Some(NonNull::new_unchecked( + nested_proxy.reflector().get_jsobject().get(), + )); + } + } + unsafe { + return Some(NonNull::new_unchecked( + element.reflector().get_jsobject().get(), + )); + } + }, + ElementLookupResult::Many => {}, }; - let collection = HTMLCollection::create(self.window(), root, box filter); - Some(NonZero::new(collection.reflector().get_jsobject().get())) + + // Step 4. + let filter = NamedElementFilter { name: name }; + let collection = HTMLCollection::create(self.window(), self.upcast(), Box::new(filter)); + unsafe { + Some(NonNull::new_unchecked( + collection.reflector().get_jsobject().get(), + )) + } } // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names fn SupportedPropertyNames(&self) -> Vec<DOMString> { - // FIXME: unimplemented (https://github.com/servo/servo/issues/7273) - vec![] + self.supported_property_names_impl(Node::is_document_named_item) } // https://html.spec.whatwg.org/multipage/#dom-document-clear @@ -3457,223 +4941,158 @@ impl DocumentMethods for Document { global_event_handlers!(); // https://html.spec.whatwg.org/multipage/#handler-onreadystatechange - event_handler!(readystatechange, GetOnreadystatechange, SetOnreadystatechange); + event_handler!( + readystatechange, + GetOnreadystatechange, + SetOnreadystatechange + ); - #[allow(unsafe_code)] // https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint - fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<Root<Element>> { - let x = *x as f32; - let y = *y as f32; - let point = &Point2D::new(x, y); - let window = window_from_node(self); - let viewport = window.window_size().unwrap().initial_viewport; - - if self.browsing_context().is_none() { - return None; - } - - if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height { - return None; - } - - match self.window.hit_test_query(*point, false) { - Some(untrusted_node_address) => { - let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) }; - - let node = node::from_untrusted_node_address(js_runtime, untrusted_node_address); - let parent_node = node.GetParentNode().unwrap(); - let element_ref = node.downcast::<Element>().unwrap_or_else(|| { - parent_node.downcast::<Element>().unwrap() - }); - - Some(Root::from_ref(element_ref)) - }, - None => self.GetDocumentElement() - } + fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> { + self.document_or_shadow_root.element_from_point( + x, + y, + self.GetDocumentElement(), + self.has_browsing_context, + ) } - #[allow(unsafe_code)] // https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint - fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<Root<Element>> { - let x = *x as f32; - let y = *y as f32; - let point = &Point2D::new(x, y); - let window = window_from_node(self); - let viewport = window.window_size().unwrap().initial_viewport; - - if self.browsing_context().is_none() { - return vec!(); - } - - // Step 2 - if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height { - return vec!(); - } - - let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) }; - - // Step 1 and Step 3 - let mut elements: Vec<Root<Element>> = self.nodes_from_point(point).iter() - .flat_map(|&untrusted_node_address| { - let node = node::from_untrusted_node_address(js_runtime, untrusted_node_address); - Root::downcast::<Element>(node) - }).collect(); - - // Step 4 - if let Some(root_element) = self.GetDocumentElement() { - if elements.last() != Some(&root_element) { - elements.push(root_element); - } - } - - // Step 5 - elements + fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> { + self.document_or_shadow_root.elements_from_point( + x, + y, + self.GetDocumentElement(), + self.has_browsing_context, + ) } // https://html.spec.whatwg.org/multipage/#dom-document-open - fn Open(&self, type_: DOMString, replace: DOMString) -> Fallible<Root<Document>> { + fn Open( + &self, + _unused1: Option<DOMString>, + _unused2: Option<DOMString>, + ) -> Fallible<DomRoot<Document>> { + // Step 1 if !self.is_html_document() { - // Step 1. return Err(Error::InvalidState); } - // Step 2. - // TODO: handle throw-on-dynamic-markup-insertion counter. - - if !self.is_active() { - // Step 3. - return Ok(Root::from_ref(self)); + // Step 2 + if self.throw_on_dynamic_markup_insertion_counter.get() > 0 { + return Err(Error::InvalidState); } + // Step 3 let entry_responsible_document = GlobalScope::entry().as_window().Document(); + // Step 4 // This check is same-origin not same-origin-domain. // https://github.com/whatwg/html/issues/2282 // https://github.com/whatwg/html/pull/2288 if !self.origin.same_origin(&entry_responsible_document.origin) { - // Step 4. return Err(Error::Security); } - if self.get_current_parser().map_or(false, |parser| parser.script_nesting_level() > 0) { - // Step 5. - return Ok(Root::from_ref(self)); + // Step 5 + if self + .get_current_parser() + .map_or(false, |parser| parser.is_active()) + { + return Ok(DomRoot::from_ref(self)); } - // Step 6. - // TODO: ignore-opens-during-unload counter check. - - // Step 7: first argument already bound to `type_`. - - // Step 8. - // TODO: check session history's state. - let replace = replace.eq_ignore_ascii_case("replace"); + // Step 6 + if self.is_prompting_or_unloading() { + return Ok(DomRoot::from_ref(self)); + } - // Step 9. - // TODO: salvageable flag. + // Step 7 + if self.active_parser_was_aborted.get() { + return Ok(DomRoot::from_ref(self)); + } - // Step 10. // TODO: prompt to unload. + // TODO: set unload_event_start and unload_event_end - // Step 11. - // TODO: unload. + window_from_node(self).set_navigation_start(); - // Step 12. - self.abort(); + // Step 8 + // TODO: https://github.com/servo/servo/issues/21937 + if self.has_browsing_context() { + // spec says "stop document loading", + // which is a process that does more than just abort + self.abort(); + } - // Step 13. - for node in self.upcast::<Node>().traverse_preorder() { + // Step 9 + for node in self + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::Yes) + { node.upcast::<EventTarget>().remove_all_listeners(); } - // Step 14. - // TODO: remove any tasks associated with the Document in any task source. + // Step 10 + if self.window.Document() == DomRoot::from_ref(self) { + self.window.upcast::<EventTarget>().remove_all_listeners(); + } - // Step 15. + // Step 11 + // TODO: https://github.com/servo/servo/issues/21936 Node::replace_all(None, self.upcast::<Node>()); - // Steps 16-18. - // Let's not? - // TODO: https://github.com/whatwg/html/issues/1698 - - // Step 19. - self.implementation.set(None); - self.location.set(None); - self.images.set(None); - self.embeds.set(None); - self.links.set(None); - self.forms.set(None); - self.scripts.set(None); - self.anchors.set(None); - self.applets.set(None); - *self.stylesheets.borrow_mut() = None; - self.stylesheets_changed_since_reflow.set(true); - self.animation_frame_ident.set(0); - self.animation_frame_list.borrow_mut().clear(); - self.pending_restyles.borrow_mut().clear(); - self.target_element.set(None); - *self.last_click_info.borrow_mut() = None; - - // Step 20. - self.set_encoding(UTF_8); - - // Step 21. - // TODO: reload override buffer. - - // Step 22. - // TODO: salvageable flag. - - let url = entry_responsible_document.url(); - - // Step 23. - self.set_url(url.clone()); - - // Step 24. - // TODO: mute iframe load. - - // Step 27. - let type_ = if type_.eq_ignore_ascii_case("replace") { - "text/html" - } else if let Some(position) = type_.find(';') { - &type_[0..position] - } else { - &*type_ - }; - let type_ = type_.trim_matches(HTML_SPACE_CHARACTERS); + // Specs and tests are in a state of flux about whether + // we want to clear the selection when we remove the contents; + // WPT selection/Document-open.html wants us to not clear it + // as of Feb 1 2020 - // Step 25. - let resource_threads = - self.window.upcast::<GlobalScope>().resource_threads().clone(); - *self.loader.borrow_mut() = - DocumentLoader::new_with_threads(resource_threads, Some(url.clone())); - ServoParser::parse_html_script_input(self, url, type_); - - // Step 26. - self.ready_state.set(DocumentReadyState::Interactive); - - // Step 28 is handled when creating the parser in step 25. + // Step 12 + if self.is_fully_active() { + let mut new_url = entry_responsible_document.url(); + if entry_responsible_document != DomRoot::from_ref(self) { + new_url.set_fragment(None); + } + // TODO: https://github.com/servo/servo/issues/21939 + self.set_url(new_url); + } - // Step 29. - // TODO: truncate session history. + // Step 13 + // TODO: https://github.com/servo/servo/issues/21938 - // Step 30. - // TODO: remove history traversal tasks. + // Step 14 + self.set_quirks_mode(QuirksMode::NoQuirks); - // Step 31. - // TODO: remove earlier entries. + // Step 15 + let resource_threads = self + .window + .upcast::<GlobalScope>() + .resource_threads() + .clone(); + *self.loader.borrow_mut() = + DocumentLoader::new_with_threads(resource_threads, Some(self.url())); + ServoParser::parse_html_script_input(self, self.url()); - if !replace { - // Step 32. - // TODO: add history entry. - } + // Step 16 + self.ready_state.set(DocumentReadyState::Loading); - // Step 33. - // TODO: clear fired unload flag. + // Step 17 + // Handled when creating the parser in step 15 - // Step 34 is handled when creating the parser in step 25. + // Step 18 + Ok(DomRoot::from_ref(self)) + } - // Step 35. - Ok(Root::from_ref(self)) + // https://html.spec.whatwg.org/multipage/#dom-document-open-window + fn Open_( + &self, + url: USVString, + target: DOMString, + features: DOMString, + ) -> Fallible<Option<DomRoot<WindowProxy>>> { + self.browsing_context() + .ok_or(Error::InvalidAccess)? + .open(url, target, features) } // https://html.spec.whatwg.org/multipage/#dom-document-write @@ -3684,27 +5103,31 @@ impl DocumentMethods for Document { } // Step 2. - // TODO: handle throw-on-dynamic-markup-insertion counter. - if !self.is_active() { - // Step 3. + if self.throw_on_dynamic_markup_insertion_counter.get() > 0 { + return Err(Error::InvalidState); + } + + // Step 3 - what specifies the is_active() part here? + if !self.is_active() || self.active_parser_was_aborted.get() { return Ok(()); } let parser = match self.get_current_parser() { - Some(ref parser) if parser.can_write() => Root::from_ref(&**parser), + Some(ref parser) if parser.can_write() => DomRoot::from_ref(&**parser), _ => { // Either there is no parser, which means the parsing ended; // or script nesting level is 0, which means the method was // called from outside a parser-executed script. - if self.ignore_destructive_writes_counter.get() > 0 { + if self.is_prompting_or_unloading() || + self.ignore_destructive_writes_counter.get() > 0 + { // Step 4. - // TODO: handle ignore-opens-during-unload counter. return Ok(()); } // Step 5. - self.Open("text/html".into(), "".into())?; + self.Open(None, None)?; self.get_current_parser().unwrap() - } + }, }; // Step 7. @@ -3731,14 +5154,16 @@ impl DocumentMethods for Document { } // Step 2. - // TODO: handle throw-on-dynamic-markup-insertion counter. + if self.throw_on_dynamic_markup_insertion_counter.get() > 0 { + return Err(Error::InvalidState); + } let parser = match self.get_current_parser() { - Some(ref parser) if parser.is_script_created() => Root::from_ref(&**parser), + Some(ref parser) if parser.is_script_created() => DomRoot::from_ref(&**parser), _ => { // Step 3. return Ok(()); - } + }, }; // Step 4-6. @@ -3754,7 +5179,11 @@ impl DocumentMethods for Document { event_handler!(fullscreenerror, GetOnfullscreenerror, SetOnfullscreenerror); // https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenchange - event_handler!(fullscreenchange, GetOnfullscreenchange, SetOnfullscreenchange); + event_handler!( + fullscreenchange, + GetOnfullscreenchange, + SetOnfullscreenchange + ); // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled fn FullscreenEnabled(&self) -> bool { @@ -3767,30 +5196,47 @@ impl DocumentMethods for Document { } // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenelement - fn GetFullscreenElement(&self) -> Option<Root<Element>> { + fn GetFullscreenElement(&self) -> Option<DomRoot<Element>> { // TODO ShadowRoot self.fullscreen_element.get() } - #[allow(unrooted_must_root)] // https://fullscreen.spec.whatwg.org/#dom-document-exitfullscreen fn ExitFullscreen(&self) -> Rc<Promise> { self.exit_fullscreen() } + + // check-tidy: no specs after this line + // Servo only API to get an instance of the controls of a specific + // media element matching the given id. + fn ServoGetMediaControls(&self, id: DOMString) -> Fallible<DomRoot<ShadowRoot>> { + match self.media_controls.borrow().get(&*id) { + Some(m) => Ok(DomRoot::from_ref(&*m)), + None => Err(Error::InvalidAccess), + } + } + + // https://w3c.github.io/selection-api/#dom-document-getselection + fn GetSelection(&self) -> Option<DomRoot<Selection>> { + if self.has_browsing_context { + Some(self.selection.or_init(|| Selection::new(self))) + } else { + None + } + } } fn update_with_current_time_ms(marker: &Cell<u64>) { - if marker.get() == Default::default() { + if marker.get() == 0 { let time = time::get_time(); let current_time_ms = time.sec * 1000 + time.nsec as i64 / 1000000; marker.set(current_time_ms as u64); } } -/// https://w3c.github.io/webappsec-referrer-policy/#determine-policy-for-token +/// <https://w3c.github.io/webappsec-referrer-policy/#determine-policy-for-token> pub fn determine_policy_for_token(token: &str) -> Option<ReferrerPolicy> { - let lower = token.to_lowercase(); - return match lower.as_ref() { + match_ignore_ascii_case! { token, "never" | "no-referrer" => Some(ReferrerPolicy::NoReferrer), "default" | "no-referrer-when-downgrade" => Some(ReferrerPolicy::NoReferrerWhenDowngrade), "origin" => Some(ReferrerPolicy::Origin), @@ -3804,79 +5250,17 @@ pub fn determine_policy_for_token(token: &str) -> Option<ReferrerPolicy> { } } -pub struct DocumentProgressHandler { - addr: Trusted<Document> -} - -impl DocumentProgressHandler { - pub fn new(addr: Trusted<Document>) -> DocumentProgressHandler { - DocumentProgressHandler { - addr: addr - } - } - - fn set_ready_state_complete(&self) { - let document = self.addr.root(); - document.set_ready_state(DocumentReadyState::Complete); - } - - fn dispatch_load(&self) { - let document = self.addr.root(); - if document.browsing_context().is_none() { - return; - } - let window = document.window(); - let event = Event::new(window.upcast(), - atom!("load"), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable); - let wintarget = window.upcast::<EventTarget>(); - event.set_trusted(true); - - // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventStart - update_with_current_time_ms(&document.load_event_start); - - debug!("About to dispatch load for {:?}", document.url()); - let _ = wintarget.dispatch_event_with_target(document.upcast(), &event); - - // http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventEnd - update_with_current_time_ms(&document.load_event_end); - - window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::DocumentLoaded); - - document.notify_constellation_load(); - } -} - -impl Runnable for DocumentProgressHandler { - fn name(&self) -> &'static str { "DocumentProgressHandler" } - - fn handler(self: Box<DocumentProgressHandler>) { - let document = self.addr.root(); - let window = document.window(); - if window.is_alive() { - self.set_ready_state_complete(); - self.dispatch_load(); - if let Some(fragment) = document.url().fragment() { - document.check_and_scroll_fragment(fragment); - } - } - } -} - /// Specifies the type of focus event that is sent to a pipeline -#[derive(Copy, Clone, PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub enum FocusType { - Element, // The first focus message - focus the element itself - Parent, // Focusing a parent element (an iframe) + Element, // The first focus message - focus the element itself + Parent, // Focusing a parent element (an iframe) } /// Focus events pub enum FocusEventType { - Focus, // Element gained focus. Doesn't bubble. - Blur, // Element lost focus. Doesn't bubble. + Focus, // Element gained focus. Doesn't bubble. + Blur, // Element lost focus. Doesn't bubble. } /// A fake `requestAnimationFrame()` callback—"fake" because it is not triggered by the video @@ -3885,10 +5269,10 @@ pub enum FocusEventType { /// If the page is observed to be using `requestAnimationFrame()` for non-animation purposes (i.e. /// without mutating the DOM), then we fall back to simple timeouts to save energy over video /// refresh. -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] pub struct FakeRequestAnimationFrameCallback { /// The document. - #[ignore_heap_size_of = "non-owning"] + #[ignore_malloc_size_of = "non-owning"] document: Trusted<Document>, } @@ -3899,12 +5283,14 @@ impl FakeRequestAnimationFrameCallback { } } -#[derive(HeapSizeOf, JSTraceable)] +#[derive(JSTraceable, MallocSizeOf)] pub enum AnimationFrameCallback { - DevtoolsFramerateTick { actor_name: String }, + DevtoolsFramerateTick { + actor_name: String, + }, FrameRequestCallback { - #[ignore_heap_size_of = "Rc is hard"] - callback: Rc<FrameRequestCallback> + #[ignore_malloc_size_of = "Rc is hard"] + callback: Rc<FrameRequestCallback>, }, } @@ -3913,22 +5299,26 @@ impl AnimationFrameCallback { match *self { AnimationFrameCallback::DevtoolsFramerateTick { ref actor_name } => { let msg = ScriptToDevtoolsControlMsg::FramerateTick(actor_name.clone(), now); - let devtools_sender = document.window().upcast::<GlobalScope>().devtools_chan().unwrap(); + let devtools_sender = document + .window() + .upcast::<GlobalScope>() + .devtools_chan() + .unwrap(); devtools_sender.send(msg).unwrap(); - } + }, AnimationFrameCallback::FrameRequestCallback { ref callback } => { // TODO(jdm): The spec says that any exceptions should be suppressed: // https://github.com/servo/servo/issues/6928 let _ = callback.Call__(Finite::wrap(now), ExceptionHandling::Report); - } + }, } } } -#[derive(Default, HeapSizeOf, JSTraceable)] -#[must_root] +#[derive(Default, JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] struct PendingInOrderScriptVec { - scripts: DOMRefCell<VecDeque<PendingScript>>, + scripts: DomRefCell<VecDeque<PendingScript>>, } impl PendingInOrderScriptVec { @@ -3937,23 +5327,25 @@ impl PendingInOrderScriptVec { } fn push(&self, element: &HTMLScriptElement) { - self.scripts.borrow_mut().push_back(PendingScript::new(element)); + self.scripts + .borrow_mut() + .push_back(PendingScript::new(element)); } fn loaded(&self, element: &HTMLScriptElement, result: ScriptResult) { let mut scripts = self.scripts.borrow_mut(); - let mut entry = scripts.iter_mut().find(|entry| &*entry.element == element).unwrap(); + let entry = scripts + .iter_mut() + .find(|entry| &*entry.element == element) + .unwrap(); entry.loaded(result); } - fn take_next_ready_to_be_executed(&self) -> Option<(Root<HTMLScriptElement>, ScriptResult)> { + fn take_next_ready_to_be_executed(&self) -> Option<(DomRoot<HTMLScriptElement>, ScriptResult)> { let mut scripts = self.scripts.borrow_mut(); - let pair = scripts.front_mut().and_then(PendingScript::take_result); - if pair.is_none() { - return None; - } + let pair = scripts.front_mut()?.take_result()?; scripts.pop_front(); - pair + Some(pair) } fn clear(&self) { @@ -3961,20 +5353,26 @@ impl PendingInOrderScriptVec { } } -#[derive(HeapSizeOf, JSTraceable)] -#[must_root] +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] struct PendingScript { - element: JS<HTMLScriptElement>, + element: Dom<HTMLScriptElement>, load: Option<ScriptResult>, } impl PendingScript { fn new(element: &HTMLScriptElement) -> Self { - Self { element: JS::from_ref(element), load: None } + Self { + element: Dom::from_ref(element), + load: None, + } } fn new_with_load(element: &HTMLScriptElement, load: Option<ScriptResult>) -> Self { - Self { element: JS::from_ref(element), load } + Self { + element: Dom::from_ref(element), + load, + } } fn loaded(&mut self, result: ScriptResult) { @@ -3982,7 +5380,17 @@ impl PendingScript { self.load = Some(result); } - fn take_result(&mut self) -> Option<(Root<HTMLScriptElement>, ScriptResult)> { - self.load.take().map(|result| (Root::from_ref(&*self.element), result)) + fn take_result(&mut self) -> Option<(DomRoot<HTMLScriptElement>, ScriptResult)> { + self.load + .take() + .map(|result| (DomRoot::from_ref(&*self.element), result)) } } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ReflowTriggerCondition { + StylesheetsChanged, + DirtyDescendants, + PendingRestyles, + PaintPostponed, +} diff --git a/components/script/dom/documentfragment.rs b/components/script/dom/documentfragment.rs index 6f2a123dd06..ac87a9a786e 100644 --- a/components/script/dom/documentfragment.rs +++ b/components/script/dom/documentfragment.rs @@ -1,78 +1,88 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::DocumentFragmentBinding; -use dom::bindings::codegen::Bindings::DocumentFragmentBinding::DocumentFragmentMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::UnionTypes::NodeOrString; -use dom::bindings::error::{ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::Element; -use dom::htmlcollection::HTMLCollection; -use dom::node::{Node, window_from_node}; -use dom::nodelist::NodeList; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::DocumentFragmentBinding::DocumentFragmentMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::UnionTypes::NodeOrString; +use crate::dom::bindings::error::{ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::htmlcollection::HTMLCollection; +use crate::dom::node::{window_from_node, Node}; +use crate::dom::nodelist::NodeList; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; +use std::collections::HashMap; // https://dom.spec.whatwg.org/#documentfragment #[dom_struct] pub struct DocumentFragment { node: Node, + /// Caches for the getElement methods + id_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>, } impl DocumentFragment { /// Creates a new DocumentFragment. - fn new_inherited(document: &Document) -> DocumentFragment { + pub fn new_inherited(document: &Document) -> DocumentFragment { DocumentFragment { node: Node::new_inherited(document), + id_map: DomRefCell::new(HashMap::new()), } } - pub fn new(document: &Document) -> Root<DocumentFragment> { - Node::reflect_node(box DocumentFragment::new_inherited(document), - document, - DocumentFragmentBinding::Wrap) + pub fn new(document: &Document) -> DomRoot<DocumentFragment> { + Node::reflect_node( + Box::new(DocumentFragment::new_inherited(document)), + document, + ) } - pub fn Constructor(window: &Window) -> Fallible<Root<DocumentFragment>> { + #[allow(non_snake_case)] + pub fn Constructor(window: &Window) -> Fallible<DomRoot<DocumentFragment>> { let document = window.Document(); Ok(DocumentFragment::new(&document)) } + + pub fn id_map(&self) -> &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>> { + &self.id_map + } } impl DocumentFragmentMethods for DocumentFragment { // https://dom.spec.whatwg.org/#dom-parentnode-children - fn Children(&self) -> Root<HTMLCollection> { + fn Children(&self) -> DomRoot<HTMLCollection> { let window = window_from_node(self); HTMLCollection::children(&window, self.upcast()) } // https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid - fn GetElementById(&self, id: DOMString) -> Option<Root<Element>> { - let node = self.upcast::<Node>(); + fn GetElementById(&self, id: DOMString) -> Option<DomRoot<Element>> { let id = Atom::from(id); - node.traverse_preorder().filter_map(Root::downcast::<Element>).find(|descendant| { - match descendant.get_attribute(&ns!(), &local_name!("id")) { - None => false, - Some(attr) => *attr.value().as_atom() == id, - } - }) + self.id_map + .borrow() + .get(&id) + .map(|ref elements| DomRoot::from_ref(&*(*elements)[0])) } // https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild - fn GetFirstElementChild(&self) -> Option<Root<Element>> { + fn GetFirstElementChild(&self) -> Option<DomRoot<Element>> { self.upcast::<Node>().child_elements().next() } // https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild - fn GetLastElementChild(&self) -> Option<Root<Element>> { - self.upcast::<Node>().rev_children().filter_map(Root::downcast::<Element>).next() + fn GetLastElementChild(&self) -> Option<DomRoot<Element>> { + self.upcast::<Node>() + .rev_children() + .filter_map(DomRoot::downcast::<Element>) + .next() } // https://dom.spec.whatwg.org/#dom-parentnode-childelementcount @@ -90,13 +100,18 @@ impl DocumentFragmentMethods for DocumentFragment { self.upcast::<Node>().append(nodes) } + // https://dom.spec.whatwg.org/#dom-parentnode-replacechildren + fn ReplaceChildren(&self, nodes: Vec<NodeOrString>) -> ErrorResult { + self.upcast::<Node>().replace_children(nodes) + } + // https://dom.spec.whatwg.org/#dom-parentnode-queryselector - fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> { + fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> { self.upcast::<Node>().query_selector(selectors) } // https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall - fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<Root<NodeList>> { + fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<DomRoot<NodeList>> { self.upcast::<Node>().query_selector_all(selectors) } } diff --git a/components/script/dom/documentorshadowroot.rs b/components/script/dom/documentorshadowroot.rs new file mode 100644 index 00000000000..43d345e5e01 --- /dev/null +++ b/components/script/dom/documentorshadowroot.rs @@ -0,0 +1,306 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::element::Element; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlmetaelement::HTMLMetaElement; +use crate::dom::node::{self, Node, VecPreOrderInsertionHelper}; +use crate::dom::window::Window; +use crate::stylesheet_set::StylesheetSetRef; +use euclid::default::Point2D; +use script_layout_interface::message::{NodesFromPointQueryType, QueryMsg}; +use script_traits::UntrustedNodeAddress; +use servo_arc::Arc; +use servo_atoms::Atom; +use std::collections::HashMap; +use std::fmt; +use style::context::QuirksMode; +use style::invalidation::media_queries::{MediaListKey, ToMediaListKey}; +use style::media_queries::MediaList; +use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard}; +use style::stylesheets::{CssRule, Origin, Stylesheet}; + +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct StyleSheetInDocument { + #[ignore_malloc_size_of = "Arc"] + pub sheet: Arc<Stylesheet>, + pub owner: Dom<Element>, +} + +impl fmt::Debug for StyleSheetInDocument { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.sheet.fmt(formatter) + } +} + +impl PartialEq for StyleSheetInDocument { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.sheet, &other.sheet) + } +} + +impl ToMediaListKey for StyleSheetInDocument { + fn to_media_list_key(&self) -> MediaListKey { + self.sheet.to_media_list_key() + } +} + +impl ::style::stylesheets::StylesheetInDocument for StyleSheetInDocument { + fn origin(&self, guard: &SharedRwLockReadGuard) -> Origin { + self.sheet.origin(guard) + } + + fn quirks_mode(&self, guard: &SharedRwLockReadGuard) -> QuirksMode { + self.sheet.quirks_mode(guard) + } + + fn enabled(&self) -> bool { + self.sheet.enabled() + } + + fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { + self.sheet.media(guard) + } + + fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { + self.sheet.rules(guard) + } +} + +// https://w3c.github.io/webcomponents/spec/shadow/#extensions-to-the-documentorshadowroot-mixin +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +pub struct DocumentOrShadowRoot { + window: Dom<Window>, +} + +impl DocumentOrShadowRoot { + pub fn new(window: &Window) -> Self { + Self { + window: Dom::from_ref(window), + } + } + + pub fn nodes_from_point( + &self, + client_point: &Point2D<f32>, + reflow_goal: NodesFromPointQueryType, + ) -> Vec<UntrustedNodeAddress> { + if !self + .window + .layout_reflow(QueryMsg::NodesFromPointQuery(*client_point, reflow_goal)) + { + return vec![]; + }; + + self.window.layout().nodes_from_point_response() + } + + #[allow(unsafe_code)] + // https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint + pub fn element_from_point( + &self, + x: Finite<f64>, + y: Finite<f64>, + document_element: Option<DomRoot<Element>>, + has_browsing_context: bool, + ) -> Option<DomRoot<Element>> { + let x = *x as f32; + let y = *y as f32; + let point = &Point2D::new(x, y); + let viewport = self.window.window_size().initial_viewport; + + if !has_browsing_context { + return None; + } + + if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height { + return None; + } + + match self + .nodes_from_point(point, NodesFromPointQueryType::Topmost) + .first() + { + Some(address) => { + let node = unsafe { node::from_untrusted_node_address(*address) }; + let parent_node = node.GetParentNode().unwrap(); + let element_ref = node + .downcast::<Element>() + .unwrap_or_else(|| parent_node.downcast::<Element>().unwrap()); + + Some(DomRoot::from_ref(element_ref)) + }, + None => document_element, + } + } + + #[allow(unsafe_code)] + // https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint + pub fn elements_from_point( + &self, + x: Finite<f64>, + y: Finite<f64>, + document_element: Option<DomRoot<Element>>, + has_browsing_context: bool, + ) -> Vec<DomRoot<Element>> { + let x = *x as f32; + let y = *y as f32; + let point = &Point2D::new(x, y); + let viewport = self.window.window_size().initial_viewport; + + if !has_browsing_context { + return vec![]; + } + + // Step 2 + if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height { + return vec![]; + } + + // Step 1 and Step 3 + let nodes = self.nodes_from_point(point, NodesFromPointQueryType::All); + let mut elements: Vec<DomRoot<Element>> = nodes + .iter() + .flat_map(|&untrusted_node_address| { + let node = unsafe { node::from_untrusted_node_address(untrusted_node_address) }; + DomRoot::downcast::<Element>(node) + }) + .collect(); + + // Step 4 + if let Some(root_element) = document_element { + if elements.last() != Some(&root_element) { + elements.push(root_element); + } + } + + // Step 5 + elements + } + + // https://html.spec.whatwg.org/multipage/#dom-document-activeelement + pub fn get_active_element( + &self, + focused_element: Option<DomRoot<Element>>, + body: Option<DomRoot<HTMLElement>>, + document_element: Option<DomRoot<Element>>, + ) -> Option<DomRoot<Element>> { + // TODO: Step 2. + + match focused_element { + Some(element) => Some(element), // Step 3. and 4. + None => match body { + // Step 5. + Some(body) => Some(DomRoot::upcast(body)), + None => document_element, + }, + } + } + + /// Remove a stylesheet owned by `owner` from the list of document sheets. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn remove_stylesheet( + owner: &Element, + s: &Arc<Stylesheet>, + mut stylesheets: StylesheetSetRef<StyleSheetInDocument>, + ) { + let guard = s.shared_lock.read(); + + // FIXME(emilio): Would be nice to remove the clone, etc. + stylesheets.remove_stylesheet( + None, + StyleSheetInDocument { + sheet: s.clone(), + owner: Dom::from_ref(owner), + }, + &guard, + ); + } + + /// Add a stylesheet owned by `owner` to the list of document sheets, in the + /// correct tree position. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn add_stylesheet( + owner: &Element, + mut stylesheets: StylesheetSetRef<StyleSheetInDocument>, + sheet: Arc<Stylesheet>, + insertion_point: Option<StyleSheetInDocument>, + style_shared_lock: &StyleSharedRwLock, + ) { + // FIXME(emilio): It'd be nice to unify more code between the elements + // that own stylesheets, but StylesheetOwner is more about loading + // them... + debug_assert!( + owner.as_stylesheet_owner().is_some() || owner.is::<HTMLMetaElement>(), + "Wat" + ); + + let sheet = StyleSheetInDocument { + sheet, + owner: Dom::from_ref(owner), + }; + + let guard = style_shared_lock.read(); + + match insertion_point { + Some(ip) => { + stylesheets.insert_stylesheet_before(None, sheet, ip, &guard); + }, + None => { + stylesheets.append_stylesheet(None, sheet, &guard); + }, + } + } + + /// Remove any existing association between the provided id/name and any elements in this document. + pub fn unregister_named_element( + &self, + id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>, + to_unregister: &Element, + id: &Atom, + ) { + debug!( + "Removing named element {:p}: {:p} id={}", + self, to_unregister, id + ); + let mut id_map = id_map.borrow_mut(); + let is_empty = match id_map.get_mut(&id) { + None => false, + Some(elements) => { + let position = elements + .iter() + .position(|element| &**element == to_unregister) + .expect("This element should be in registered."); + elements.remove(position); + elements.is_empty() + }, + }; + if is_empty { + id_map.remove(&id); + } + } + + /// Associate an element present in this document with the provided id/name. + pub fn register_named_element( + &self, + id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>, + element: &Element, + id: &Atom, + root: DomRoot<Node>, + ) { + debug!("Adding named element {:p}: {:p} id={}", self, element, id); + assert!(element.upcast::<Node>().is_connected()); + assert!(!id.is_empty()); + let mut id_map = id_map.borrow_mut(); + let elements = id_map.entry(id.clone()).or_insert(Vec::new()); + elements.insert_pre_order(element, &root); + } +} diff --git a/components/script/dom/documenttype.rs b/components/script/dom/documenttype.rs index 2b03ce2e07b..f323cd63d63 100644 --- a/components/script/dom/documenttype.rs +++ b/components/script/dom/documenttype.rs @@ -1,16 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DocumentTypeBinding; -use dom::bindings::codegen::Bindings::DocumentTypeBinding::DocumentTypeMethods; -use dom::bindings::codegen::UnionTypes::NodeOrString; -use dom::bindings::error::ErrorResult; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::node::Node; +use crate::dom::bindings::codegen::Bindings::DocumentTypeBinding::DocumentTypeMethods; +use crate::dom::bindings::codegen::UnionTypes::NodeOrString; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::node::Node; use dom_struct::dom_struct; // https://dom.spec.whatwg.org/#documenttype @@ -24,11 +23,12 @@ pub struct DocumentType { } impl DocumentType { - fn new_inherited(name: DOMString, - public_id: Option<DOMString>, - system_id: Option<DOMString>, - document: &Document) - -> DocumentType { + fn new_inherited( + name: DOMString, + public_id: Option<DOMString>, + system_id: Option<DOMString>, + document: &Document, + ) -> DocumentType { DocumentType { node: Node::new_inherited(document), name: name, @@ -37,14 +37,18 @@ impl DocumentType { } } #[allow(unrooted_must_root)] - pub fn new(name: DOMString, - public_id: Option<DOMString>, - system_id: Option<DOMString>, - document: &Document) - -> Root<DocumentType> { - Node::reflect_node(box DocumentType::new_inherited(name, public_id, system_id, document), - document, - DocumentTypeBinding::Wrap) + pub fn new( + name: DOMString, + public_id: Option<DOMString>, + system_id: Option<DOMString>, + document: &Document, + ) -> DomRoot<DocumentType> { + Node::reflect_node( + Box::new(DocumentType::new_inherited( + name, public_id, system_id, document, + )), + document, + ) } #[inline] diff --git a/components/script/dom/domexception.rs b/components/script/dom/domexception.rs index 628ae5ceed5..c2533cd7932 100644 --- a/components/script/dom/domexception.rs +++ b/components/script/dom/domexception.rs @@ -1,18 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DOMExceptionBinding; -use dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionConstants; -use dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionConstants; +use crate::dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionMethods; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; #[repr(u16)] -#[derive(JSTraceable, Copy, Clone, Debug, HeapSizeOf)] +#[derive(Clone, Copy, Debug, Eq, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)] pub enum DOMErrorName { IndexSizeError = DOMExceptionConstants::INDEX_SIZE_ERR, HierarchyRequestError = DOMExceptionConstants::HIERARCHY_REQUEST_ERR, @@ -31,49 +31,61 @@ pub enum DOMErrorName { NetworkError = DOMExceptionConstants::NETWORK_ERR, AbortError = DOMExceptionConstants::ABORT_ERR, TypeMismatchError = DOMExceptionConstants::TYPE_MISMATCH_ERR, + URLMismatchError = DOMExceptionConstants::URL_MISMATCH_ERR, QuotaExceededError = DOMExceptionConstants::QUOTA_EXCEEDED_ERR, TimeoutError = DOMExceptionConstants::TIMEOUT_ERR, InvalidNodeTypeError = DOMExceptionConstants::INVALID_NODE_TYPE_ERR, DataCloneError = DOMExceptionConstants::DATA_CLONE_ERR, + NotReadableError, + OperationError, +} + +impl DOMErrorName { + pub fn from(s: &DOMString) -> Option<DOMErrorName> { + match s.as_ref() { + "IndexSizeError" => Some(DOMErrorName::IndexSizeError), + "HierarchyRequestError" => Some(DOMErrorName::HierarchyRequestError), + "WrongDocumentError" => Some(DOMErrorName::WrongDocumentError), + "InvalidCharacterError" => Some(DOMErrorName::InvalidCharacterError), + "NoModificationAllowedError" => Some(DOMErrorName::NoModificationAllowedError), + "NotFoundError" => Some(DOMErrorName::NotFoundError), + "NotSupportedError" => Some(DOMErrorName::NotSupportedError), + "InUseAttributeError" => Some(DOMErrorName::InUseAttributeError), + "InvalidStateError" => Some(DOMErrorName::InvalidStateError), + "SyntaxError" => Some(DOMErrorName::SyntaxError), + "InvalidModificationError" => Some(DOMErrorName::InvalidModificationError), + "NamespaceError" => Some(DOMErrorName::NamespaceError), + "InvalidAccessError" => Some(DOMErrorName::InvalidAccessError), + "SecurityError" => Some(DOMErrorName::SecurityError), + "NetworkError" => Some(DOMErrorName::NetworkError), + "AbortError" => Some(DOMErrorName::AbortError), + "TypeMismatchError" => Some(DOMErrorName::TypeMismatchError), + "URLMismatchError" => Some(DOMErrorName::URLMismatchError), + "QuotaExceededError" => Some(DOMErrorName::QuotaExceededError), + "TimeoutError" => Some(DOMErrorName::TimeoutError), + "InvalidNodeTypeError" => Some(DOMErrorName::InvalidNodeTypeError), + "DataCloneError" => Some(DOMErrorName::DataCloneError), + "NotReadableError" => Some(DOMErrorName::NotReadableError), + "OperationError" => Some(DOMErrorName::OperationError), + _ => None, + } + } } #[dom_struct] pub struct DOMException { reflector_: Reflector, - code: DOMErrorName, + message: DOMString, + name: DOMString, } impl DOMException { - fn new_inherited(code: DOMErrorName) -> DOMException { - DOMException { - reflector_: Reflector::new(), - code: code, - } - } - - pub fn new(global: &GlobalScope, code: DOMErrorName) -> Root<DOMException> { - reflect_dom_object(box DOMException::new_inherited(code), - global, - DOMExceptionBinding::Wrap) - } -} - -impl DOMExceptionMethods for DOMException { - // https://heycam.github.io/webidl/#dfn-DOMException - fn Code(&self) -> u16 { - self.code as u16 - } - - // https://heycam.github.io/webidl/#idl-DOMException-error-names - fn Name(&self) -> DOMString { - DOMString::from(format!("{:?}", self.code)) - } - - // https://heycam.github.io/webidl/#error-names - fn Message(&self) -> DOMString { - let message = match self.code { + fn get_error_data_by_code(code: DOMErrorName) -> (DOMString, DOMString) { + let message = match &code { DOMErrorName::IndexSizeError => "The index is not in the allowed range.", - DOMErrorName::HierarchyRequestError => "The operation would yield an incorrect node tree.", + DOMErrorName::HierarchyRequestError => { + "The operation would yield an incorrect node tree." + }, DOMErrorName::WrongDocumentError => "The object is in the wrong document.", DOMErrorName::InvalidCharacterError => "The string contains invalid characters.", DOMErrorName::NoModificationAllowedError => "The object can not be modified.", @@ -84,23 +96,80 @@ impl DOMExceptionMethods for DOMException { DOMErrorName::SyntaxError => "The string did not match the expected pattern.", DOMErrorName::InvalidModificationError => "The object can not be modified in this way.", DOMErrorName::NamespaceError => "The operation is not allowed by Namespaces in XML.", - DOMErrorName::InvalidAccessError => "The object does not support the operation or argument.", + DOMErrorName::InvalidAccessError => { + "The object does not support the operation or argument." + }, DOMErrorName::SecurityError => "The operation is insecure.", DOMErrorName::NetworkError => "A network error occurred.", DOMErrorName::AbortError => "The operation was aborted.", DOMErrorName::TypeMismatchError => "The given type does not match any expected type.", + DOMErrorName::URLMismatchError => "The given URL does not match another URL.", DOMErrorName::QuotaExceededError => "The quota has been exceeded.", DOMErrorName::TimeoutError => "The operation timed out.", - DOMErrorName::InvalidNodeTypeError => - "The supplied node is incorrect or has an incorrect ancestor for this operation.", + DOMErrorName::InvalidNodeTypeError => { + "The supplied node is incorrect or has an incorrect ancestor for this operation." + }, DOMErrorName::DataCloneError => "The object can not be cloned.", + DOMErrorName::NotReadableError => "The I/O read operation failed.", + DOMErrorName::OperationError => { + "The operation failed for an operation-specific reason." + }, }; - DOMString::from(message) + ( + DOMString::from(message), + DOMString::from(format!("{:?}", code)), + ) + } + + fn new_inherited(message_: DOMString, name_: DOMString) -> DOMException { + DOMException { + reflector_: Reflector::new(), + message: message_, + name: name_, + } + } + + pub fn new(global: &GlobalScope, code: DOMErrorName) -> DomRoot<DOMException> { + let (message, name) = DOMException::get_error_data_by_code(code); + + reflect_dom_object(Box::new(DOMException::new_inherited(message, name)), global) + } + + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + message: DOMString, + name: DOMString, + ) -> Result<DomRoot<DOMException>, Error> { + Ok(reflect_dom_object( + Box::new(DOMException::new_inherited(message, name)), + global, + )) } - // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-error.prototype.tostring - fn Stringifier(&self) -> DOMString { - DOMString::from(format!("{}: {}", self.Name(), self.Message())) + // not an IDL stringifier, used internally + pub fn stringifier(&self) -> DOMString { + DOMString::from(format!("{}: {}", self.name, self.message)) + } +} + +impl DOMExceptionMethods for DOMException { + // https://heycam.github.io/webidl/#dom-domexception-code + fn Code(&self) -> u16 { + match DOMErrorName::from(&self.name) { + Some(code) if code <= DOMErrorName::DataCloneError => code as u16, + _ => 0, + } + } + + // https://heycam.github.io/webidl/#idl-DOMException-error-names + fn Name(&self) -> DOMString { + self.name.clone() + } + + // https://heycam.github.io/webidl/#error-names + fn Message(&self) -> DOMString { + self.message.clone() } } diff --git a/components/script/dom/domimplementation.rs b/components/script/dom/domimplementation.rs index e0f9d69a45a..d5a3f07bde2 100644 --- a/components/script/dom/domimplementation.rs +++ b/components/script/dom/domimplementation.rs @@ -1,28 +1,30 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use document_loader::DocumentLoader; -use dom::bindings::codegen::Bindings::DOMImplementationBinding; -use dom::bindings::codegen::Bindings::DOMImplementationBinding::DOMImplementationMethods; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::bindings::xmlname::{namespace_from_domstring, validate_qualified_name}; -use dom::document::{Document, HasBrowsingContext, IsHTMLDocument}; -use dom::document::DocumentSource; -use dom::documenttype::DocumentType; -use dom::htmlbodyelement::HTMLBodyElement; -use dom::htmlheadelement::HTMLHeadElement; -use dom::htmlhtmlelement::HTMLHtmlElement; -use dom::htmltitleelement::HTMLTitleElement; -use dom::node::Node; -use dom::text::Text; -use dom::xmldocument::XMLDocument; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::document_loader::DocumentLoader; +use crate::dom::bindings::codegen::Bindings::DOMImplementationBinding::DOMImplementationMethods; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ + DocumentMethods, ElementCreationOptions, +}; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::UnionTypes::StringOrElementCreationOptions; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::xmlname::{namespace_from_domstring, validate_qualified_name}; +use crate::dom::document::DocumentSource; +use crate::dom::document::{Document, HasBrowsingContext, IsHTMLDocument}; +use crate::dom::documenttype::DocumentType; +use crate::dom::htmlbodyelement::HTMLBodyElement; +use crate::dom::htmlheadelement::HTMLHeadElement; +use crate::dom::htmlhtmlelement::HTMLHtmlElement; +use crate::dom::htmltitleelement::HTMLTitleElement; +use crate::dom::node::Node; +use crate::dom::text::Text; +use crate::dom::xmldocument::XMLDocument; use dom_struct::dom_struct; use script_traits::DocumentActivity; @@ -30,69 +32,83 @@ use script_traits::DocumentActivity; #[dom_struct] pub struct DOMImplementation { reflector_: Reflector, - document: JS<Document>, + document: Dom<Document>, } impl DOMImplementation { fn new_inherited(document: &Document) -> DOMImplementation { DOMImplementation { reflector_: Reflector::new(), - document: JS::from_ref(document), + document: Dom::from_ref(document), } } - pub fn new(document: &Document) -> Root<DOMImplementation> { + pub fn new(document: &Document) -> DomRoot<DOMImplementation> { let window = document.window(); - reflect_dom_object(box DOMImplementation::new_inherited(document), - window, - DOMImplementationBinding::Wrap) + reflect_dom_object(Box::new(DOMImplementation::new_inherited(document)), window) } } // https://dom.spec.whatwg.org/#domimplementation impl DOMImplementationMethods for DOMImplementation { // https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype - fn CreateDocumentType(&self, - qualified_name: DOMString, - pubid: DOMString, - sysid: DOMString) - -> Fallible<Root<DocumentType>> { - try!(validate_qualified_name(&qualified_name)); - Ok(DocumentType::new(qualified_name, Some(pubid), Some(sysid), &self.document)) + fn CreateDocumentType( + &self, + qualified_name: DOMString, + pubid: DOMString, + sysid: DOMString, + ) -> Fallible<DomRoot<DocumentType>> { + validate_qualified_name(&qualified_name)?; + Ok(DocumentType::new( + qualified_name, + Some(pubid), + Some(sysid), + &self.document, + )) } // https://dom.spec.whatwg.org/#dom-domimplementation-createdocument - fn CreateDocument(&self, - maybe_namespace: Option<DOMString>, - qname: DOMString, - maybe_doctype: Option<&DocumentType>) - -> Fallible<Root<XMLDocument>> { + fn CreateDocument( + &self, + maybe_namespace: Option<DOMString>, + qname: DOMString, + maybe_doctype: Option<&DocumentType>, + ) -> Fallible<DomRoot<XMLDocument>> { let win = self.document.window(); let loader = DocumentLoader::new(&self.document.loader()); let namespace = namespace_from_domstring(maybe_namespace.to_owned()); let content_type = match namespace { - ns!(html) => "application/xhtml+xml", - ns!(svg) => "image/svg+xml", - _ => "application/xml" + ns!(html) => "application/xhtml+xml".parse().unwrap(), + ns!(svg) => mime::IMAGE_SVG, + _ => "application/xml".parse().unwrap(), }; // Step 1. - let doc = XMLDocument::new(win, - HasBrowsingContext::No, - None, - self.document.origin().clone(), - IsHTMLDocument::NonHTMLDocument, - Some(DOMString::from(content_type)), - None, - DocumentActivity::Inactive, - DocumentSource::NotFromParser, - loader); + let doc = XMLDocument::new( + win, + HasBrowsingContext::No, + None, + self.document.origin().clone(), + IsHTMLDocument::NonHTMLDocument, + Some(content_type), + None, + DocumentActivity::Inactive, + DocumentSource::NotFromParser, + loader, + ); // Step 2-3. let maybe_elem = if qname.is_empty() { None } else { - match doc.upcast::<Document>().CreateElementNS(maybe_namespace, qname) { + let options = + StringOrElementCreationOptions::ElementCreationOptions(ElementCreationOptions { + is: None, + }); + match doc + .upcast::<Document>() + .CreateElementNS(maybe_namespace, qname, options) + { Err(error) => return Err(error), Ok(elem) => Some(elem), } @@ -113,30 +129,33 @@ impl DOMImplementationMethods for DOMImplementation { } // Step 6. - // FIXME: https://github.com/mozilla/servo/issues/1522 + // The origin is already set // Step 7. Ok(doc) } // https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument - fn CreateHTMLDocument(&self, title: Option<DOMString>) -> Root<Document> { + fn CreateHTMLDocument(&self, title: Option<DOMString>) -> DomRoot<Document> { let win = self.document.window(); let loader = DocumentLoader::new(&self.document.loader()); // Step 1-2. - let doc = Document::new(win, - HasBrowsingContext::No, - None, - self.document.origin().clone(), - IsHTMLDocument::HTMLDocument, - None, - None, - DocumentActivity::Inactive, - DocumentSource::NotFromParser, - loader, - None, - None); + let doc = Document::new( + win, + HasBrowsingContext::No, + None, + self.document.origin().clone(), + IsHTMLDocument::HTMLDocument, + None, + None, + DocumentActivity::Inactive, + DocumentSource::NotFromParser, + loader, + None, + None, + Default::default(), + ); { // Step 3. @@ -148,25 +167,24 @@ impl DOMImplementationMethods for DOMImplementation { { // Step 4. let doc_node = doc.upcast::<Node>(); - let doc_html = Root::upcast::<Node>(HTMLHtmlElement::new(local_name!("html"), - None, - &doc)); + let doc_html = + DomRoot::upcast::<Node>(HTMLHtmlElement::new(local_name!("html"), None, &doc)); doc_node.AppendChild(&doc_html).expect("Appending failed"); { // Step 5. - let doc_head = Root::upcast::<Node>(HTMLHeadElement::new(local_name!("head"), - None, - &doc)); + let doc_head = + DomRoot::upcast::<Node>(HTMLHeadElement::new(local_name!("head"), None, &doc)); doc_html.AppendChild(&doc_head).unwrap(); // Step 6. if let Some(title_str) = title { // Step 6.1. - let doc_title = - Root::upcast::<Node>(HTMLTitleElement::new(local_name!("title"), - None, - &doc)); + let doc_title = DomRoot::upcast::<Node>(HTMLTitleElement::new( + local_name!("title"), + None, + &doc, + )); doc_head.AppendChild(&doc_title).unwrap(); // Step 6.2. @@ -181,7 +199,7 @@ impl DOMImplementationMethods for DOMImplementation { } // Step 8. - // FIXME: https://github.com/mozilla/servo/issues/1522 + // The origin is already set // Step 9. doc diff --git a/components/script/dom/dommatrix.rs b/components/script/dom/dommatrix.rs index eb33d42efcf..c5520c7d6a7 100644 --- a/components/script/dom/dommatrix.rs +++ b/components/script/dom/dommatrix.rs @@ -1,370 +1,435 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::DOMMatrixBinding::{Wrap, DOMMatrixMethods, DOMMatrixInit}; -use dom::bindings::codegen::Bindings::DOMMatrixReadOnlyBinding::DOMMatrixReadOnlyMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::dommatrixreadonly::{dommatrixinit_to_matrix, DOMMatrixReadOnly, entries_to_matrix}; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::{DOMMatrixInit, DOMMatrixMethods}; +use crate::dom::bindings::codegen::Bindings::DOMMatrixReadOnlyBinding::DOMMatrixReadOnlyMethods; +use crate::dom::bindings::codegen::UnionTypes::StringOrUnrestrictedDoubleSequence; +use crate::dom::bindings::error; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::dommatrixreadonly::{ + dommatrixinit_to_matrix, entries_to_matrix, transform_to_matrix, DOMMatrixReadOnly, +}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use dom_struct::dom_struct; -use euclid::Matrix4D; - +use euclid::default::Transform3D; +use js::rust::CustomAutoRooterGuard; +use js::typedarray::{Float32Array, Float64Array}; #[dom_struct] pub struct DOMMatrix { - parent: DOMMatrixReadOnly + parent: DOMMatrixReadOnly, } +#[allow(non_snake_case)] impl DOMMatrix { #[allow(unrooted_must_root)] - pub fn new(global: &GlobalScope, is2D: bool, matrix: Matrix4D<f64>) -> Root<Self> { + pub fn new(global: &GlobalScope, is2D: bool, matrix: Transform3D<f64>) -> DomRoot<Self> { let dommatrix = Self::new_inherited(is2D, matrix); - reflect_dom_object(box dommatrix, global, Wrap) + reflect_dom_object(Box::new(dommatrix), global) } - pub fn new_inherited(is2D: bool, matrix: Matrix4D<f64>) -> Self { + pub fn new_inherited(is2D: bool, matrix: Transform3D<f64>) -> Self { DOMMatrix { - parent: DOMMatrixReadOnly::new_inherited(is2D, matrix) + parent: DOMMatrixReadOnly::new_inherited(is2D, matrix), } } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-dommatrix - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<Self>> { - Self::Constructor_(global, vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0]) + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-dommatrixreadonly + pub fn Constructor( + global: &GlobalScope, + init: Option<StringOrUnrestrictedDoubleSequence>, + ) -> Fallible<DomRoot<Self>> { + if init.is_none() { + return Ok(Self::new(global, true, Transform3D::identity())); + } + match init.unwrap() { + StringOrUnrestrictedDoubleSequence::String(ref s) => { + if global.downcast::<Window>().is_none() { + return Err(error::Error::Type( + "String constructor is only supported in the main thread.".to_owned(), + )); + } + if s.is_empty() { + return Ok(Self::new(global, true, Transform3D::identity())); + } + transform_to_matrix(s.to_string()) + .map(|(is2D, matrix)| Self::new(global, is2D, matrix)) + }, + StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(ref entries) => { + entries_to_matrix(&entries[..]) + .map(|(is2D, matrix)| Self::new(global, is2D, matrix)) + }, + } } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-dommatrix-numbersequence - pub fn Constructor_(global: &GlobalScope, entries: Vec<f64>) -> Fallible<Root<Self>> { - entries_to_matrix(&entries[..]) - .map(|(is2D, matrix)| { - Self::new(global, is2D, matrix) - }) + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-frommatrix + pub fn FromMatrix(global: &GlobalScope, other: &DOMMatrixInit) -> Fallible<DomRoot<Self>> { + dommatrixinit_to_matrix(&other).map(|(is2D, matrix)| Self::new(global, is2D, matrix)) } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-frommatrix - pub fn FromMatrix(global: &GlobalScope, other: &DOMMatrixInit) -> Fallible<Root<Self>> { - dommatrixinit_to_matrix(&other) - .map(|(is2D, matrix)| { - Self::new(global, is2D, matrix) - }) + pub fn from_readonly(global: &GlobalScope, ro: &DOMMatrixReadOnly) -> DomRoot<Self> { + Self::new(global, ro.is2D(), ro.matrix().clone()) } - pub fn from_readonly(global: &GlobalScope, ro: &DOMMatrixReadOnly) -> Root<Self> { - Self::new(global, ro.is_2d(), ro.matrix().clone()) + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-fromfloat32array + pub fn FromFloat32Array( + global: &GlobalScope, + array: CustomAutoRooterGuard<Float32Array>, + ) -> Fallible<DomRoot<DOMMatrix>> { + let vec: Vec<f64> = array.to_vec().iter().map(|&x| x as f64).collect(); + DOMMatrix::Constructor( + global, + Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)), + ) + } + + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-fromfloat64array + pub fn FromFloat64Array( + global: &GlobalScope, + array: CustomAutoRooterGuard<Float64Array>, + ) -> Fallible<DomRoot<DOMMatrix>> { + let vec: Vec<f64> = array.to_vec(); + DOMMatrix::Constructor( + global, + Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)), + ) } } +#[allow(non_snake_case)] impl DOMMatrixMethods for DOMMatrix { - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11 - fn M11(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M11() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11 + fn M11(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M11() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11 - fn SetM11(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m11(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11 + fn SetM11(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m11(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m12 - fn M12(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M12() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m12 + fn M12(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M12() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m12 - fn SetM12(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m12(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m12 + fn SetM12(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m12(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m13 - fn M13(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M13() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m13 + fn M13(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M13() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m13 - fn SetM13(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m13(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m13 + fn SetM13(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m13(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m14 - fn M14(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M14() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m14 + fn M14(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M14() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m14 - fn SetM14(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m14(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m14 + fn SetM14(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m14(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m21 - fn M21(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M21() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m21 + fn M21(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M21() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m21 - fn SetM21(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m21(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m21 + fn SetM21(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m21(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m22 - fn M22(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M22() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m22 + fn M22(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M22() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m22 - fn SetM22(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m22(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m22 + fn SetM22(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m22(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m23 - fn M23(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M23() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m23 + fn M23(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M23() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m23 - fn SetM23(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m23(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m23 + fn SetM23(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m23(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m24 - fn M24(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M24() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m24 + fn M24(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M24() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m24 - fn SetM24(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m24(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m24 + fn SetM24(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m24(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m31 - fn M31(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M31() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m31 + fn M31(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M31() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m31 - fn SetM31(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m31(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m31 + fn SetM31(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m31(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m32 - fn M32(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M32() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m32 + fn M32(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M32() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m32 - fn SetM32(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m32(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m32 + fn SetM32(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m32(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m33 - fn M33(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M33() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m33 + fn M33(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M33() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m33 - fn SetM33(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m33(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m33 + fn SetM33(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m33(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m34 - fn M34(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M34() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m34 + fn M34(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M34() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m34 - fn SetM34(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m34(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m34 + fn SetM34(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m34(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m41 - fn M41(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M41() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m41 + fn M41(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M41() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m41 - fn SetM41(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m41(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m41 + fn SetM41(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m41(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m42 - fn M42(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M42() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m42 + fn M42(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M42() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m42 - fn SetM42(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m42(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m42 + fn SetM42(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m42(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m43 - fn M43(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M43() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m43 + fn M43(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M43() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m43 - fn SetM43(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m43(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m43 + fn SetM43(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m43(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m44 - fn M44(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().M44() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m44 + fn M44(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().M44() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m44 - fn SetM44(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m44(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m44 + fn SetM44(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m44(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-a - fn A(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().A() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-a + fn A(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().A() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-a - fn SetA(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m11(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-a + fn SetA(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m11(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-b - fn B(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().B() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-b + fn B(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().B() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-b - fn SetB(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m12(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-b + fn SetB(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m12(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-c - fn C(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().C() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-c + fn C(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().C() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-c - fn SetC(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m21(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-c + fn SetC(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m21(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-d - fn D(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().D() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-d + fn D(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().D() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-d - fn SetD(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m22(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-d + fn SetD(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m22(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-e - fn E(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().E() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-e + fn E(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().E() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-e - fn SetE(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m41(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-e + fn SetE(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m41(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-f - fn F(&self) -> f64 { - self.upcast::<DOMMatrixReadOnly>().F() - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-f + fn F(&self) -> f64 { + self.upcast::<DOMMatrixReadOnly>().F() + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-f - fn SetF(&self, value: f64) { - self.upcast::<DOMMatrixReadOnly>().set_m42(value); - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-f + fn SetF(&self, value: f64) { + self.upcast::<DOMMatrixReadOnly>().set_m42(value); + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-multiplyself - fn MultiplySelf(&self, other:&DOMMatrixInit) -> Fallible<Root<DOMMatrix>> { - // Steps 1-3. - self.upcast::<DOMMatrixReadOnly>().multiply_self(other) - // Step 4. - .and(Ok(Root::from_ref(&self))) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-multiplyself + fn MultiplySelf(&self, other: &DOMMatrixInit) -> Fallible<DomRoot<DOMMatrix>> { + // Steps 1-3. + self.upcast::<DOMMatrixReadOnly>() + .multiply_self(other) + // Step 4. + .and(Ok(DomRoot::from_ref(&self))) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-premultiplyself - fn PreMultiplySelf(&self, other:&DOMMatrixInit) -> Fallible<Root<DOMMatrix>> { - // Steps 1-3. - self.upcast::<DOMMatrixReadOnly>().pre_multiply_self(other) - // Step 4. - .and(Ok(Root::from_ref(&self))) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-premultiplyself + fn PreMultiplySelf(&self, other: &DOMMatrixInit) -> Fallible<DomRoot<DOMMatrix>> { + // Steps 1-3. + self.upcast::<DOMMatrixReadOnly>() + .pre_multiply_self(other) + // Step 4. + .and(Ok(DomRoot::from_ref(&self))) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-translateself - fn TranslateSelf(&self, tx: f64, ty: f64, tz: f64) -> Root<DOMMatrix> { - // Steps 1-2. - self.upcast::<DOMMatrixReadOnly>().translate_self(tx, ty, tz); - // Step 3. - Root::from_ref(&self) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-translateself + fn TranslateSelf(&self, tx: f64, ty: f64, tz: f64) -> DomRoot<DOMMatrix> { + // Steps 1-2. + self.upcast::<DOMMatrixReadOnly>() + .translate_self(tx, ty, tz); + // Step 3. + DomRoot::from_ref(&self) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-scaleself - fn ScaleSelf(&self, scaleX: f64, scaleY: Option<f64>, scaleZ: f64, - originX: f64, originY: f64, originZ: f64) -> Root<DOMMatrix> { - // Steps 1-6. - self.upcast::<DOMMatrixReadOnly>().scale_self(scaleX, scaleY, scaleZ, originX, originY, originZ); - // Step 7. - Root::from_ref(&self) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-scaleself + fn ScaleSelf( + &self, + scaleX: f64, + scaleY: Option<f64>, + scaleZ: f64, + originX: f64, + originY: f64, + originZ: f64, + ) -> DomRoot<DOMMatrix> { + // Steps 1-6. + self.upcast::<DOMMatrixReadOnly>() + .scale_self(scaleX, scaleY, scaleZ, originX, originY, originZ); + // Step 7. + DomRoot::from_ref(&self) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-scale3dself - fn Scale3dSelf(&self, scale: f64, originX: f64, originY: f64, originZ: f64) -> Root<DOMMatrix> { - // Steps 1-4. - self.upcast::<DOMMatrixReadOnly>().scale_3d_self(scale, originX, originY, originZ); - // Step 5. - Root::from_ref(&self) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-scale3dself + fn Scale3dSelf( + &self, + scale: f64, + originX: f64, + originY: f64, + originZ: f64, + ) -> DomRoot<DOMMatrix> { + // Steps 1-4. + self.upcast::<DOMMatrixReadOnly>() + .scale_3d_self(scale, originX, originY, originZ); + // Step 5. + DomRoot::from_ref(&self) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotateself - fn RotateSelf(&self, rotX: f64, rotY: Option<f64>, rotZ: Option<f64>) -> Root<DOMMatrix> { - // Steps 1-7. - self.upcast::<DOMMatrixReadOnly>().rotate_self(rotX, rotY, rotZ); - // Step 8. - Root::from_ref(&self) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotateself + fn RotateSelf(&self, rotX: f64, rotY: Option<f64>, rotZ: Option<f64>) -> DomRoot<DOMMatrix> { + // Steps 1-7. + self.upcast::<DOMMatrixReadOnly>() + .rotate_self(rotX, rotY, rotZ); + // Step 8. + DomRoot::from_ref(&self) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotatefromvectorself - fn RotateFromVectorSelf(&self, x: f64, y: f64) -> Root<DOMMatrix> { - // Step 1. - self.upcast::<DOMMatrixReadOnly>().rotate_from_vector_self(x, y); - // Step 2. - Root::from_ref(&self) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotatefromvectorself + fn RotateFromVectorSelf(&self, x: f64, y: f64) -> DomRoot<DOMMatrix> { + // Step 1. + self.upcast::<DOMMatrixReadOnly>() + .rotate_from_vector_self(x, y); + // Step 2. + DomRoot::from_ref(&self) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotateaxisangleself - fn RotateAxisAngleSelf(&self, x: f64, y: f64, z: f64, angle: f64) -> Root<DOMMatrix> { - // Steps 1-2. - self.upcast::<DOMMatrixReadOnly>().rotate_axis_angle_self(x, y, z, angle); - // Step 3. - Root::from_ref(&self) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotateaxisangleself + fn RotateAxisAngleSelf(&self, x: f64, y: f64, z: f64, angle: f64) -> DomRoot<DOMMatrix> { + // Steps 1-2. + self.upcast::<DOMMatrixReadOnly>() + .rotate_axis_angle_self(x, y, z, angle); + // Step 3. + DomRoot::from_ref(&self) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewxself - fn SkewXSelf(&self, sx: f64) -> Root<DOMMatrix> { - // Step 1. - self.upcast::<DOMMatrixReadOnly>().skew_x_self(sx); - // Step 2. - Root::from_ref(&self) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewxself + fn SkewXSelf(&self, sx: f64) -> DomRoot<DOMMatrix> { + // Step 1. + self.upcast::<DOMMatrixReadOnly>().skew_x_self(sx); + // Step 2. + DomRoot::from_ref(&self) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewyself - fn SkewYSelf(&self, sy: f64) -> Root<DOMMatrix> { - // Step 1. - self.upcast::<DOMMatrixReadOnly>().skew_y_self(sy); - // Step 2. - Root::from_ref(&self) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewyself + fn SkewYSelf(&self, sy: f64) -> DomRoot<DOMMatrix> { + // Step 1. + self.upcast::<DOMMatrixReadOnly>().skew_y_self(sy); + // Step 2. + DomRoot::from_ref(&self) + } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-invertself - fn InvertSelf(&self) -> Root<DOMMatrix> { - // Steps 1-2. - self.upcast::<DOMMatrixReadOnly>().invert_self(); - // Step 3. - Root::from_ref(&self) - } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-invertself + fn InvertSelf(&self) -> DomRoot<DOMMatrix> { + // Steps 1-2. + self.upcast::<DOMMatrixReadOnly>().invert_self(); + // Step 3. + DomRoot::from_ref(&self) + } } diff --git a/components/script/dom/dommatrixreadonly.rs b/components/script/dom/dommatrixreadonly.rs index 875e8e6f42c..ff724524909 100644 --- a/components/script/dom/dommatrixreadonly.rs +++ b/components/script/dom/dommatrixreadonly.rs @@ -1,71 +1,97 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DOMMatrixBinding::{DOMMatrixInit, DOMMatrixMethods}; -use dom::bindings::codegen::Bindings::DOMMatrixReadOnlyBinding::{DOMMatrixReadOnlyMethods, Wrap}; -use dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit; -use dom::bindings::error; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; -use dom::dommatrix::DOMMatrix; -use dom::dompoint::DOMPoint; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::{DOMMatrixInit, DOMMatrixMethods}; +use crate::dom::bindings::codegen::Bindings::DOMMatrixReadOnlyBinding::DOMMatrixReadOnlyMethods; +use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit; +use crate::dom::bindings::codegen::UnionTypes::StringOrUnrestrictedDoubleSequence; +use crate::dom::bindings::error; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::dommatrix::DOMMatrix; +use crate::dom::dompoint::DOMPoint; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use crate::script_runtime::JSContext; +use cssparser::{Parser, ParserInput}; use dom_struct::dom_struct; -use euclid::{Matrix4D, Point4D, Radians}; -use std::cell::{Cell, Ref}; +use euclid::{default::Transform3D, Angle}; +use js::jsapi::JSObject; +use js::rust::CustomAutoRooterGuard; +use js::typedarray::CreateWith; +use js::typedarray::{Float32Array, Float64Array}; +use std::cell::Cell; use std::f64; +use std::ptr; +use std::ptr::NonNull; +use style::parser::ParserContext; #[dom_struct] +#[allow(non_snake_case)] pub struct DOMMatrixReadOnly { reflector_: Reflector, - matrix: DOMRefCell<Matrix4D<f64>>, + matrix: DomRefCell<Transform3D<f64>>, is2D: Cell<bool>, } +#[allow(non_snake_case)] impl DOMMatrixReadOnly { #[allow(unrooted_must_root)] - pub fn new(global: &GlobalScope, is2D: bool, matrix: Matrix4D<f64>) -> Root<Self> { + pub fn new(global: &GlobalScope, is2D: bool, matrix: Transform3D<f64>) -> DomRoot<Self> { let dommatrix = Self::new_inherited(is2D, matrix); - reflect_dom_object(box dommatrix, global, Wrap) + reflect_dom_object(Box::new(dommatrix), global) } - pub fn new_inherited(is2D: bool, matrix: Matrix4D<f64>) -> Self { + pub fn new_inherited(is2D: bool, matrix: Transform3D<f64>) -> Self { DOMMatrixReadOnly { reflector_: Reflector::new(), - matrix: DOMRefCell::new(matrix), + matrix: DomRefCell::new(matrix), is2D: Cell::new(is2D), } } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-dommatrixreadonly - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<Self>> { - Ok(Self::new(global, true, Matrix4D::identity())) - } - - // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-dommatrixreadonly-numbersequence - pub fn Constructor_(global: &GlobalScope, entries: Vec<f64>) -> Fallible<Root<Self>> { - entries_to_matrix(&entries[..]) - .map(|(is2D, matrix)| { - Self::new(global, is2D, matrix) - }) + pub fn Constructor( + global: &GlobalScope, + init: Option<StringOrUnrestrictedDoubleSequence>, + ) -> Fallible<DomRoot<Self>> { + if init.is_none() { + return Ok(Self::new(global, true, Transform3D::identity())); + } + match init.unwrap() { + StringOrUnrestrictedDoubleSequence::String(ref s) => { + if global.downcast::<Window>().is_none() { + return Err(error::Error::Type( + "String constructor is only supported in the main thread.".to_owned(), + )); + } + if s.is_empty() { + return Ok(Self::new(global, true, Transform3D::identity())); + } + transform_to_matrix(s.to_string()) + .map(|(is2D, matrix)| Self::new(global, is2D, matrix)) + }, + StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(ref entries) => { + entries_to_matrix(&entries[..]) + .map(|(is2D, matrix)| Self::new(global, is2D, matrix)) + }, + } } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-frommatrix - pub fn FromMatrix(global: &GlobalScope, other: &DOMMatrixInit) -> Fallible<Root<Self>> { - dommatrixinit_to_matrix(&other) - .map(|(is2D, matrix)| { - Self::new(global, is2D, matrix) - }) + pub fn FromMatrix(global: &GlobalScope, other: &DOMMatrixInit) -> Fallible<DomRoot<Self>> { + dommatrixinit_to_matrix(&other).map(|(is2D, matrix)| Self::new(global, is2D, matrix)) } - pub fn matrix(&self) -> Ref<Matrix4D<f64>> { + pub fn matrix(&self) -> Ref<Transform3D<f64>> { self.matrix.borrow() } - pub fn is_2d(&self) -> bool { + pub fn is2D(&self) -> bool { self.is2D.get() } @@ -149,14 +175,13 @@ impl DOMMatrixReadOnly { self.matrix.borrow_mut().m44 = value; } - // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-multiplyself pub fn multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> { // Step 1. dommatrixinit_to_matrix(&other).map(|(is2D, other_matrix)| { // Step 2. let mut matrix = self.matrix.borrow_mut(); - *matrix = other_matrix.post_mul(&matrix); + *matrix = other_matrix.post_transform(&matrix); // Step 3. if !is2D { self.is2D.set(false); @@ -171,7 +196,7 @@ impl DOMMatrixReadOnly { dommatrixinit_to_matrix(&other).map(|(is2D, other_matrix)| { // Step 2. let mut matrix = self.matrix.borrow_mut(); - *matrix = other_matrix.pre_mul(&matrix); + *matrix = other_matrix.pre_transform(&matrix); // Step 3. if !is2D { self.is2D.set(false); @@ -183,9 +208,9 @@ impl DOMMatrixReadOnly { // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-translateself pub fn translate_self(&self, tx: f64, ty: f64, tz: f64) { // Step 1. - let translation = Matrix4D::create_translation(tx, ty, tz); + let translation = Transform3D::create_translation(tx, ty, tz); let mut matrix = self.matrix.borrow_mut(); - *matrix = translation.post_mul(&matrix); + *matrix = translation.post_transform(&matrix); // Step 2. if tz != 0.0 { self.is2D.set(false); @@ -194,17 +219,24 @@ impl DOMMatrixReadOnly { } // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-scaleself - pub fn scale_self(&self, scaleX: f64, scaleY: Option<f64>, scaleZ: f64, - mut originX: f64, mut originY: f64, mut originZ: f64) { + pub fn scale_self( + &self, + scaleX: f64, + scaleY: Option<f64>, + scaleZ: f64, + mut originX: f64, + mut originY: f64, + mut originZ: f64, + ) { // Step 1. self.translate_self(originX, originY, originZ); // Step 2. let scaleY = scaleY.unwrap_or(scaleX); // Step 3. { - let scale3D = Matrix4D::create_scale(scaleX, scaleY, scaleZ); + let scale3D = Transform3D::create_scale(scaleX, scaleY, scaleZ); let mut matrix = self.matrix.borrow_mut(); - *matrix = scale3D.post_mul(&matrix); + *matrix = scale3D.post_transform(&matrix); } // Step 4. originX = -originX; @@ -225,9 +257,9 @@ impl DOMMatrixReadOnly { self.translate_self(originX, originY, originZ); // Step 2. { - let scale3D = Matrix4D::create_scale(scale, scale, scale); + let scale3D = Transform3D::create_scale(scale, scale, scale); let mut matrix = self.matrix.borrow_mut(); - *matrix = scale3D.post_mul(&matrix); + *matrix = scale3D.post_transform(&matrix); } // Step 3. self.translate_self(-originX, -originY, -originZ); @@ -256,21 +288,27 @@ impl DOMMatrixReadOnly { } if rotZ != 0.0 { // Step 5. - let rotation = Matrix4D::create_rotation(0.0, 0.0, 1.0, Radians::new(rotZ.to_radians())); + // Beware: pass negated value until https://github.com/servo/euclid/issues/354 + let rotation = + Transform3D::create_rotation(0.0, 0.0, -1.0, Angle::radians(rotZ.to_radians())); let mut matrix = self.matrix.borrow_mut(); - *matrix = rotation.post_mul(&matrix); + *matrix = rotation.post_transform(&matrix); } if rotY != 0.0 { // Step 6. - let rotation = Matrix4D::create_rotation(0.0, 1.0, 0.0, Radians::new(rotY.to_radians())); + // Beware: pass negated value until https://github.com/servo/euclid/issues/354 + let rotation = + Transform3D::create_rotation(0.0, -1.0, 0.0, Angle::radians(rotY.to_radians())); let mut matrix = self.matrix.borrow_mut(); - *matrix = rotation.post_mul(&matrix); + *matrix = rotation.post_transform(&matrix); } if rotX != 0.0 { // Step 7. - let rotation = Matrix4D::create_rotation(1.0, 0.0, 0.0, Radians::new(rotX.to_radians())); + // Beware: pass negated value until https://github.com/servo/euclid/issues/354 + let rotation = + Transform3D::create_rotation(-1.0, 0.0, 0.0, Angle::radians(rotX.to_radians())); let mut matrix = self.matrix.borrow_mut(); - *matrix = rotation.post_mul(&matrix); + *matrix = rotation.post_transform(&matrix); } // Step 8 in DOMMatrix.RotateSelf } @@ -280,10 +318,11 @@ impl DOMMatrixReadOnly { // don't do anything when the rotation angle is zero or undefined if y != 0.0 || x < 0.0 { // Step 1. - let rotZ = Radians::new(f64::atan2(y, x)); - let rotation = Matrix4D::create_rotation(0.0, 0.0, 1.0, rotZ); + let rotZ = Angle::radians(f64::atan2(y, x)); + // Beware: pass negated value until https://github.com/servo/euclid/issues/354 + let rotation = Transform3D::create_rotation(0.0, 0.0, -1.0, rotZ); let mut matrix = self.matrix.borrow_mut(); - *matrix = rotation.post_mul(&matrix); + *matrix = rotation.post_transform(&matrix); } // Step 2 in DOMMatrix.RotateFromVectorSelf } @@ -292,9 +331,15 @@ impl DOMMatrixReadOnly { pub fn rotate_axis_angle_self(&self, x: f64, y: f64, z: f64, angle: f64) { // Step 1. let (norm_x, norm_y, norm_z) = normalize_point(x, y, z); - let rotation = Matrix4D::create_rotation(norm_x, norm_y, norm_z, Radians::new(angle.to_radians())); + // Beware: pass negated value until https://github.com/servo/euclid/issues/354 + let rotation = Transform3D::create_rotation( + -norm_x, + -norm_y, + -norm_z, + Angle::radians(angle.to_radians()), + ); let mut matrix = self.matrix.borrow_mut(); - *matrix = rotation.post_mul(&matrix); + *matrix = rotation.post_transform(&matrix); // Step 2. if x != 0.0 || y != 0.0 { self.is2D.set(false); @@ -305,18 +350,18 @@ impl DOMMatrixReadOnly { // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewxself pub fn skew_x_self(&self, sx: f64) { // Step 1. - let skew = Matrix4D::create_skew(Radians::new(sx.to_radians()), Radians::new(0.0)); + let skew = Transform3D::create_skew(Angle::radians(sx.to_radians()), Angle::radians(0.0)); let mut matrix = self.matrix.borrow_mut(); - *matrix = skew.post_mul(&matrix); + *matrix = skew.post_transform(&matrix); // Step 2 in DOMMatrix.SkewXSelf } // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewyself pub fn skew_y_self(&self, sy: f64) { // Step 1. - let skew = Matrix4D::create_skew(Radians::new(0.0), Radians::new(sy.to_radians())); + let skew = Transform3D::create_skew(Angle::radians(0.0), Angle::radians(sy.to_radians())); let mut matrix = self.matrix.borrow_mut(); - *matrix = skew.post_mul(&matrix); + *matrix = skew.post_transform(&matrix); // Step 2 in DOMMatrix.SkewYSelf } @@ -327,16 +372,56 @@ impl DOMMatrixReadOnly { *matrix = matrix.inverse().unwrap_or_else(|| { // Step 2. self.is2D.set(false); - Matrix4D::row_major(f64::NAN, f64::NAN, f64::NAN, f64::NAN, - f64::NAN, f64::NAN, f64::NAN, f64::NAN, - f64::NAN, f64::NAN, f64::NAN, f64::NAN, - f64::NAN, f64::NAN, f64::NAN, f64::NAN) + Transform3D::row_major( + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + f64::NAN, + ) }) // Step 3 in DOMMatrix.InvertSelf } -} + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-fromfloat32array + #[allow(unsafe_code)] + pub fn FromFloat32Array( + global: &GlobalScope, + array: CustomAutoRooterGuard<Float32Array>, + ) -> Fallible<DomRoot<DOMMatrixReadOnly>> { + let vec: Vec<f64> = array.to_vec().iter().map(|&x| x as f64).collect(); + DOMMatrixReadOnly::Constructor( + global, + Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)), + ) + } + + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-fromfloat64array + #[allow(unsafe_code)] + pub fn FromFloat64Array( + global: &GlobalScope, + array: CustomAutoRooterGuard<Float64Array>, + ) -> Fallible<DomRoot<DOMMatrixReadOnly>> { + let vec: Vec<f64> = array.to_vec(); + DOMMatrixReadOnly::Constructor( + global, + Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)), + ) + } +} +#[allow(non_snake_case)] impl DOMMatrixReadOnlyMethods for DOMMatrixReadOnly { // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11 fn M11(&self) -> f64 { @@ -456,120 +541,193 @@ impl DOMMatrixReadOnlyMethods for DOMMatrixReadOnly { // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-isidentity fn IsIdentity(&self) -> bool { let matrix = self.matrix.borrow(); - matrix.m12 == 0.0 && matrix.m13 == 0.0 && matrix.m14 == 0.0 && matrix.m21 == 0.0 && - matrix.m23 == 0.0 && matrix.m24 == 0.0 && matrix.m31 == 0.0 && matrix.m32 == 0.0 && - matrix.m34 == 0.0 && matrix.m41 == 0.0 && matrix.m42 == 0.0 && matrix.m43 == 0.0 && - matrix.m11 == 1.0 && matrix.m22 == 1.0 && matrix.m33 == 1.0 && matrix.m44 == 1.0 + matrix.m12 == 0.0 && + matrix.m13 == 0.0 && + matrix.m14 == 0.0 && + matrix.m21 == 0.0 && + matrix.m23 == 0.0 && + matrix.m24 == 0.0 && + matrix.m31 == 0.0 && + matrix.m32 == 0.0 && + matrix.m34 == 0.0 && + matrix.m41 == 0.0 && + matrix.m42 == 0.0 && + matrix.m43 == 0.0 && + matrix.m11 == 1.0 && + matrix.m22 == 1.0 && + matrix.m33 == 1.0 && + matrix.m44 == 1.0 } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-translate - fn Translate(&self, tx: f64, ty: f64, tz: f64) -> Root<DOMMatrix> { + fn Translate(&self, tx: f64, ty: f64, tz: f64) -> DomRoot<DOMMatrix> { DOMMatrix::from_readonly(&self.global(), self).TranslateSelf(tx, ty, tz) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-scale - fn Scale(&self, scaleX: f64, scaleY: Option<f64>, scaleZ: f64, - originX: f64, originY: f64, originZ: f64) -> Root<DOMMatrix> { + fn Scale( + &self, + scaleX: f64, + scaleY: Option<f64>, + scaleZ: f64, + originX: f64, + originY: f64, + originZ: f64, + ) -> DomRoot<DOMMatrix> { DOMMatrix::from_readonly(&self.global(), self) .ScaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ) } + // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-scalenonuniform + fn ScaleNonUniform(&self, scaleX: f64, scaleY: f64) -> DomRoot<DOMMatrix> { + DOMMatrix::from_readonly(&self.global(), self).ScaleSelf( + scaleX, + Some(scaleY), + 1.0, + 0.0, + 0.0, + 0.0, + ) + } + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-scale3d - fn Scale3d(&self, scale: f64, originX: f64, originY: f64, originZ: f64) -> Root<DOMMatrix> { - DOMMatrix::from_readonly(&self.global(), self) - .Scale3dSelf(scale, originX, originY, originZ) + fn Scale3d(&self, scale: f64, originX: f64, originY: f64, originZ: f64) -> DomRoot<DOMMatrix> { + DOMMatrix::from_readonly(&self.global(), self).Scale3dSelf(scale, originX, originY, originZ) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotate - fn Rotate(&self, rotX: f64, rotY: Option<f64>, rotZ: Option<f64>) -> Root<DOMMatrix> { + fn Rotate(&self, rotX: f64, rotY: Option<f64>, rotZ: Option<f64>) -> DomRoot<DOMMatrix> { DOMMatrix::from_readonly(&self.global(), self).RotateSelf(rotX, rotY, rotZ) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotatefromvector - fn RotateFromVector(&self, x: f64, y: f64) -> Root<DOMMatrix> { + fn RotateFromVector(&self, x: f64, y: f64) -> DomRoot<DOMMatrix> { DOMMatrix::from_readonly(&self.global(), self).RotateFromVectorSelf(x, y) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotateaxisangle - fn RotateAxisAngle(&self, x: f64, y: f64, z: f64, angle: f64) -> Root<DOMMatrix> { + fn RotateAxisAngle(&self, x: f64, y: f64, z: f64, angle: f64) -> DomRoot<DOMMatrix> { DOMMatrix::from_readonly(&self.global(), self).RotateAxisAngleSelf(x, y, z, angle) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-skewx - fn SkewX(&self, sx: f64) -> Root<DOMMatrix> { + fn SkewX(&self, sx: f64) -> DomRoot<DOMMatrix> { DOMMatrix::from_readonly(&self.global(), self).SkewXSelf(sx) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-skewy - fn SkewY(&self, sy: f64) -> Root<DOMMatrix> { + fn SkewY(&self, sy: f64) -> DomRoot<DOMMatrix> { DOMMatrix::from_readonly(&self.global(), self).SkewYSelf(sy) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-multiply - fn Multiply(&self, other: &DOMMatrixInit) -> Fallible<Root<DOMMatrix>> { + fn Multiply(&self, other: &DOMMatrixInit) -> Fallible<DomRoot<DOMMatrix>> { DOMMatrix::from_readonly(&self.global(), self).MultiplySelf(&other) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-flipx - fn FlipX(&self) -> Root<DOMMatrix> { + fn FlipX(&self) -> DomRoot<DOMMatrix> { let is2D = self.is2D.get(); - let flip = Matrix4D::row_major(-1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0); - let matrix = flip.post_mul(&self.matrix.borrow()); + let flip = Transform3D::row_major( + -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + ); + let matrix = flip.post_transform(&self.matrix.borrow()); DOMMatrix::new(&self.global(), is2D, matrix) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-flipy - fn FlipY(&self) -> Root<DOMMatrix> { + fn FlipY(&self) -> DomRoot<DOMMatrix> { let is2D = self.is2D.get(); - let flip = Matrix4D::row_major(1.0, 0.0, 0.0, 0.0, - 0.0, -1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0); - let matrix = flip.post_mul(&self.matrix.borrow()); + let flip = Transform3D::row_major( + 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + ); + let matrix = flip.post_transform(&self.matrix.borrow()); DOMMatrix::new(&self.global(), is2D, matrix) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-inverse - fn Inverse(&self) -> Root<DOMMatrix> { + fn Inverse(&self) -> DomRoot<DOMMatrix> { DOMMatrix::from_readonly(&self.global(), self).InvertSelf() } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-transformpoint - fn TransformPoint(&self, point: &DOMPointInit) -> Root<DOMPoint> { - let matrix = self.matrix.borrow(); - let result = matrix.transform_point4d(&Point4D::new(point.x, point.y, point.z, point.w)); - DOMPoint::new( - &self.global(), - result.x as f64, - result.y as f64, - result.z as f64, - result.w as f64) + fn TransformPoint(&self, point: &DOMPointInit) -> DomRoot<DOMPoint> { + // Euclid always normalizes the homogeneous coordinate which is usually the right + // thing but may (?) not be compliant with the CSS matrix spec (or at least is + // probably not the behavior web authors will expect even if it is mathematically + // correct in the context of geometry computations). + // Since this is the only place where this is needed, better implement it here + // than in euclid (which does not have a notion of 4d points). + let mat = self.matrix.borrow(); + let x = point.x * mat.m11 + point.y * mat.m21 + point.z * mat.m31 + point.w * mat.m41; + let y = point.x * mat.m12 + point.y * mat.m22 + point.z * mat.m32 + point.w * mat.m42; + let z = point.x * mat.m13 + point.y * mat.m23 + point.z * mat.m33 + point.w * mat.m43; + let w = point.x * mat.m14 + point.y * mat.m24 + point.z * mat.m34 + point.w * mat.m44; + + DOMPoint::new(&self.global(), x, y, z, w) + } + + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat32array + #[allow(unsafe_code)] + fn ToFloat32Array(&self, cx: JSContext) -> NonNull<JSObject> { + let vec: Vec<f32> = self + .matrix + .borrow() + .to_row_major_array() + .iter() + .map(|&x| x as f32) + .collect(); + unsafe { + rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); + let _ = Float32Array::create(*cx, CreateWith::Slice(&vec), array.handle_mut()).unwrap(); + NonNull::new_unchecked(array.get()) + } } -} + // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat64array + #[allow(unsafe_code)] + fn ToFloat64Array(&self, cx: JSContext) -> NonNull<JSObject> { + let arr = self.matrix.borrow().to_row_major_array(); + unsafe { + rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); + let _ = Float64Array::create(*cx, CreateWith::Slice(&arr), array.handle_mut()).unwrap(); + NonNull::new_unchecked(array.get()) + } + } +} // https://drafts.fxtf.org/geometry-1/#create-a-2d-matrix -fn create_2d_matrix(entries: &[f64]) -> Matrix4D<f64> { - Matrix4D::row_major(entries[0], entries[1], 0.0, 0.0, - entries[2], entries[3], 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - entries[4], entries[5], 0.0, 1.0) +fn create_2d_matrix(entries: &[f64]) -> Transform3D<f64> { + Transform3D::row_major( + entries[0], entries[1], 0.0, 0.0, entries[2], entries[3], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, + entries[4], entries[5], 0.0, 1.0, + ) } - // https://drafts.fxtf.org/geometry-1/#create-a-3d-matrix -fn create_3d_matrix(entries: &[f64]) -> Matrix4D<f64> { - Matrix4D::row_major(entries[0], entries[1], entries[2], entries[3], - entries[4], entries[5], entries[6], entries[7], - entries[8], entries[9], entries[10], entries[11], - entries[12], entries[13], entries[14], entries[15]) +fn create_3d_matrix(entries: &[f64]) -> Transform3D<f64> { + Transform3D::row_major( + entries[0], + entries[1], + entries[2], + entries[3], + entries[4], + entries[5], + entries[6], + entries[7], + entries[8], + entries[9], + entries[10], + entries[11], + entries[12], + entries[13], + entries[14], + entries[15], + ) } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-dommatrixreadonly-numbersequence -pub fn entries_to_matrix(entries: &[f64]) -> Fallible<(bool, Matrix4D<f64>)> { +pub fn entries_to_matrix(entries: &[f64]) -> Fallible<(bool, Transform3D<f64>)> { if entries.len() == 6 { Ok((true, create_2d_matrix(&entries))) } else if entries.len() == 16 { @@ -580,23 +738,31 @@ pub fn entries_to_matrix(entries: &[f64]) -> Fallible<(bool, Matrix4D<f64>)> { } } - // https://drafts.fxtf.org/geometry-1/#validate-and-fixup -pub fn dommatrixinit_to_matrix(dict: &DOMMatrixInit) -> Fallible<(bool, Matrix4D<f64>)> { +pub fn dommatrixinit_to_matrix(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> { // Step 1. if dict.a.is_some() && dict.m11.is_some() && dict.a.unwrap() != dict.m11.unwrap() || - dict.b.is_some() && dict.m12.is_some() && dict.b.unwrap() != dict.m12.unwrap() || - dict.c.is_some() && dict.m21.is_some() && dict.c.unwrap() != dict.m21.unwrap() || - dict.d.is_some() && dict.m22.is_some() && dict.d.unwrap() != dict.m22.unwrap() || - dict.e.is_some() && dict.m41.is_some() && dict.e.unwrap() != dict.m41.unwrap() || - dict.f.is_some() && dict.m42.is_some() && dict.f.unwrap() != dict.m42.unwrap() || - dict.is2D.is_some() && dict.is2D.unwrap() && - (dict.m31 != 0.0 || dict.m32 != 0.0 || dict.m13 != 0.0 || dict.m23 != 0.0 || - dict.m43 != 0.0 || dict.m14 != 0.0 || dict.m24 != 0.0 || dict.m34 != 0.0 || - dict.m33 != 1.0 || dict.m44 != 1.0) { - Err(error::Error::Type("Invalid matrix initializer.".to_owned())) + dict.b.is_some() && dict.m12.is_some() && dict.b.unwrap() != dict.m12.unwrap() || + dict.c.is_some() && dict.m21.is_some() && dict.c.unwrap() != dict.m21.unwrap() || + dict.d.is_some() && dict.m22.is_some() && dict.d.unwrap() != dict.m22.unwrap() || + dict.e.is_some() && dict.m41.is_some() && dict.e.unwrap() != dict.m41.unwrap() || + dict.f.is_some() && dict.m42.is_some() && dict.f.unwrap() != dict.m42.unwrap() || + dict.is2D.is_some() && + dict.is2D.unwrap() && + (dict.m31 != 0.0 || + dict.m32 != 0.0 || + dict.m13 != 0.0 || + dict.m23 != 0.0 || + dict.m43 != 0.0 || + dict.m14 != 0.0 || + dict.m24 != 0.0 || + dict.m34 != 0.0 || + dict.m33 != 1.0 || + dict.m44 != 1.0) + { + Err(error::Error::Type("Invalid matrix initializer.".to_owned())) } else { - let mut is2D = dict.is2D; + let mut is_2d = dict.is2D; // Step 2. let m11 = dict.m11.unwrap_or(dict.a.unwrap_or(1.0)); // Step 3. @@ -610,26 +776,32 @@ pub fn dommatrixinit_to_matrix(dict: &DOMMatrixInit) -> Fallible<(bool, Matrix4D // Step 7. let m42 = dict.m42.unwrap_or(dict.f.unwrap_or(0.0)); // Step 8. - if is2D.is_none() && - (dict.m31 != 0.0 || dict.m32 != 0.0 || dict.m13 != 0.0 || - dict.m23 != 0.0 || dict.m43 != 0.0 || dict.m14 != 0.0 || - dict.m24 != 0.0 || dict.m34 != 0.0 || - dict.m33 != 1.0 || dict.m44 != 1.0) { - is2D = Some(false); + if is_2d.is_none() && + (dict.m31 != 0.0 || + dict.m32 != 0.0 || + dict.m13 != 0.0 || + dict.m23 != 0.0 || + dict.m43 != 0.0 || + dict.m14 != 0.0 || + dict.m24 != 0.0 || + dict.m34 != 0.0 || + dict.m33 != 1.0 || + dict.m44 != 1.0) + { + is_2d = Some(false); } // Step 9. - if is2D.is_none() { - is2D = Some(true); + if is_2d.is_none() { + is_2d = Some(true); } - let matrix = Matrix4D::row_major(m11, m12, dict.m13, dict.m14, - m21, m22, dict.m23, dict.m24, - dict.m31, dict.m32, dict.m33, dict.m34, - m41, m42, dict.m43, dict.m44); - Ok((is2D.unwrap(), matrix)) + let matrix = Transform3D::row_major( + m11, m12, dict.m13, dict.m14, m21, m22, dict.m23, dict.m24, dict.m31, dict.m32, + dict.m33, dict.m34, m41, m42, dict.m43, dict.m44, + ); + Ok((is_2d.unwrap(), matrix)) } } - #[inline] fn normalize_point(x: f64, y: f64, z: f64) -> (f64, f64, f64) { let len = (x * x + y * y + z * z).sqrt(); @@ -639,3 +811,32 @@ fn normalize_point(x: f64, y: f64, z: f64) -> (f64, f64, f64) { (x / len, y / len, z / len) } } + +pub fn transform_to_matrix(value: String) -> Fallible<(bool, Transform3D<f64>)> { + use style::properties::longhands::transform; + + let mut input = ParserInput::new(&value); + let mut parser = Parser::new(&mut input); + let url = ::servo_url::ServoUrl::parse("about:blank").unwrap(); + let context = ParserContext::new( + ::style::stylesheets::Origin::Author, + &url, + Some(::style::stylesheets::CssRuleType::Style), + ::style_traits::ParsingMode::DEFAULT, + ::style::context::QuirksMode::NoQuirks, + None, + None, + ); + + let transform = match parser.parse_entirely(|t| transform::parse(&context, t)) { + Ok(result) => result, + Err(..) => return Err(error::Error::Syntax), + }; + + let (m, is_3d) = match transform.to_transform_3d_matrix_f64(None) { + Ok(result) => result, + Err(..) => return Err(error::Error::Syntax), + }; + + Ok((!is_3d, m)) +} diff --git a/components/script/dom/domparser.rs b/components/script/dom/domparser.rs index 4af6ebf8c60..876d87eef25 100644 --- a/components/script/dom/domparser.rs +++ b/components/script/dom/domparser.rs @@ -1,97 +1,106 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use document_loader::DocumentLoader; -use dom::bindings::codegen::Bindings::DOMParserBinding; -use dom::bindings::codegen::Bindings::DOMParserBinding::DOMParserMethods; -use dom::bindings::codegen::Bindings::DOMParserBinding::SupportedType::Application_xhtml_xml; -use dom::bindings::codegen::Bindings::DOMParserBinding::SupportedType::Application_xml; -use dom::bindings::codegen::Bindings::DOMParserBinding::SupportedType::Text_html; -use dom::bindings::codegen::Bindings::DOMParserBinding::SupportedType::Text_xml; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentReadyState; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::Fallible; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::document::{Document, HasBrowsingContext, IsHTMLDocument}; -use dom::document::DocumentSource; -use dom::servoparser::ServoParser; -use dom::window::Window; +use crate::document_loader::DocumentLoader; +use crate::dom::bindings::codegen::Bindings::DOMParserBinding; +use crate::dom::bindings::codegen::Bindings::DOMParserBinding::DOMParserMethods; +use crate::dom::bindings::codegen::Bindings::DOMParserBinding::SupportedType::Application_xhtml_xml; +use crate::dom::bindings::codegen::Bindings::DOMParserBinding::SupportedType::Application_xml; +use crate::dom::bindings::codegen::Bindings::DOMParserBinding::SupportedType::Text_html; +use crate::dom::bindings::codegen::Bindings::DOMParserBinding::SupportedType::Text_xml; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentReadyState; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::DocumentSource; +use crate::dom::document::{Document, HasBrowsingContext, IsHTMLDocument}; +use crate::dom::servoparser::ServoParser; +use crate::dom::window::Window; use dom_struct::dom_struct; use script_traits::DocumentActivity; #[dom_struct] pub struct DOMParser { reflector_: Reflector, - window: JS<Window>, // XXXjdm Document instead? + window: Dom<Window>, // XXXjdm Document instead? } impl DOMParser { fn new_inherited(window: &Window) -> DOMParser { DOMParser { reflector_: Reflector::new(), - window: JS::from_ref(window), + window: Dom::from_ref(window), } } - pub fn new(window: &Window) -> Root<DOMParser> { - reflect_dom_object(box DOMParser::new_inherited(window), - window, - DOMParserBinding::Wrap) + pub fn new(window: &Window) -> DomRoot<DOMParser> { + reflect_dom_object(Box::new(DOMParser::new_inherited(window)), window) } - pub fn Constructor(window: &Window) -> Fallible<Root<DOMParser>> { + #[allow(non_snake_case)] + pub fn Constructor(window: &Window) -> Fallible<DomRoot<DOMParser>> { Ok(DOMParser::new(window)) } } impl DOMParserMethods for DOMParser { // https://w3c.github.io/DOM-Parsing/#the-domparser-interface - fn ParseFromString(&self, - s: DOMString, - ty: DOMParserBinding::SupportedType) - -> Fallible<Root<Document>> { + fn ParseFromString( + &self, + s: DOMString, + ty: DOMParserBinding::SupportedType, + ) -> Fallible<DomRoot<Document>> { let url = self.window.get_url(); - let content_type = DOMString::from(ty.as_str()); + let content_type = ty + .as_str() + .parse() + .expect("Supported type is not a MIME type"); let doc = self.window.Document(); let loader = DocumentLoader::new(&*doc.loader()); match ty { Text_html => { - let document = Document::new(&self.window, - HasBrowsingContext::No, - Some(url.clone()), - doc.origin().clone(), - IsHTMLDocument::HTMLDocument, - Some(content_type), - None, - DocumentActivity::Inactive, - DocumentSource::FromParser, - loader, - None, - None); - ServoParser::parse_html_document(&document, s, url); + let document = Document::new( + &self.window, + HasBrowsingContext::No, + Some(url.clone()), + doc.origin().clone(), + IsHTMLDocument::HTMLDocument, + Some(content_type), + None, + DocumentActivity::Inactive, + DocumentSource::FromParser, + loader, + None, + None, + Default::default(), + ); + ServoParser::parse_html_document(&document, Some(s), url); document.set_ready_state(DocumentReadyState::Complete); Ok(document) - } + }, Text_xml | Application_xml | Application_xhtml_xml => { - // FIXME: this should probably be FromParser when we actually parse the string (#3756). - let document = Document::new(&self.window, - HasBrowsingContext::No, - Some(url.clone()), - doc.origin().clone(), - IsHTMLDocument::NonHTMLDocument, - Some(content_type), - None, - DocumentActivity::Inactive, - DocumentSource::NotFromParser, - loader, - None, - None); - ServoParser::parse_xml_document(&document, s, url); + let document = Document::new( + &self.window, + HasBrowsingContext::No, + Some(url.clone()), + doc.origin().clone(), + IsHTMLDocument::NonHTMLDocument, + Some(content_type), + None, + DocumentActivity::Inactive, + DocumentSource::FromParser, + loader, + None, + None, + Default::default(), + ); + ServoParser::parse_xml_document(&document, Some(s), url); + document.set_ready_state(DocumentReadyState::Complete); Ok(document) - } + }, } } } diff --git a/components/script/dom/dompoint.rs b/components/script/dom/dompoint.rs index 0a2ed9cec5d..fe523b8e766 100644 --- a/components/script/dom/dompoint.rs +++ b/components/script/dom/dompoint.rs @@ -1,14 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DOMPointBinding::{DOMPointInit, DOMPointMethods, Wrap}; -use dom::bindings::codegen::Bindings::DOMPointReadOnlyBinding::DOMPointReadOnlyMethods; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::dompointreadonly::{DOMPointReadOnly, DOMPointWriteMethods}; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::DOMPointBinding::{DOMPointInit, DOMPointMethods}; +use crate::dom::bindings::codegen::Bindings::DOMPointReadOnlyBinding::DOMPointReadOnlyMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::dompointreadonly::{DOMPointReadOnly, DOMPointWriteMethods}; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; // http://dev.w3.org/fxtf/geometry/Overview.html#dompoint @@ -17,6 +17,7 @@ pub struct DOMPoint { point: DOMPointReadOnly, } +#[allow(non_snake_case)] impl DOMPoint { fn new_inherited(x: f64, y: f64, z: f64, w: f64) -> DOMPoint { DOMPoint { @@ -24,20 +25,26 @@ impl DOMPoint { } } - pub fn new(global: &GlobalScope, x: f64, y: f64, z: f64, w: f64) -> Root<DOMPoint> { - reflect_dom_object(box DOMPoint::new_inherited(x, y, z, w), global, Wrap) + pub fn new(global: &GlobalScope, x: f64, y: f64, z: f64, w: f64) -> DomRoot<DOMPoint> { + reflect_dom_object(Box::new(DOMPoint::new_inherited(x, y, z, w)), global) } - pub fn Constructor(global: &GlobalScope, - x: f64, - y: f64, - z: f64, - w: f64) - -> Fallible<Root<DOMPoint>> { + pub fn Constructor( + global: &GlobalScope, + x: f64, + y: f64, + z: f64, + w: f64, + ) -> Fallible<DomRoot<DOMPoint>> { Ok(DOMPoint::new(global, x, y, z, w)) } - pub fn new_from_init(global: &GlobalScope, p: &DOMPointInit) -> Root<DOMPoint> { + // https://drafts.fxtf.org/geometry/#dom-dompoint-frompoint + pub fn FromPoint(global: &GlobalScope, init: &DOMPointInit) -> DomRoot<Self> { + Self::new_from_init(global, init) + } + + pub fn new_from_init(global: &GlobalScope, p: &DOMPointInit) -> DomRoot<DOMPoint> { DOMPoint::new(global, p.x, p.y, p.z, p.w) } } diff --git a/components/script/dom/dompointreadonly.rs b/components/script/dom/dompointreadonly.rs index 7180207c930..9d093db2b81 100644 --- a/components/script/dom/dompointreadonly.rs +++ b/components/script/dom/dompointreadonly.rs @@ -1,12 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DOMPointReadOnlyBinding::{DOMPointReadOnlyMethods, Wrap}; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit; +use crate::dom::bindings::codegen::Bindings::DOMPointReadOnlyBinding::DOMPointReadOnlyMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; use std::cell::Cell; @@ -20,6 +21,7 @@ pub struct DOMPointReadOnly { w: Cell<f64>, } +#[allow(non_snake_case)] impl DOMPointReadOnly { pub fn new_inherited(x: f64, y: f64, z: f64, w: f64) -> DOMPointReadOnly { DOMPointReadOnly { @@ -31,22 +33,30 @@ impl DOMPointReadOnly { } } - pub fn new(global: &GlobalScope, x: f64, y: f64, z: f64, w: f64) -> Root<DOMPointReadOnly> { - reflect_dom_object(box DOMPointReadOnly::new_inherited(x, y, z, w), - global, - Wrap) + pub fn new(global: &GlobalScope, x: f64, y: f64, z: f64, w: f64) -> DomRoot<DOMPointReadOnly> { + reflect_dom_object( + Box::new(DOMPointReadOnly::new_inherited(x, y, z, w)), + global, + ) } - pub fn Constructor(global: &GlobalScope, - x: f64, - y: f64, - z: f64, - w: f64) - -> Fallible<Root<DOMPointReadOnly>> { + pub fn Constructor( + global: &GlobalScope, + x: f64, + y: f64, + z: f64, + w: f64, + ) -> Fallible<DomRoot<DOMPointReadOnly>> { Ok(DOMPointReadOnly::new(global, x, y, z, w)) } + + // https://drafts.fxtf.org/geometry/#dom-dompointreadonly-frompoint + pub fn FromPoint(global: &GlobalScope, init: &DOMPointInit) -> DomRoot<Self> { + Self::new(global, init.x, init.y, init.z, init.w) + } } +#[allow(non_snake_case)] impl DOMPointReadOnlyMethods for DOMPointReadOnly { // https://dev.w3.org/fxtf/geometry/Overview.html#dom-dompointreadonly-x fn X(&self) -> f64 { @@ -69,6 +79,7 @@ impl DOMPointReadOnlyMethods for DOMPointReadOnly { } } +#[allow(non_snake_case)] pub trait DOMPointWriteMethods { fn SetX(&self, value: f64); fn SetY(&self, value: f64); diff --git a/components/script/dom/domquad.rs b/components/script/dom/domquad.rs index 8c665731f84..e6decd7e1b2 100644 --- a/components/script/dom/domquad.rs +++ b/components/script/dom/domquad.rs @@ -1,117 +1,143 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DOMPointBinding::{DOMPointInit, DOMPointMethods}; -use dom::bindings::codegen::Bindings::DOMQuadBinding::{DOMQuadInit, DOMQuadMethods, Wrap}; -use dom::bindings::codegen::Bindings::DOMRectReadOnlyBinding::DOMRectInit; -use dom::bindings::error::Fallible; -use dom::bindings::js::{Root, JS}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::dompoint::DOMPoint; -use dom::domrect::DOMRect; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::DOMPointBinding::{DOMPointInit, DOMPointMethods}; +use crate::dom::bindings::codegen::Bindings::DOMQuadBinding::{DOMQuadInit, DOMQuadMethods}; +use crate::dom::bindings::codegen::Bindings::DOMRectReadOnlyBinding::DOMRectInit; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::dompoint::DOMPoint; +use crate::dom::domrect::DOMRect; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; // https://drafts.fxtf.org/geometry/#DOMQuad #[dom_struct] pub struct DOMQuad { reflector_: Reflector, - p1: JS<DOMPoint>, - p2: JS<DOMPoint>, - p3: JS<DOMPoint>, - p4: JS<DOMPoint>, + p1: Dom<DOMPoint>, + p2: Dom<DOMPoint>, + p3: Dom<DOMPoint>, + p4: Dom<DOMPoint>, } +#[allow(non_snake_case)] impl DOMQuad { - fn new_inherited(p1: &DOMPoint, - p2: &DOMPoint, - p3: &DOMPoint, - p4: &DOMPoint) - -> DOMQuad { + fn new_inherited(p1: &DOMPoint, p2: &DOMPoint, p3: &DOMPoint, p4: &DOMPoint) -> DOMQuad { DOMQuad { reflector_: Reflector::new(), - p1: JS::from_ref(p1), - p2: JS::from_ref(p2), - p3: JS::from_ref(p3), - p4: JS::from_ref(p4), + p1: Dom::from_ref(p1), + p2: Dom::from_ref(p2), + p3: Dom::from_ref(p3), + p4: Dom::from_ref(p4), } } - pub fn new(global: &GlobalScope, - p1: &DOMPoint, - p2: &DOMPoint, - p3: &DOMPoint, - p4: &DOMPoint) -> Root<DOMQuad> { - reflect_dom_object(box DOMQuad::new_inherited(p1, p2, p3, p4), - global, - Wrap) + pub fn new( + global: &GlobalScope, + p1: &DOMPoint, + p2: &DOMPoint, + p3: &DOMPoint, + p4: &DOMPoint, + ) -> DomRoot<DOMQuad> { + reflect_dom_object(Box::new(DOMQuad::new_inherited(p1, p2, p3, p4)), global) } - pub fn Constructor(global: &GlobalScope, - p1: &DOMPointInit, - p2: &DOMPointInit, - p3: &DOMPointInit, - p4: &DOMPointInit) - -> Fallible<Root<DOMQuad>> { - Ok(DOMQuad::new(global, - &*DOMPoint::new_from_init(global, p1), - &*DOMPoint::new_from_init(global, p2), - &*DOMPoint::new_from_init(global, p3), - &*DOMPoint::new_from_init(global, p4))) + pub fn Constructor( + global: &GlobalScope, + p1: &DOMPointInit, + p2: &DOMPointInit, + p3: &DOMPointInit, + p4: &DOMPointInit, + ) -> Fallible<DomRoot<DOMQuad>> { + Ok(DOMQuad::new( + global, + &*DOMPoint::new_from_init(global, p1), + &*DOMPoint::new_from_init(global, p2), + &*DOMPoint::new_from_init(global, p3), + &*DOMPoint::new_from_init(global, p4), + )) } // https://drafts.fxtf.org/geometry/#dom-domquad-fromrect - pub fn FromRect(global: &GlobalScope, other: &DOMRectInit) -> Root<DOMQuad> { - DOMQuad::new(global, - &*DOMPoint::new(global, other.x, other.y, 0f64, 1f64), - &*DOMPoint::new(global, other.x + other.width, other.y, 0f64, 1f64), - &*DOMPoint::new(global, other.x + other.width, other.y + other.height, 0f64, 1f64), - &*DOMPoint::new(global, other.x, other.y + other.height, 0f64, 1f64)) + pub fn FromRect(global: &GlobalScope, other: &DOMRectInit) -> DomRoot<DOMQuad> { + DOMQuad::new( + global, + &*DOMPoint::new(global, other.x, other.y, 0f64, 1f64), + &*DOMPoint::new(global, other.x + other.width, other.y, 0f64, 1f64), + &*DOMPoint::new( + global, + other.x + other.width, + other.y + other.height, + 0f64, + 1f64, + ), + &*DOMPoint::new(global, other.x, other.y + other.height, 0f64, 1f64), + ) } // https://drafts.fxtf.org/geometry/#dom-domquad-fromquad - pub fn FromQuad(global: &GlobalScope, other: &DOMQuadInit) -> Root<DOMQuad> { - DOMQuad::new(global, - &DOMPoint::new_from_init(global, &other.p1), - &DOMPoint::new_from_init(global, &other.p2), - &DOMPoint::new_from_init(global, &other.p3), - &DOMPoint::new_from_init(global, &other.p4)) + pub fn FromQuad(global: &GlobalScope, other: &DOMQuadInit) -> DomRoot<DOMQuad> { + DOMQuad::new( + global, + &DOMPoint::new_from_init(global, &other.p1), + &DOMPoint::new_from_init(global, &other.p2), + &DOMPoint::new_from_init(global, &other.p3), + &DOMPoint::new_from_init(global, &other.p4), + ) } } impl DOMQuadMethods for DOMQuad { // https://drafts.fxtf.org/geometry/#dom-domquad-p1 - fn P1(&self) -> Root<DOMPoint> { - Root::from_ref(&self.p1) + fn P1(&self) -> DomRoot<DOMPoint> { + DomRoot::from_ref(&self.p1) } // https://drafts.fxtf.org/geometry/#dom-domquad-p2 - fn P2(&self) -> Root<DOMPoint> { - Root::from_ref(&self.p2) + fn P2(&self) -> DomRoot<DOMPoint> { + DomRoot::from_ref(&self.p2) } // https://drafts.fxtf.org/geometry/#dom-domquad-p3 - fn P3(&self) -> Root<DOMPoint> { - Root::from_ref(&self.p3) + fn P3(&self) -> DomRoot<DOMPoint> { + DomRoot::from_ref(&self.p3) } // https://drafts.fxtf.org/geometry/#dom-domquad-p4 - fn P4(&self) -> Root<DOMPoint> { - Root::from_ref(&self.p4) + fn P4(&self) -> DomRoot<DOMPoint> { + DomRoot::from_ref(&self.p4) } // https://drafts.fxtf.org/geometry/#dom-domquad-getbounds - fn GetBounds(&self) -> Root<DOMRect> { - let left = self.p1.X().min(self.p2.X()).min(self.p3.X()).min(self.p4.X()); - let top = self.p1.Y().min(self.p2.Y()).min(self.p3.Y()).min(self.p4.Y()); - let right = self.p1.X().max(self.p2.X()).max(self.p3.X()).max(self.p4.X()); - let bottom = self.p1.Y().max(self.p2.Y()).max(self.p3.Y()).max(self.p4.Y()); + fn GetBounds(&self) -> DomRoot<DOMRect> { + let left = self + .p1 + .X() + .min(self.p2.X()) + .min(self.p3.X()) + .min(self.p4.X()); + let top = self + .p1 + .Y() + .min(self.p2.Y()) + .min(self.p3.Y()) + .min(self.p4.Y()); + let right = self + .p1 + .X() + .max(self.p2.X()) + .max(self.p3.X()) + .max(self.p4.X()); + let bottom = self + .p1 + .Y() + .max(self.p2.Y()) + .max(self.p3.Y()) + .max(self.p4.Y()); - DOMRect::new(&self.global(), - left, - top, - right - left, - bottom - top) + DOMRect::new(&self.global(), left, top, right - left, bottom - top) } } diff --git a/components/script/dom/domrect.rs b/components/script/dom/domrect.rs index 3201e6c49ef..d925176f96d 100644 --- a/components/script/dom/domrect.rs +++ b/components/script/dom/domrect.rs @@ -1,15 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DOMRectBinding; -use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods; -use dom::bindings::codegen::Bindings::DOMRectReadOnlyBinding::DOMRectReadOnlyMethods; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::domrectreadonly::DOMRectReadOnly; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods; +use crate::dom::bindings::codegen::Bindings::DOMRectReadOnlyBinding::DOMRectReadOnlyMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::domrectreadonly::DOMRectReadOnly; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; #[dom_struct] @@ -24,18 +23,21 @@ impl DOMRect { } } - pub fn new(global: &GlobalScope, x: f64, y: f64, width: f64, height: f64) -> Root<DOMRect> { - reflect_dom_object(box DOMRect::new_inherited(x, y, width, height), - global, - DOMRectBinding::Wrap) + pub fn new(global: &GlobalScope, x: f64, y: f64, width: f64, height: f64) -> DomRoot<DOMRect> { + reflect_dom_object( + Box::new(DOMRect::new_inherited(x, y, width, height)), + global, + ) } - pub fn Constructor(global: &GlobalScope, - x: f64, - y: f64, - width: f64, - height: f64) - -> Fallible<Root<DOMRect>> { + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + x: f64, + y: f64, + width: f64, + height: f64, + ) -> Fallible<DomRoot<DOMRect>> { Ok(DOMRect::new(global, x, y, width, height)) } } diff --git a/components/script/dom/domrectreadonly.rs b/components/script/dom/domrectreadonly.rs index f5e08a4fcb7..49eed59ac58 100644 --- a/components/script/dom/domrectreadonly.rs +++ b/components/script/dom/domrectreadonly.rs @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DOMRectReadOnlyBinding::{DOMRectReadOnlyMethods, Wrap}; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::DOMRectReadOnlyBinding::DOMRectReadOnlyMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; use std::cell::Cell; @@ -30,23 +30,27 @@ impl DOMRectReadOnly { } } - pub fn new(global: &GlobalScope, - x: f64, - y: f64, - width: f64, - height: f64) - -> Root<DOMRectReadOnly> { - reflect_dom_object(box DOMRectReadOnly::new_inherited(x, y, width, height), - global, - Wrap) + pub fn new( + global: &GlobalScope, + x: f64, + y: f64, + width: f64, + height: f64, + ) -> DomRoot<DOMRectReadOnly> { + reflect_dom_object( + Box::new(DOMRectReadOnly::new_inherited(x, y, width, height)), + global, + ) } - pub fn Constructor(global: &GlobalScope, - x: f64, - y: f64, - width: f64, - height: f64) - -> Fallible<Root<DOMRectReadOnly>> { + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + x: f64, + y: f64, + width: f64, + height: f64, + ) -> Fallible<DomRoot<DOMRectReadOnly>> { Ok(DOMRectReadOnly::new(global, x, y, width, height)) } diff --git a/components/script/dom/domstringlist.rs b/components/script/dom/domstringlist.rs new file mode 100644 index 00000000000..a1592faca79 --- /dev/null +++ b/components/script/dom/domstringlist.rs @@ -0,0 +1,54 @@ +/* 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 crate::dom::bindings::codegen::Bindings::DOMStringListBinding::DOMStringListMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct DOMStringList { + reflector_: Reflector, + strings: Vec<DOMString>, +} + +impl DOMStringList { + #[allow(unused)] + pub fn new_inherited(strings: Vec<DOMString>) -> DOMStringList { + DOMStringList { + reflector_: Reflector::new(), + strings, + } + } + + #[allow(unused)] + pub fn new(window: &Window, strings: Vec<DOMString>) -> DomRoot<DOMStringList> { + reflect_dom_object(Box::new(DOMStringList::new_inherited(strings)), window) + } +} + +// https://html.spec.whatwg.org/multipage/#domstringlist +impl DOMStringListMethods for DOMStringList { + // https://html.spec.whatwg.org/multipage/#dom-domstringlist-length + fn Length(&self) -> u32 { + self.strings.len() as u32 + } + + // https://html.spec.whatwg.org/multipage/#dom-domstringlist-item + fn Item(&self, index: u32) -> Option<DOMString> { + self.strings.get(index as usize).cloned() + } + + // https://html.spec.whatwg.org/multipage/#dom-domstringlist-contains + fn Contains(&self, string: DOMString) -> bool { + self.strings.contains(&string) + } + + // check-tidy: no specs after this line + fn IndexedGetter(&self, index: u32) -> Option<DOMString> { + self.Item(index) + } +} diff --git a/components/script/dom/domstringmap.rs b/components/script/dom/domstringmap.rs index d529ca25cf8..9ff1071e85b 100644 --- a/components/script/dom/domstringmap.rs +++ b/components/script/dom/domstringmap.rs @@ -1,36 +1,33 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::DOMStringMapBinding; -use dom::bindings::codegen::Bindings::DOMStringMapBinding::DOMStringMapMethods; -use dom::bindings::error::ErrorResult; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::htmlelement::HTMLElement; -use dom::node::window_from_node; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::DOMStringMapBinding::DOMStringMapMethods; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::window_from_node; use dom_struct::dom_struct; #[dom_struct] pub struct DOMStringMap { reflector_: Reflector, - element: JS<HTMLElement>, + element: Dom<HTMLElement>, } impl DOMStringMap { fn new_inherited(element: &HTMLElement) -> DOMStringMap { DOMStringMap { reflector_: Reflector::new(), - element: JS::from_ref(element), + element: Dom::from_ref(element), } } - pub fn new(element: &HTMLElement) -> Root<DOMStringMap> { + pub fn new(element: &HTMLElement) -> DomRoot<DOMStringMap> { let window = window_from_node(element); - reflect_dom_object(box DOMStringMap::new_inherited(element), - &*window, - DOMStringMapBinding::Wrap) + reflect_dom_object(Box::new(DOMStringMap::new_inherited(element)), &*window) } } @@ -53,6 +50,10 @@ impl DOMStringMapMethods for DOMStringMap { // https://html.spec.whatwg.org/multipage/#the-domstringmap-interface:supported-property-names fn SupportedPropertyNames(&self) -> Vec<DOMString> { - self.element.supported_prop_names_custom_attr().iter().cloned().collect() + self.element + .supported_prop_names_custom_attr() + .iter() + .cloned() + .collect() } } diff --git a/components/script/dom/domtokenlist.rs b/components/script/dom/domtokenlist.rs index 28266709f67..70b306d223b 100644 --- a/components/script/dom/domtokenlist.rs +++ b/components/script/dom/domtokenlist.rs @@ -1,45 +1,59 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::DOMTokenListBinding; -use dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::element::Element; -use dom::node::window_from_node; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::element::Element; +use crate::dom::node::window_from_node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::LocalName; use servo_atoms::Atom; use style::str::HTML_SPACE_CHARACTERS; #[dom_struct] pub struct DOMTokenList { reflector_: Reflector, - element: JS<Element>, + element: Dom<Element>, local_name: LocalName, + supported_tokens: Option<Vec<Atom>>, } impl DOMTokenList { - pub fn new_inherited(element: &Element, local_name: LocalName) -> DOMTokenList { + pub fn new_inherited( + element: &Element, + local_name: LocalName, + supported_tokens: Option<Vec<Atom>>, + ) -> DOMTokenList { DOMTokenList { reflector_: Reflector::new(), - element: JS::from_ref(element), + element: Dom::from_ref(element), local_name: local_name, + supported_tokens: supported_tokens, } } - pub fn new(element: &Element, local_name: &LocalName) -> Root<DOMTokenList> { + pub fn new( + element: &Element, + local_name: &LocalName, + supported_tokens: Option<Vec<Atom>>, + ) -> DomRoot<DOMTokenList> { let window = window_from_node(element); - reflect_dom_object(box DOMTokenList::new_inherited(element, local_name.clone()), - &*window, - DOMTokenListBinding::Wrap) + reflect_dom_object( + Box::new(DOMTokenList::new_inherited( + element, + local_name.clone(), + supported_tokens, + )), + &*window, + ) } - fn attribute(&self) -> Option<Root<Attr>> { + fn attribute(&self) -> Option<DomRoot<Attr>> { self.element.get_attribute(&ns!(), &self.local_name) } @@ -50,22 +64,54 @@ impl DOMTokenList { slice => Ok(Atom::from(slice)), } } + + // https://dom.spec.whatwg.org/#concept-dtl-update + fn perform_update_steps(&self, atoms: Vec<Atom>) { + // Step 1 + if !self.element.has_attribute(&self.local_name) && atoms.len() == 0 { + return; + } + // step 2 + self.element + .set_atomic_tokenlist_attribute(&self.local_name, atoms) + } + + // https://dom.spec.whatwg.org/#concept-domtokenlist-validation + fn validation_steps(&self, token: &str) -> Fallible<bool> { + match &self.supported_tokens { + None => Err(Error::Type( + "This attribute has no supported tokens".to_owned(), + )), + Some(supported_tokens) => { + let token = Atom::from(token).to_ascii_lowercase(); + if supported_tokens + .iter() + .any(|supported_token| *supported_token == token) + { + return Ok(true); + } + Ok(false) + }, + } + } } // https://dom.spec.whatwg.org/#domtokenlist impl DOMTokenListMethods for DOMTokenList { // https://dom.spec.whatwg.org/#dom-domtokenlist-length fn Length(&self) -> u32 { - self.attribute().map_or(0, |attr| { - attr.value().as_tokens().len() - }) as u32 + self.attribute() + .map_or(0, |attr| attr.value().as_tokens().len()) as u32 } // https://dom.spec.whatwg.org/#dom-domtokenlist-item fn Item(&self, index: u32) -> Option<DOMString> { self.attribute().and_then(|attr| { // FIXME(ajeffrey): Convert directly from Atom to DOMString - attr.value().as_tokens().get(index as usize).map(|token| DOMString::from(&**token)) + attr.value() + .as_tokens() + .get(index as usize) + .map(|token| DOMString::from(&**token)) }) } @@ -84,12 +130,12 @@ impl DOMTokenListMethods for DOMTokenList { fn Add(&self, tokens: Vec<DOMString>) -> ErrorResult { let mut atoms = self.element.get_tokenlist_attribute(&self.local_name); for token in &tokens { - let token = try!(self.check_token_exceptions(&token)); + let token = self.check_token_exceptions(&token)?; if !atoms.iter().any(|atom| *atom == token) { atoms.push(token); } } - self.element.set_atomic_tokenlist_attribute(&self.local_name, atoms); + self.perform_update_steps(atoms); Ok(()) } @@ -97,33 +143,36 @@ impl DOMTokenListMethods for DOMTokenList { fn Remove(&self, tokens: Vec<DOMString>) -> ErrorResult { let mut atoms = self.element.get_tokenlist_attribute(&self.local_name); for token in &tokens { - let token = try!(self.check_token_exceptions(&token)); - atoms.iter().position(|atom| *atom == token).map(|index| atoms.remove(index)); + let token = self.check_token_exceptions(&token)?; + atoms + .iter() + .position(|atom| *atom == token) + .map(|index| atoms.remove(index)); } - self.element.set_atomic_tokenlist_attribute(&self.local_name, atoms); + self.perform_update_steps(atoms); Ok(()) } // https://dom.spec.whatwg.org/#dom-domtokenlist-toggle fn Toggle(&self, token: DOMString, force: Option<bool>) -> Fallible<bool> { let mut atoms = self.element.get_tokenlist_attribute(&self.local_name); - let token = try!(self.check_token_exceptions(&token)); + let token = self.check_token_exceptions(&token)?; match atoms.iter().position(|atom| *atom == token) { Some(index) => match force { Some(true) => Ok(true), _ => { atoms.remove(index); - self.element.set_atomic_tokenlist_attribute(&self.local_name, atoms); + self.perform_update_steps(atoms); Ok(false) - } + }, }, None => match force { Some(false) => Ok(false), _ => { atoms.push(token); - self.element.set_atomic_tokenlist_attribute(&self.local_name, atoms); + self.perform_update_steps(atoms); Ok(true) - } + }, }, } } @@ -135,11 +184,12 @@ impl DOMTokenListMethods for DOMTokenList { // https://dom.spec.whatwg.org/#dom-domtokenlist-value fn SetValue(&self, value: DOMString) { - self.element.set_tokenlist_attribute(&self.local_name, value); + self.element + .set_tokenlist_attribute(&self.local_name, value); } // https://dom.spec.whatwg.org/#dom-domtokenlist-replace - fn Replace(&self, token: DOMString, new_token: DOMString) -> ErrorResult { + fn Replace(&self, token: DOMString, new_token: DOMString) -> Fallible<bool> { if token.is_empty() || new_token.is_empty() { // Step 1. return Err(Error::Syntax); @@ -152,21 +202,37 @@ impl DOMTokenListMethods for DOMTokenList { let token = Atom::from(token); let new_token = Atom::from(new_token); let mut atoms = self.element.get_tokenlist_attribute(&self.local_name); + let mut result = false; if let Some(pos) = atoms.iter().position(|atom| *atom == token) { - if !atoms.contains(&new_token) { - atoms[pos] = new_token; + if let Some(redundant_pos) = atoms.iter().position(|atom| *atom == new_token) { + if redundant_pos > pos { + // The replacement is already in the list, later, + // so we perform the replacement and remove the + // later copy. + atoms[pos] = new_token; + atoms.remove(redundant_pos); + } else if redundant_pos < pos { + // The replacement is already in the list, earlier, + // so we remove the index where we'd be putting the + // later copy. + atoms.remove(pos); + } + // else we are replacing the token with itself, nothing to change } else { - atoms.remove(pos); + // The replacement is not in the list already + atoms[pos] = new_token; } + + // Step 5. + self.perform_update_steps(atoms); + result = true; } - // Step 5. - self.element.set_atomic_tokenlist_attribute(&self.local_name, atoms); - Ok(()) + Ok(result) } - // https://dom.spec.whatwg.org/#concept-dtl-serialize - fn Stringifier(&self) -> DOMString { - self.element.get_string_attribute(&self.local_name) + // https://dom.spec.whatwg.org/#dom-domtokenlist-supports + fn Supports(&self, token: DOMString) -> Fallible<bool> { + self.validation_steps(&token) } // check-tidy: no specs after this line diff --git a/components/script/dom/dynamicmoduleowner.rs b/components/script/dom/dynamicmoduleowner.rs new file mode 100644 index 00000000000..50fc71fbd79 --- /dev/null +++ b/components/script/dom/dynamicmoduleowner.rs @@ -0,0 +1,54 @@ +/* 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 crate::dom::bindings::codegen::Bindings::DynamicModuleOwnerBinding::DynamicModuleOwnerMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use dom_struct::dom_struct; +use std::rc::Rc; +use uuid::Uuid; + +/// An unique id for dynamic module +#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)] +pub struct DynamicModuleId(pub Uuid); + +#[dom_struct] +pub struct DynamicModuleOwner { + reflector_: Reflector, + + #[ignore_malloc_size_of = "Rc"] + promise: Rc<Promise>, + + /// Unique id for each dynamic module + #[ignore_malloc_size_of = "Defined in uuid"] + id: DynamicModuleId, +} + +impl DynamicModuleOwner { + #[allow(unrooted_must_root)] + fn new_inherited(promise: Rc<Promise>, id: DynamicModuleId) -> Self { + DynamicModuleOwner { + reflector_: Reflector::new(), + promise, + id, + } + } + + #[allow(unrooted_must_root)] + pub fn new(global: &GlobalScope, promise: Rc<Promise>, id: DynamicModuleId) -> DomRoot<Self> { + reflect_dom_object( + Box::new(DynamicModuleOwner::new_inherited(promise, id)), + global, + ) + } +} + +impl DynamicModuleOwnerMethods for DynamicModuleOwner { + // https://html.spec.whatwg.org/multipage/#integration-with-the-javascript-module-system:import() + fn Promise(&self) -> Rc<Promise> { + self.promise.clone() + } +} diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 7b56b6c6e81..e69fd558f5c 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -1,117 +1,144 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Element nodes. -use cssparser::Color; +use crate::dom::activation::Activatable; +use crate::dom::attr::{Attr, AttrHelpersForLayout}; +use crate::dom::bindings::cell::{ref_filter_map, 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::ElementMethods; +use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; +use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootBinding::ShadowRootMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions}; +use crate::dom::bindings::codegen::UnionTypes::NodeOrString; +use crate::dom::bindings::conversions::DerivedFrom; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::xmlname::XMLName::InvalidXMLName; +use crate::dom::bindings::xmlname::{ + namespace_from_domstring, validate_and_extract, xml_name_type, +}; +use crate::dom::characterdata::CharacterData; +use crate::dom::create::create_element; +use crate::dom::customelementregistry::{ + CallbackReaction, CustomElementDefinition, CustomElementReaction, CustomElementState, +}; +use crate::dom::document::{determine_policy_for_token, Document, LayoutDocumentHelpers}; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::domrect::DOMRect; +use crate::dom::domtokenlist::DOMTokenList; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlanchorelement::HTMLAnchorElement; +use crate::dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers}; +use crate::dom::htmlbuttonelement::HTMLButtonElement; +use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutHTMLCanvasElementHelpers}; +use crate::dom::htmlcollection::HTMLCollection; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlfontelement::{HTMLFontElement, HTMLFontElementLayoutHelpers}; +use crate::dom::htmlformelement::FormControlElementHelpers; +use crate::dom::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers}; +use crate::dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods}; +use crate::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; +use crate::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; +use crate::dom::htmllabelelement::HTMLLabelElement; +use crate::dom::htmllegendelement::HTMLLegendElement; +use crate::dom::htmllinkelement::HTMLLinkElement; +use crate::dom::htmlobjectelement::HTMLObjectElement; +use crate::dom::htmloptgroupelement::HTMLOptGroupElement; +use crate::dom::htmloutputelement::HTMLOutputElement; +use crate::dom::htmlselectelement::HTMLSelectElement; +use crate::dom::htmlstyleelement::HTMLStyleElement; +use crate::dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementLayoutHelpers}; +use crate::dom::htmltableelement::{HTMLTableElement, HTMLTableElementLayoutHelpers}; +use crate::dom::htmltablerowelement::{HTMLTableRowElement, HTMLTableRowElementLayoutHelpers}; +use crate::dom::htmltablesectionelement::{ + HTMLTableSectionElement, HTMLTableSectionElementLayoutHelpers, +}; +use crate::dom::htmltemplateelement::HTMLTemplateElement; +use crate::dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; +use crate::dom::mutationobserver::{Mutation, MutationObserver}; +use crate::dom::namednodemap::NamedNodeMap; +use crate::dom::node::{document_from_node, window_from_node}; +use crate::dom::node::{BindContext, NodeDamage, NodeFlags, UnbindContext}; +use crate::dom::node::{ChildrenMutation, LayoutNodeHelpers, Node, ShadowIncluding}; +use crate::dom::nodelist::NodeList; +use crate::dom::promise::Promise; +use crate::dom::raredata::ElementRareData; +use crate::dom::servoparser::ServoParser; +use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot}; +use crate::dom::text::Text; +use crate::dom::validation::Validatable; +use crate::dom::virtualmethods::{vtable_for, VirtualMethods}; +use crate::dom::window::ReflowReason; +use crate::script_thread::ScriptThread; +use crate::stylesheet_loader::StylesheetOwner; +use crate::task::TaskOnce; use devtools_traits::AttrInfo; -use dom::activation::Activatable; -use dom::attr::{Attr, AttrHelpersForLayout}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::ElementBinding; -use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions}; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::UnionTypes::NodeOrString; -use dom::bindings::conversions::DerivedFrom; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; -use dom::bindings::js::{JS, LayoutJS, MutNullableJS}; -use dom::bindings::js::{Root, RootedReference}; -use dom::bindings::refcounted::{Trusted, TrustedPromise}; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::{DOMString, extended_filtering}; -use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml_name_type}; -use dom::bindings::xmlname::XMLName::InvalidXMLName; -use dom::characterdata::CharacterData; -use dom::create::create_element; -use dom::document::{Document, LayoutDocumentHelpers}; -use dom::documentfragment::DocumentFragment; -use dom::domrect::DOMRect; -use dom::domtokenlist::DOMTokenList; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::htmlanchorelement::HTMLAnchorElement; -use dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers}; -use dom::htmlbuttonelement::HTMLButtonElement; -use dom::htmlcanvaselement::{HTMLCanvasElement, LayoutHTMLCanvasElementHelpers}; -use dom::htmlcollection::HTMLCollection; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlfontelement::{HTMLFontElement, HTMLFontElementLayoutHelpers}; -use dom::htmlformelement::FormControlElementHelpers; -use dom::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers}; -use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods}; -use dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; -use dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; -use dom::htmllabelelement::HTMLLabelElement; -use dom::htmllegendelement::HTMLLegendElement; -use dom::htmllinkelement::HTMLLinkElement; -use dom::htmlobjectelement::HTMLObjectElement; -use dom::htmloptgroupelement::HTMLOptGroupElement; -use dom::htmlselectelement::HTMLSelectElement; -use dom::htmlstyleelement::HTMLStyleElement; -use dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementLayoutHelpers}; -use dom::htmltableelement::{HTMLTableElement, HTMLTableElementLayoutHelpers}; -use dom::htmltablerowelement::{HTMLTableRowElement, HTMLTableRowElementLayoutHelpers}; -use dom::htmltablesectionelement::{HTMLTableSectionElement, HTMLTableSectionElementLayoutHelpers}; -use dom::htmltemplateelement::HTMLTemplateElement; -use dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; -use dom::namednodemap::NamedNodeMap; -use dom::node::{CLICK_IN_PROGRESS, ChildrenMutation, LayoutNodeHelpers, Node}; -use dom::node::{NodeDamage, SEQUENTIALLY_FOCUSABLE, UnbindContext}; -use dom::node::{document_from_node, window_from_node}; -use dom::nodelist::NodeList; -use dom::promise::Promise; -use dom::servoparser::ServoParser; -use dom::text::Text; -use dom::validation::Validatable; -use dom::virtualmethods::{VirtualMethods, vtable_for}; -use dom::window::ReflowReason; use dom_struct::dom_struct; +use euclid::default::Rect; use html5ever::serialize; use html5ever::serialize::SerializeOpts; use html5ever::serialize::TraversalScope; use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode}; -use html5ever_atoms::{Prefix, LocalName, Namespace, QualName}; -use js::jsapi::{HandleValue, JSAutoCompartment}; +use html5ever::{LocalName, Namespace, Prefix, QualName}; +use js::jsapi::Heap; +use js::jsval::JSVal; +use msg::constellation_msg::InputMethodType; use net_traits::request::CorsSettings; -use ref_filter_map::ref_filter_map; -use script_layout_interface::message::ReflowQueryType; -use script_thread::Runnable; -use selectors::matching::{ElementSelectorFlags, StyleRelations, matches_selector_list}; -use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS}; -use selectors::parser::{AttrSelector, NamespaceConstraint}; +use net_traits::ReferrerPolicy; +use script_layout_interface::message::ReflowGoal; +use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; +use selectors::matching::{ElementSelectorFlags, MatchingContext}; +use selectors::sink::Push; +use selectors::Element as SelectorsElement; +use servo_arc::Arc; use servo_atoms::Atom; -use std::ascii::AsciiExt; use std::borrow::Cow; -use std::cell::{Cell, Ref}; -use std::convert::TryFrom; +use std::cell::Cell; use std::default::Default; use std::fmt; +use std::mem; use std::rc::Rc; -use std::sync::Arc; +use std::str::FromStr; +use style::applicable_declarations::ApplicableDeclarationBlock; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; -use style::context::{QuirksMode, ReflowGoal}; -use style::element_state::*; -use style::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute}; -use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size, overflow_x}; -use style::restyle_hints::RESTYLE_SELF; +use style::context::QuirksMode; +use style::dom_apis; +use style::element_state::ElementState; +use style::invalidation::element::restyle_hints::RestyleHint; +use style::properties::longhands::{ + self, background_image, border_spacing, font_family, font_size, +}; +use style::properties::longhands::{overflow_x, overflow_y}; +use style::properties::{parse_style_attribute, PropertyDeclarationBlock}; +use style::properties::{ComputedValues, Importance, PropertyDeclaration}; use style::rule_tree::CascadeLevel; -use style::selector_parser::{NonTSPseudoClass, RestyleDamage, SelectorImpl, SelectorParser}; -use style::shared_lock::{SharedRwLock, Locked}; -use style::sink::Push; -use style::stylist::ApplicableDeclarationBlock; +use style::selector_parser::extended_filtering; +use style::selector_parser::{ + NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser, +}; +use style::shared_lock::{Locked, SharedRwLock}; +use style::stylesheets::CssRuleType; use style::thread_state; -use style::values::CSSFloat; -use style::values::specified::{self, CSSColor}; -use stylesheet_loader::StylesheetOwner; +use style::values::generics::NonNegative; +use style::values::{computed, specified, AtomIdent, AtomString, CSSFloat}; +use style::CaseSensitivityExt; +use xml5ever::serialize as xmlSerialize; +use xml5ever::serialize::SerializeOpts as XmlSerializeOpts; +use xml5ever::serialize::TraversalScope as XmlTraversalScope; +use xml5ever::serialize::TraversalScope::ChildrenOnly as XmlChildrenOnly; +use xml5ever::serialize::TraversalScope::IncludeNode as XmlIncludeNode; // TODO: Update focus state when the top-level browsing context gains or loses system focus, // and when the element enters or leaves a browsing context container. @@ -123,38 +150,51 @@ pub struct Element { local_name: LocalName, tag_name: TagName, namespace: Namespace, - prefix: Option<DOMString>, - attrs: DOMRefCell<Vec<JS<Attr>>>, - id_attribute: DOMRefCell<Option<Atom>>, - #[ignore_heap_size_of = "Arc"] - style_attribute: DOMRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>>, - attr_list: MutNullableJS<NamedNodeMap>, - class_list: MutNullableJS<DOMTokenList>, + prefix: DomRefCell<Option<Prefix>>, + attrs: DomRefCell<Vec<Dom<Attr>>>, + id_attribute: DomRefCell<Option<Atom>>, + is: DomRefCell<Option<LocalName>>, + #[ignore_malloc_size_of = "Arc"] + style_attribute: DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>>, + attr_list: MutNullableDom<NamedNodeMap>, + class_list: MutNullableDom<DOMTokenList>, state: Cell<ElementState>, /// These flags are set by the style system to indicate the that certain /// operations may require restyling this element or its descendants. The /// flags are not atomic, so the style system takes care of only set them /// when it has exclusive access to the element. - #[ignore_heap_size_of = "bitflags defined in rust-selectors"] + #[ignore_malloc_size_of = "bitflags defined in rust-selectors"] selector_flags: Cell<ElementSelectorFlags>, + rare_data: DomRefCell<Option<Box<ElementRareData>>>, } impl fmt::Debug for Element { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(write!(f, "<{}", self.local_name)); + write!(f, "<{}", self.local_name)?; if let Some(ref id) = *self.id_attribute.borrow() { - try!(write!(f, " id={}", id)); + write!(f, " id={}", id)?; } write!(f, ">") } } -#[derive(PartialEq, HeapSizeOf)] +impl fmt::Debug for DomRoot<Element> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + (**self).fmt(f) + } +} + +#[derive(MallocSizeOf, PartialEq)] pub enum ElementCreator { ParserCreated(u64), ScriptCreated, } +pub enum CustomElementCreationMode { + Synchronous, + Asynchronous, +} + impl ElementCreator { pub fn is_parser_created(&self) -> bool { match *self { @@ -177,10 +217,10 @@ pub enum AdjacentPosition { BeforeEnd, } -impl<'a> TryFrom<&'a str> for AdjacentPosition { - type Error = Error; +impl FromStr for AdjacentPosition { + type Err = Error; - fn try_from(position: &'a str) -> Result<AdjacentPosition, Self::Error> { + fn from_str(position: &str) -> Result<Self, Self::Err> { match_ignore_ascii_case! { &*position, "beforebegin" => Ok(AdjacentPosition::BeforeBegin), "afterbegin" => Ok(AdjacentPosition::AfterBegin), @@ -195,201 +235,420 @@ impl<'a> TryFrom<&'a str> for AdjacentPosition { // Element methods // impl Element { - pub fn create(name: QualName, prefix: Option<Prefix>, - document: &Document, creator: ElementCreator) - -> Root<Element> { - create_element(name, prefix, document, creator) - } - - pub fn new_inherited(local_name: LocalName, - namespace: Namespace, prefix: Option<DOMString>, - document: &Document) -> Element { - Element::new_inherited_with_state(ElementState::empty(), local_name, - namespace, prefix, document) + pub fn create( + name: QualName, + is: Option<LocalName>, + document: &Document, + creator: ElementCreator, + mode: CustomElementCreationMode, + ) -> DomRoot<Element> { + create_element(name, is, document, creator, mode) + } + + pub fn new_inherited( + local_name: LocalName, + namespace: Namespace, + prefix: Option<Prefix>, + document: &Document, + ) -> Element { + Element::new_inherited_with_state( + ElementState::empty(), + local_name, + namespace, + prefix, + document, + ) } - pub fn new_inherited_with_state(state: ElementState, local_name: LocalName, - namespace: Namespace, prefix: Option<DOMString>, - document: &Document) - -> Element { + pub fn new_inherited_with_state( + state: ElementState, + local_name: LocalName, + namespace: Namespace, + prefix: Option<Prefix>, + document: &Document, + ) -> Element { Element { node: Node::new_inherited(document), local_name: local_name, tag_name: TagName::new(), namespace: namespace, - prefix: prefix, - attrs: DOMRefCell::new(vec![]), - id_attribute: DOMRefCell::new(None), - style_attribute: DOMRefCell::new(None), + prefix: DomRefCell::new(prefix), + attrs: DomRefCell::new(vec![]), + id_attribute: DomRefCell::new(None), + is: DomRefCell::new(None), + style_attribute: DomRefCell::new(None), attr_list: Default::default(), class_list: Default::default(), state: Cell::new(state), selector_flags: Cell::new(ElementSelectorFlags::empty()), + rare_data: Default::default(), } } - pub fn new(local_name: LocalName, - namespace: Namespace, - prefix: Option<DOMString>, - document: &Document) -> Root<Element> { + pub fn new( + local_name: LocalName, + namespace: Namespace, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<Element> { Node::reflect_node( - box Element::new_inherited(local_name, namespace, prefix, document), + Box::new(Element::new_inherited( + local_name, namespace, prefix, document, + )), document, - ElementBinding::Wrap) + ) } + impl_rare_data!(ElementRareData); + pub fn restyle(&self, damage: NodeDamage) { let doc = self.node.owner_doc(); let mut restyle = doc.ensure_pending_restyle(self); // FIXME(bholley): I think we should probably only do this for // NodeStyleDamaged, but I'm preserving existing behavior. - restyle.hint |= RESTYLE_SELF; + restyle.hint.insert(RestyleHint::RESTYLE_SELF); if damage == NodeDamage::OtherNodeDamage { + doc.note_node_with_dirty_descendants(self.upcast()); restyle.damage = RestyleDamage::rebuild_and_reflow(); } } + pub fn set_is(&self, is: LocalName) { + *self.is.borrow_mut() = Some(is); + } + + pub fn get_is(&self) -> Option<LocalName> { + self.is.borrow().clone() + } + + pub fn set_custom_element_state(&self, state: CustomElementState) { + // no need to inflate rare data for uncustomized + if state != CustomElementState::Uncustomized || self.rare_data().is_some() { + self.ensure_rare_data().custom_element_state = state; + } + // https://dom.spec.whatwg.org/#concept-element-defined + let in_defined_state = match state { + CustomElementState::Uncustomized | CustomElementState::Custom => true, + _ => false, + }; + self.set_state(ElementState::IN_DEFINED_STATE, in_defined_state) + } + + pub fn get_custom_element_state(&self) -> CustomElementState { + if let Some(rare_data) = self.rare_data().as_ref() { + return rare_data.custom_element_state; + } + CustomElementState::Uncustomized + } + + pub fn set_custom_element_definition(&self, definition: Rc<CustomElementDefinition>) { + self.ensure_rare_data().custom_element_definition = Some(definition); + } + + pub fn get_custom_element_definition(&self) -> Option<Rc<CustomElementDefinition>> { + self.rare_data().as_ref()?.custom_element_definition.clone() + } + + pub fn clear_custom_element_definition(&self) { + self.ensure_rare_data().custom_element_definition = None; + } + + pub fn push_callback_reaction(&self, function: Rc<Function>, args: Box<[Heap<JSVal>]>) { + self.ensure_rare_data() + .custom_element_reaction_queue + .push(CustomElementReaction::Callback(function, args)); + } + + pub fn push_upgrade_reaction(&self, definition: Rc<CustomElementDefinition>) { + self.ensure_rare_data() + .custom_element_reaction_queue + .push(CustomElementReaction::Upgrade(definition)); + } + + pub fn clear_reaction_queue(&self) { + if let Some(ref mut rare_data) = *self.rare_data_mut() { + rare_data.custom_element_reaction_queue.clear(); + } + } + + pub fn invoke_reactions(&self) { + loop { + rooted_vec!(let mut reactions); + match *self.rare_data_mut() { + Some(ref mut data) => { + mem::swap(&mut *reactions, &mut data.custom_element_reaction_queue) + }, + None => break, + }; + + if reactions.is_empty() { + break; + } + + for reaction in reactions.iter() { + reaction.invoke(self); + } + + reactions.clear(); + } + } + + /// style will be `None` for elements in a `display: none` subtree. otherwise, the element has a + /// layout box iff it doesn't have `display: none`. + pub fn style(&self) -> Option<Arc<ComputedValues>> { + self.upcast::<Node>().style() + } + // https://drafts.csswg.org/cssom-view/#css-layout-box - // Elements that have a computed value of the display property - // that is table-column or table-column-group - // FIXME: Currently, it is assumed to be true always - fn has_css_layout_box(&self) -> bool { - true + pub fn has_css_layout_box(&self) -> bool { + self.style() + .map_or(false, |s| !s.get_box().clone_display().is_none()) } // https://drafts.csswg.org/cssom-view/#potentially-scrollable fn potentially_scrollable(&self) -> bool { - self.has_css_layout_box() && - !self.overflow_x_is_visible() && - !self.overflow_y_is_visible() + self.has_css_layout_box() && !self.has_any_visible_overflow() } - // used value of overflow-x is "visible" - fn overflow_x_is_visible(&self) -> bool { - let window = window_from_node(self); - let overflow_pair = window.overflow_query(self.upcast::<Node>().to_trusted_node_address()); - overflow_pair.x == overflow_x::computed_value::T::visible + // https://drafts.csswg.org/cssom-view/#scrolling-box + fn has_scrolling_box(&self) -> bool { + // TODO: scrolling mechanism, such as scrollbar (We don't have scrollbar yet) + // self.has_scrolling_mechanism() + self.has_any_hidden_overflow() } - // used value of overflow-y is "visible" - fn overflow_y_is_visible(&self) -> bool { - let window = window_from_node(self); - let overflow_pair = window.overflow_query(self.upcast::<Node>().to_trusted_node_address()); - overflow_pair.y != overflow_x::computed_value::T::visible + fn has_overflow(&self) -> bool { + self.ScrollHeight() > self.ClientHeight() || self.ScrollWidth() > self.ClientWidth() } -} -#[allow(unsafe_code)] -pub trait RawLayoutElementHelpers { - unsafe fn get_attr_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName) - -> Option<&'a AttrValue>; - unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName) - -> Option<&'a str>; - unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a str>; -} + // TODO: Once #19183 is closed (overflow-x/y types moved out of mako), then we could implement + // a more generic `fn has_some_overflow(&self, overflow: Overflow)` rather than have + // these two `has_any_{visible,hidden}_overflow` methods which are very structurally + // similar. -#[inline] -#[allow(unsafe_code)] -pub unsafe fn get_attr_for_layout<'a>(elem: &'a Element, namespace: &Namespace, name: &LocalName) - -> Option<LayoutJS<Attr>> { - // cast to point to T in RefCell<T> directly - let attrs = elem.attrs.borrow_for_layout(); - attrs.iter().find(|attr| { - let attr = attr.to_layout(); - *name == attr.local_name_atom_forever() && - (*attr.unsafe_get()).namespace() == namespace - }).map(|attr| attr.to_layout()) -} + /// Computed value of overflow-x or overflow-y is "visible" + fn has_any_visible_overflow(&self) -> bool { + self.style().map_or(false, |s| { + let box_ = s.get_box(); -#[allow(unsafe_code)] -impl RawLayoutElementHelpers for Element { - #[inline] - unsafe fn get_attr_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName) - -> Option<&'a AttrValue> { - get_attr_for_layout(self, namespace, name).map(|attr| { - attr.value_forever() + box_.clone_overflow_x() == overflow_x::computed_value::T::Visible || + box_.clone_overflow_y() == overflow_y::computed_value::T::Visible }) } - unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName) - -> Option<&'a str> { - get_attr_for_layout(self, namespace, name).map(|attr| { - attr.value_ref_forever() + /// Computed value of overflow-x or overflow-y is "hidden" + fn has_any_hidden_overflow(&self) -> bool { + self.style().map_or(false, |s| { + let box_ = s.get_box(); + + box_.clone_overflow_x() == overflow_x::computed_value::T::Hidden || + box_.clone_overflow_y() == overflow_y::computed_value::T::Hidden }) } - #[inline] - unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a str> { - let attrs = self.attrs.borrow_for_layout(); - attrs.iter().filter_map(|attr| { - let attr = attr.to_layout(); - if *name == attr.local_name_atom_forever() { - Some(attr.value_ref_forever()) - } else { - None + fn shadow_root(&self) -> Option<DomRoot<ShadowRoot>> { + self.rare_data() + .as_ref()? + .shadow_root + .as_ref() + .map(|sr| DomRoot::from_ref(&**sr)) + } + + pub fn is_shadow_host(&self) -> bool { + self.shadow_root().is_some() + } + + /// https://dom.spec.whatwg.org/#dom-element-attachshadow + /// XXX This is not exposed to web content yet. It is meant to be used + /// for UA widgets only. + pub fn attach_shadow(&self, is_ua_widget: IsUserAgentWidget) -> Fallible<DomRoot<ShadowRoot>> { + // Step 1. + if self.namespace != ns!(html) { + return Err(Error::NotSupported); + } + + // Step 2. + match self.local_name() { + &local_name!("article") | + &local_name!("aside") | + &local_name!("blockquote") | + &local_name!("body") | + &local_name!("div") | + &local_name!("footer") | + &local_name!("h1") | + &local_name!("h2") | + &local_name!("h3") | + &local_name!("h4") | + &local_name!("h5") | + &local_name!("h6") | + &local_name!("header") | + &local_name!("main") | + &local_name!("nav") | + &local_name!("p") | + &local_name!("section") | + &local_name!("span") => {}, + &local_name!("video") | &local_name!("audio") + if is_ua_widget == IsUserAgentWidget::Yes => {}, + _ => return Err(Error::NotSupported), + }; + + // Step 3. + if self.is_shadow_host() { + return Err(Error::InvalidState); + } + + // Steps 4, 5 and 6. + let shadow_root = ShadowRoot::new(self, &*self.node.owner_doc()); + self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root)); + shadow_root + .upcast::<Node>() + .set_containing_shadow_root(Some(&shadow_root)); + + if self.is_connected() { + self.node.owner_doc().register_shadow_root(&*shadow_root); + } + + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + + Ok(shadow_root) + } + + pub fn detach_shadow(&self) { + if let Some(ref shadow_root) = self.shadow_root() { + self.upcast::<Node>().note_dirty_descendants(); + shadow_root.detach(); + self.ensure_rare_data().shadow_root = None; + } else { + debug_assert!(false, "Trying to detach a non-attached shadow root"); + } + } + + // https://html.spec.whatwg.org/multipage/#translation-mode + pub fn is_translate_enabled(&self) -> bool { + // TODO change this to local_name! when html5ever updates + let name = &LocalName::from("translate"); + if self.has_attribute(name) { + match &*self.get_string_attribute(name) { + "yes" | "" => return true, + "no" => return false, + _ => {}, + } + } + if let Some(parent) = self.upcast::<Node>().GetParentNode() { + if let Some(elem) = parent.downcast::<Element>() { + return elem.is_translate_enabled(); } - }).collect() + } + true // whatwg/html#5239 + } + + // https://html.spec.whatwg.org/multipage/#the-directionality + pub fn directionality(&self) -> String { + self.downcast::<HTMLElement>() + .and_then(|html_element| html_element.directionality()) + .unwrap_or_else(|| { + let node = self.upcast::<Node>(); + node.parent_directionality() + }) } } -pub trait LayoutElementHelpers { - #[allow(unsafe_code)] - unsafe fn has_class_for_layout(&self, name: &Atom) -> bool; - #[allow(unsafe_code)] - unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]>; +#[inline] +pub fn get_attr_for_layout<'dom>( + elem: LayoutDom<'dom, Element>, + namespace: &Namespace, + name: &LocalName, +) -> Option<LayoutDom<'dom, Attr>> { + elem.attrs() + .iter() + .find(|attr| name == attr.local_name() && namespace == attr.namespace()) + .cloned() +} +pub trait LayoutElementHelpers<'dom> { + fn attrs(self) -> &'dom [LayoutDom<'dom, Attr>]; + fn has_class_for_layout(self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool; + fn get_classes_for_layout(self) -> Option<&'dom [Atom]>; + + fn synthesize_presentational_hints_for_legacy_attributes<V>(self, hints: &mut V) + where + V: Push<ApplicableDeclarationBlock>; + fn get_colspan(self) -> u32; + fn get_rowspan(self) -> u32; + fn is_html_element(self) -> bool; + fn id_attribute(self) -> *const Option<Atom>; + fn style_attribute(self) -> *const Option<Arc<Locked<PropertyDeclarationBlock>>>; + fn local_name(self) -> &'dom LocalName; + fn namespace(self) -> &'dom Namespace; + fn get_lang_for_layout(self) -> String; + fn get_state_for_layout(self) -> ElementState; + fn insert_selector_flags(self, flags: ElementSelectorFlags); + fn has_selector_flags(self, flags: ElementSelectorFlags) -> bool; + /// The shadow root this element is a host of. + fn get_shadow_root_for_layout(self) -> Option<LayoutDom<'dom, ShadowRoot>>; + fn get_attr_for_layout( + self, + namespace: &Namespace, + name: &LocalName, + ) -> Option<&'dom AttrValue>; + fn get_attr_val_for_layout(self, namespace: &Namespace, name: &LocalName) -> Option<&'dom str>; + fn get_attr_vals_for_layout(self, name: &LocalName) -> Vec<&'dom AttrValue>; +} + +impl<'dom> LayoutDom<'dom, Element> { #[allow(unsafe_code)] - unsafe fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, &mut V) - where V: Push<ApplicableDeclarationBlock>; - #[allow(unsafe_code)] - unsafe fn get_colspan(self) -> u32; - #[allow(unsafe_code)] - unsafe fn get_rowspan(self) -> u32; - #[allow(unsafe_code)] - unsafe fn html_element_in_html_document_for_layout(&self) -> bool; - fn id_attribute(&self) -> *const Option<Atom>; - fn style_attribute(&self) -> *const Option<Arc<Locked<PropertyDeclarationBlock>>>; - fn local_name(&self) -> &LocalName; - fn namespace(&self) -> &Namespace; - fn get_lang_for_layout(&self) -> String; - fn get_checked_state_for_layout(&self) -> bool; - fn get_indeterminate_state_for_layout(&self) -> bool; - fn get_state_for_layout(&self) -> ElementState; - fn insert_selector_flags(&self, flags: ElementSelectorFlags); - fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool; + pub(super) fn focus_state(self) -> bool { + unsafe { + self.unsafe_get() + .state + .get() + .contains(ElementState::IN_FOCUS_STATE) + } + } } -impl LayoutElementHelpers for LayoutJS<Element> { +impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> { #[allow(unsafe_code)] #[inline] - unsafe fn has_class_for_layout(&self, name: &Atom) -> bool { - get_attr_for_layout(&*self.unsafe_get(), &ns!(), &local_name!("class")).map_or(false, |attr| { - attr.value_tokens_forever().unwrap().iter().any(|atom| atom == name) + fn attrs(self) -> &'dom [LayoutDom<'dom, Attr>] { + unsafe { LayoutDom::to_layout_slice(self.unsafe_get().attrs.borrow_for_layout()) } + } + + #[inline] + fn has_class_for_layout(self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { + get_attr_for_layout(self, &ns!(), &local_name!("class")).map_or(false, |attr| { + attr.as_tokens() + .unwrap() + .iter() + .any(|atom| case_sensitivity.eq_atom(atom, name)) }) } - #[allow(unsafe_code)] #[inline] - unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]> { - get_attr_for_layout(&*self.unsafe_get(), &ns!(), &local_name!("class")) - .map(|attr| attr.value_tokens_forever().unwrap()) + fn get_classes_for_layout(self) -> Option<&'dom [Atom]> { + get_attr_for_layout(self, &ns!(), &local_name!("class")) + .map(|attr| attr.as_tokens().unwrap()) } - #[allow(unsafe_code)] - unsafe fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, hints: &mut V) - where V: Push<ApplicableDeclarationBlock> + fn synthesize_presentational_hints_for_legacy_attributes<V>(self, hints: &mut V) + where + V: Push<ApplicableDeclarationBlock>, { // FIXME(emilio): Just a single PDB should be enough. #[inline] - fn from_declaration(shared_lock: &SharedRwLock, declaration: PropertyDeclaration) - -> ApplicableDeclarationBlock { + fn from_declaration( + shared_lock: &SharedRwLock, + declaration: PropertyDeclaration, + ) -> ApplicableDeclarationBlock { ApplicableDeclarationBlock::from_declarations( Arc::new(shared_lock.wrap(PropertyDeclarationBlock::with_one( - declaration, Importance::Normal + declaration, + Importance::Normal, ))), - CascadeLevel::PresHints) + CascadeLevel::PresHints, + ) } let document = self.upcast::<Node>().owner_doc_for_layout(); @@ -412,8 +671,8 @@ impl LayoutElementHelpers for LayoutJS<Element> { if let Some(color) = bgcolor { hints.push(from_declaration( shared_lock, - PropertyDeclaration::BackgroundColor( - CSSColor { parsed: Color::RGBA(color), authored: None }))); + PropertyDeclaration::BackgroundColor(color.into()), + )); } let background = if let Some(this) = self.downcast::<HTMLBodyElement>() { @@ -425,12 +684,10 @@ impl LayoutElementHelpers for LayoutJS<Element> { if let Some(url) = background { hints.push(from_declaration( shared_lock, - PropertyDeclaration::BackgroundImage( - background_image::SpecifiedValue(vec![ - background_image::single_value::SpecifiedValue(Some( - specified::Image::for_cascade(url.into()) - )) - ])))); + PropertyDeclaration::BackgroundImage(background_image::SpecifiedValue( + vec![specified::Image::for_cascade(url.into())].into(), + )), + )); } let color = if let Some(this) = self.downcast::<HTMLFontElement>() { @@ -448,12 +705,7 @@ impl LayoutElementHelpers for LayoutJS<Element> { if let Some(color) = color { hints.push(from_declaration( shared_lock, - PropertyDeclaration::Color( - longhands::color::SpecifiedValue(CSSColor { - parsed: Color::RGBA(color), - authored: None, - }) - ) + PropertyDeclaration::Color(longhands::color::SpecifiedValue(color.into())), )); } @@ -464,22 +716,27 @@ impl LayoutElementHelpers for LayoutJS<Element> { }; if let Some(font_family) = font_family { + // FIXME(emilio): This in Gecko parses a whole family list. hints.push(from_declaration( shared_lock, - PropertyDeclaration::FontFamily( - font_family::SpecifiedValue::Values(vec![ - font_family::computed_value::FontFamily::from_atom( - font_family)])))); + PropertyDeclaration::FontFamily(font_family::SpecifiedValue::Values( + computed::font::FontFamilyList::new(Box::new([ + computed::font::SingleFontFamily::from_atom(font_family), + ])), + )), + )); } - let font_size = self.downcast::<HTMLFontElement>().and_then(|this| this.get_size()); + let font_size = self + .downcast::<HTMLFontElement>() + .and_then(|this| this.get_size()); if let Some(font_size) = font_size { hints.push(from_declaration( shared_lock, - PropertyDeclaration::FontSize( - font_size::SpecifiedValue::from_html_size(font_size as u8) - ) + PropertyDeclaration::FontSize(font_size::SpecifiedValue::from_html_size( + font_size as u8, + )), )) } @@ -493,30 +750,37 @@ impl LayoutElementHelpers for LayoutJS<Element> { let width_value = specified::Length::from_px(cellspacing as f32); hints.push(from_declaration( shared_lock, - PropertyDeclaration::BorderSpacing( - Box::new(border_spacing::SpecifiedValue { - horizontal: width_value, - vertical: None, - })))); + PropertyDeclaration::BorderSpacing(Box::new(border_spacing::SpecifiedValue::new( + width_value.clone().into(), + width_value.into(), + ))), + )); } - let size = if let Some(this) = self.downcast::<HTMLInputElement>() { // FIXME(pcwalton): More use of atoms, please! - match (*self.unsafe_get()).get_attr_val_for_layout(&ns!(), &local_name!("type")) { + match self.get_attr_val_for_layout(&ns!(), &local_name!("type")) { // Not text entry widget - Some("hidden") | Some("date") | Some("month") | Some("week") | - Some("time") | Some("datetime-local") | Some("number") | Some("range") | - Some("color") | Some("checkbox") | Some("radio") | Some("file") | - Some("submit") | Some("image") | Some("reset") | Some("button") => { - None - }, + Some("hidden") | + Some("date") | + Some("month") | + Some("week") | + Some("time") | + Some("datetime-local") | + Some("number") | + Some("range") | + Some("color") | + Some("checkbox") | + Some("radio") | + Some("file") | + Some("submit") | + Some("image") | + Some("reset") | + Some("button") => None, // Others - _ => { - match this.size_for_layout() { - 0 => None, - s => Some(s as i32), - } + _ => match this.size_for_layout() { + 0 => None, + s => Some(s as i32), }, } } else { @@ -524,11 +788,14 @@ impl LayoutElementHelpers for LayoutJS<Element> { }; if let Some(size) = size { - let value = specified::NoCalcLength::ServoCharacterWidth(specified::CharacterWidth(size)); + let value = + specified::NoCalcLength::ServoCharacterWidth(specified::CharacterWidth(size)); hints.push(from_declaration( shared_lock, - PropertyDeclaration::Width( - specified::LengthOrPercentageOrAuto::Length(value)))); + PropertyDeclaration::Width(specified::Size::LengthPercentage(NonNegative( + specified::LengthPercentage::Length(value), + ))), + )); } let width = if let Some(this) = self.downcast::<HTMLIFrameElement>() { @@ -550,24 +817,29 @@ impl LayoutElementHelpers for LayoutJS<Element> { // FIXME(emilio): Use from_computed value here and below. match width { - LengthOrPercentageOrAuto::Auto => {} + LengthOrPercentageOrAuto::Auto => {}, LengthOrPercentageOrAuto::Percentage(percentage) => { - let width_value = - specified::LengthOrPercentageOrAuto::Percentage(specified::Percentage(percentage)); + let width_value = specified::Size::LengthPercentage(NonNegative( + specified::LengthPercentage::Percentage(computed::Percentage(percentage)), + )); hints.push(from_declaration( shared_lock, - PropertyDeclaration::Width(width_value))); - } + PropertyDeclaration::Width(width_value), + )); + }, LengthOrPercentageOrAuto::Length(length) => { - let width_value = specified::LengthOrPercentageOrAuto::Length( - specified::NoCalcLength::Absolute(specified::AbsoluteLength::Px(length.to_f32_px()))); + let width_value = specified::Size::LengthPercentage(NonNegative( + specified::LengthPercentage::Length(specified::NoCalcLength::Absolute( + specified::AbsoluteLength::Px(length.to_f32_px()), + )), + )); hints.push(from_declaration( shared_lock, - PropertyDeclaration::Width(width_value))); - } + PropertyDeclaration::Width(width_value), + )); + }, } - let height = if let Some(this) = self.downcast::<HTMLIFrameElement>() { this.get_height() } else if let Some(this) = self.downcast::<HTMLImageElement>() { @@ -579,24 +851,29 @@ impl LayoutElementHelpers for LayoutJS<Element> { }; match height { - LengthOrPercentageOrAuto::Auto => {} + LengthOrPercentageOrAuto::Auto => {}, LengthOrPercentageOrAuto::Percentage(percentage) => { - let height_value = - specified::LengthOrPercentageOrAuto::Percentage(specified::Percentage(percentage)); + let height_value = specified::Size::LengthPercentage(NonNegative( + specified::LengthPercentage::Percentage(computed::Percentage(percentage)), + )); hints.push(from_declaration( shared_lock, - PropertyDeclaration::Height(height_value))); - } + PropertyDeclaration::Height(height_value), + )); + }, LengthOrPercentageOrAuto::Length(length) => { - let height_value = specified::LengthOrPercentageOrAuto::Length( - specified::NoCalcLength::Absolute(specified::AbsoluteLength::Px(length.to_f32_px()))); + let height_value = specified::Size::LengthPercentage(NonNegative( + specified::LengthPercentage::Length(specified::NoCalcLength::Absolute( + specified::AbsoluteLength::Px(length.to_f32_px()), + )), + )); hints.push(from_declaration( shared_lock, - PropertyDeclaration::Height(height_value))); - } + PropertyDeclaration::Height(height_value), + )); + }, } - let cols = if let Some(this) = self.downcast::<HTMLTextAreaElement>() { match this.get_cols() { 0 => None, @@ -612,10 +889,14 @@ impl LayoutElementHelpers for LayoutJS<Element> { // scrollbar size into consideration (but we don't have a scrollbar yet!) // // https://html.spec.whatwg.org/multipage/#textarea-effective-width - let value = specified::NoCalcLength::ServoCharacterWidth(specified::CharacterWidth(cols)); + let value = + specified::NoCalcLength::ServoCharacterWidth(specified::CharacterWidth(cols)); hints.push(from_declaration( shared_lock, - PropertyDeclaration::Width(specified::LengthOrPercentageOrAuto::Length(value)))); + PropertyDeclaration::Width(specified::Size::LengthPercentage(NonNegative( + specified::LengthPercentage::Length(value), + ))), + )); } let rows = if let Some(this) = self.downcast::<HTMLTextAreaElement>() { @@ -631,13 +912,17 @@ impl LayoutElementHelpers for LayoutJS<Element> { // TODO(mttr) This should take scrollbar size into consideration. // // https://html.spec.whatwg.org/multipage/#textarea-effective-height - let value = specified::NoCalcLength::FontRelative(specified::FontRelativeLength::Em(rows as CSSFloat)); + let value = specified::NoCalcLength::FontRelative(specified::FontRelativeLength::Em( + rows as CSSFloat, + )); hints.push(from_declaration( shared_lock, - PropertyDeclaration::Height(specified::LengthOrPercentageOrAuto::Length(value)))); + PropertyDeclaration::Height(specified::Size::LengthPercentage(NonNegative( + specified::LengthPercentage::Length(value), + ))), + )); } - let border = if let Some(this) = self.downcast::<HTMLTableElement>() { this.get_border() } else { @@ -645,24 +930,29 @@ impl LayoutElementHelpers for LayoutJS<Element> { }; if let Some(border) = border { - let width_value = specified::BorderWidth::from_length(specified::Length::from_px(border as f32)); + let width_value = specified::BorderSideWidth::Length(NonNegative( + specified::Length::from_px(border as f32), + )); hints.push(from_declaration( shared_lock, - PropertyDeclaration::BorderTopWidth(width_value.clone()))); + PropertyDeclaration::BorderTopWidth(width_value.clone()), + )); hints.push(from_declaration( shared_lock, - PropertyDeclaration::BorderLeftWidth(width_value.clone()))); + PropertyDeclaration::BorderLeftWidth(width_value.clone()), + )); hints.push(from_declaration( shared_lock, - PropertyDeclaration::BorderBottomWidth(width_value.clone()))); + PropertyDeclaration::BorderBottomWidth(width_value.clone()), + )); hints.push(from_declaration( shared_lock, - PropertyDeclaration::BorderRightWidth(width_value))); + PropertyDeclaration::BorderRightWidth(width_value), + )); } } - #[allow(unsafe_code)] - unsafe fn get_colspan(self) -> u32 { + fn get_colspan(self) -> u32 { if let Some(this) = self.downcast::<HTMLTableCellElement>() { this.get_colspan().unwrap_or(1) } else { @@ -672,8 +962,7 @@ impl LayoutElementHelpers for LayoutJS<Element> { } } - #[allow(unsafe_code)] - unsafe fn get_rowspan(self) -> u32 { + fn get_rowspan(self) -> u32 { if let Some(this) = self.downcast::<HTMLTableCellElement>() { this.get_rowspan().unwrap_or(1) } else { @@ -684,120 +973,125 @@ impl LayoutElementHelpers for LayoutJS<Element> { } #[inline] - #[allow(unsafe_code)] - unsafe fn html_element_in_html_document_for_layout(&self) -> bool { - if (*self.unsafe_get()).namespace != ns!(html) { - return false; - } - self.upcast::<Node>().owner_doc_for_layout().is_html_document_for_layout() + fn is_html_element(self) -> bool { + *self.namespace() == ns!(html) } #[allow(unsafe_code)] - fn id_attribute(&self) -> *const Option<Atom> { - unsafe { - (*self.unsafe_get()).id_attribute.borrow_for_layout() - } + fn id_attribute(self) -> *const Option<Atom> { + unsafe { (*self.unsafe_get()).id_attribute.borrow_for_layout() } } #[allow(unsafe_code)] - fn style_attribute(&self) -> *const Option<Arc<Locked<PropertyDeclarationBlock>>> { - unsafe { - (*self.unsafe_get()).style_attribute.borrow_for_layout() - } + fn style_attribute(self) -> *const Option<Arc<Locked<PropertyDeclarationBlock>>> { + unsafe { (*self.unsafe_get()).style_attribute.borrow_for_layout() } } #[allow(unsafe_code)] - fn local_name(&self) -> &LocalName { - unsafe { - &(*self.unsafe_get()).local_name - } + fn local_name(self) -> &'dom LocalName { + unsafe { &(*self.unsafe_get()).local_name } } #[allow(unsafe_code)] - fn namespace(&self) -> &Namespace { - unsafe { - &(*self.unsafe_get()).namespace - } + fn namespace(self) -> &'dom Namespace { + unsafe { &(*self.unsafe_get()).namespace } } - #[allow(unsafe_code)] - fn get_lang_for_layout(&self) -> String { - unsafe { - let mut current_node = Some(self.upcast::<Node>()); - while let Some(node) = current_node { - current_node = node.parent_node_ref(); - match node.downcast::<Element>().map(|el| el.unsafe_get()) { - Some(elem) => { - if let Some(attr) = (*elem).get_attr_val_for_layout(&ns!(xml), &local_name!("lang")) { - return attr.to_owned(); - } - if let Some(attr) = (*elem).get_attr_val_for_layout(&ns!(), &local_name!("lang")) { - return attr.to_owned(); - } + fn get_lang_for_layout(self) -> String { + let mut current_node = Some(self.upcast::<Node>()); + while let Some(node) = current_node { + current_node = node.composed_parent_node_ref(); + match node.downcast::<Element>() { + Some(elem) => { + if let Some(attr) = + elem.get_attr_val_for_layout(&ns!(xml), &local_name!("lang")) + { + return attr.to_owned(); } - None => continue - } + if let Some(attr) = elem.get_attr_val_for_layout(&ns!(), &local_name!("lang")) { + return attr.to_owned(); + } + }, + None => continue, } - // TODO: Check meta tags for a pragma-set default language - // TODO: Check HTTP Content-Language header - String::new() } + // TODO: Check meta tags for a pragma-set default language + // TODO: Check HTTP Content-Language header + String::new() } #[inline] #[allow(unsafe_code)] - fn get_checked_state_for_layout(&self) -> bool { - // TODO option and menuitem can also have a checked state. - match self.downcast::<HTMLInputElement>() { - Some(input) => unsafe { - input.checked_state_for_layout() - }, - None => false, - } + fn get_state_for_layout(self) -> ElementState { + unsafe { (*self.unsafe_get()).state.get() } } #[inline] #[allow(unsafe_code)] - fn get_indeterminate_state_for_layout(&self) -> bool { - // TODO progress elements can also be matched with :indeterminate - match self.downcast::<HTMLInputElement>() { - Some(input) => unsafe { - input.indeterminate_state_for_layout() - }, - None => false, + fn insert_selector_flags(self, flags: ElementSelectorFlags) { + debug_assert!(thread_state::get().is_layout()); + unsafe { + let f = &(*self.unsafe_get()).selector_flags; + f.set(f.get() | flags); } } #[inline] #[allow(unsafe_code)] - fn get_state_for_layout(&self) -> ElementState { - unsafe { - (*self.unsafe_get()).state.get() - } + fn has_selector_flags(self, flags: ElementSelectorFlags) -> bool { + unsafe { (*self.unsafe_get()).selector_flags.get().contains(flags) } } #[inline] #[allow(unsafe_code)] - fn insert_selector_flags(&self, flags: ElementSelectorFlags) { - debug_assert!(thread_state::get().is_layout()); + fn get_shadow_root_for_layout(self) -> Option<LayoutDom<'dom, ShadowRoot>> { unsafe { - let f = &(*self.unsafe_get()).selector_flags; - f.set(f.get() | flags); + self.unsafe_get() + .rare_data + .borrow_for_layout() + .as_ref()? + .shadow_root + .as_ref() + .map(|sr| sr.to_layout()) } } #[inline] - #[allow(unsafe_code)] - fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool { - unsafe { - (*self.unsafe_get()).selector_flags.get().contains(flags) - } + fn get_attr_for_layout( + self, + namespace: &Namespace, + name: &LocalName, + ) -> Option<&'dom AttrValue> { + get_attr_for_layout(self, namespace, name).map(|attr| attr.value()) + } + + #[inline] + fn get_attr_val_for_layout(self, namespace: &Namespace, name: &LocalName) -> Option<&'dom str> { + get_attr_for_layout(self, namespace, name).map(|attr| attr.as_str()) + } + + #[inline] + fn get_attr_vals_for_layout(self, name: &LocalName) -> Vec<&'dom AttrValue> { + self.attrs() + .iter() + .filter_map(|attr| { + if name == attr.local_name() { + Some(attr.value()) + } else { + None + } + }) + .collect() } } impl Element { + pub fn is_html_element(&self) -> bool { + self.namespace == ns!(html) + } + pub fn html_element_in_html_document(&self) -> bool { - self.namespace == ns!(html) && self.upcast::<Node>().is_in_html_doc() + self.is_html_element() && self.upcast::<Node>().is_in_html_doc() } pub fn local_name(&self) -> &LocalName { @@ -815,11 +1109,15 @@ impl Element { &self.namespace } - pub fn prefix(&self) -> Option<&DOMString> { - self.prefix.as_ref() + pub fn prefix(&self) -> Ref<Option<Prefix>> { + self.prefix.borrow() + } + + pub fn set_prefix(&self, prefix: Option<Prefix>) { + *self.prefix.borrow_mut() = prefix; } - pub fn attrs(&self) -> Ref<[JS<Attr>]> { + pub fn attrs(&self) -> Ref<[Dom<Attr>]> { Ref::map(self.attrs.borrow(), |attrs| &**attrs) } @@ -827,15 +1125,17 @@ impl Element { pub fn locate_namespace(&self, prefix: Option<DOMString>) -> Namespace { let prefix = prefix.map(String::from).map(LocalName::from); - let inclusive_ancestor_elements = - self.upcast::<Node>() - .inclusive_ancestors() - .filter_map(Root::downcast::<Self>); + let inclusive_ancestor_elements = self + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<Self>); // Steps 3-4. for element in inclusive_ancestor_elements { // Step 1. - if element.namespace() != &ns!() && element.prefix().map(|p| &**p) == prefix.as_ref().map(|p| &**p) { + if element.namespace() != &ns!() && + element.prefix().as_ref().map(|p| &**p) == prefix.as_ref().map(|p| &**p) + { return element.namespace().clone(); } @@ -863,51 +1163,84 @@ impl Element { ns!() } - pub fn style_attribute(&self) -> &DOMRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>> { + pub fn name_attribute(&self) -> Option<Atom> { + self.rare_data().as_ref()?.name_attribute.clone() + } + + pub fn style_attribute(&self) -> &DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>> { &self.style_attribute } pub fn summarize(&self) -> Vec<AttrInfo> { - self.attrs.borrow().iter() - .map(|attr| attr.summarize()) - .collect() + self.attrs + .borrow() + .iter() + .map(|attr| attr.summarize()) + .collect() } pub fn is_void(&self) -> bool { if self.namespace != ns!(html) { - return false + return false; } match self.local_name { /* List of void elements from https://html.spec.whatwg.org/multipage/#html-fragment-serialisation-algorithm */ - - local_name!("area") | local_name!("base") | local_name!("basefont") | - local_name!("bgsound") | local_name!("br") | - local_name!("col") | local_name!("embed") | local_name!("frame") | - local_name!("hr") | local_name!("img") | - local_name!("input") | local_name!("keygen") | local_name!("link") | - local_name!("menuitem") | local_name!("meta") | - local_name!("param") | local_name!("source") | local_name!("track") | + local_name!("area") | + local_name!("base") | + local_name!("basefont") | + local_name!("bgsound") | + local_name!("br") | + local_name!("col") | + local_name!("embed") | + local_name!("frame") | + local_name!("hr") | + local_name!("img") | + local_name!("input") | + local_name!("keygen") | + local_name!("link") | + local_name!("meta") | + local_name!("param") | + local_name!("source") | + local_name!("track") | local_name!("wbr") => true, - _ => false + _ => false, } } pub fn serialize(&self, traversal_scope: TraversalScope) -> Fallible<DOMString> { let mut writer = vec![]; - match serialize(&mut writer, - &self.upcast::<Node>(), - SerializeOpts { - traversal_scope: traversal_scope, - ..Default::default() - }) { + match serialize( + &mut writer, + &self.upcast::<Node>(), + SerializeOpts { + traversal_scope: traversal_scope, + ..Default::default() + }, + ) { // FIXME(ajeffrey): Directly convert UTF8 to DOMString Ok(()) => Ok(DOMString::from(String::from_utf8(writer).unwrap())), Err(_) => panic!("Cannot serialize element"), } } - pub fn root_element(&self) -> Root<Element> { + #[allow(non_snake_case)] + pub fn xmlSerialize(&self, traversal_scope: XmlTraversalScope) -> Fallible<DOMString> { + let mut writer = vec![]; + match xmlSerialize::serialize( + &mut writer, + &self.upcast::<Node>(), + XmlSerializeOpts { + traversal_scope: traversal_scope, + ..Default::default() + }, + ) { + Ok(()) => Ok(DOMString::from(String::from_utf8(writer).unwrap())), + Err(_) => panic!("Cannot serialize element"), + } + } + + pub fn root_element(&self) -> DomRoot<Element> { if self.node.is_in_doc() { self.upcast::<Node>() .owner_doc() @@ -915,8 +1248,8 @@ impl Element { .unwrap() } else { self.upcast::<Node>() - .inclusive_ancestors() - .filter_map(Root::downcast) + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast) .last() .expect("We know inclusive_ancestors will return `self` which is an element") } @@ -924,47 +1257,69 @@ impl Element { // https://dom.spec.whatwg.org/#locate-a-namespace-prefix pub fn lookup_prefix(&self, namespace: Namespace) -> Option<DOMString> { - for node in self.upcast::<Node>().inclusive_ancestors() { - match node.downcast::<Element>() { - Some(element) => { - // Step 1. - if *element.namespace() == namespace { - if let Some(prefix) = element.GetPrefix() { - return Some(prefix); - } - } + for node in self + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::No) + { + let element = node.downcast::<Element>()?; + // Step 1. + if *element.namespace() == namespace { + if let Some(prefix) = element.GetPrefix() { + return Some(prefix); + } + } - // Step 2. - for attr in element.attrs.borrow().iter() { - if attr.prefix() == Some(&namespace_prefix!("xmlns")) && - **attr.value() == *namespace { - return Some(attr.LocalName()); - } - } - }, - None => return None, + // Step 2. + for attr in element.attrs.borrow().iter() { + if attr.prefix() == Some(&namespace_prefix!("xmlns")) && + **attr.value() == *namespace + { + return Some(attr.LocalName()); + } } } None } + // Returns the kind of IME control needed for a focusable element, if any. + pub fn input_method_type(&self) -> Option<InputMethodType> { + if !self.is_focusable_area() { + return None; + } + + if let Some(input) = self.downcast::<HTMLInputElement>() { + input.input_type().as_ime_type() + } else if self.is::<HTMLTextAreaElement>() { + Some(InputMethodType::Text) + } else { + // Other focusable elements that are not input fields. + None + } + } + pub fn is_focusable_area(&self) -> bool { if self.is_actually_disabled() { return false; } - // TODO: Check whether the element is being rendered (i.e. not hidden). let node = self.upcast::<Node>(); - if node.get_flag(SEQUENTIALLY_FOCUSABLE) { + if node.get_flag(NodeFlags::SEQUENTIALLY_FOCUSABLE) { return true; } - // https://html.spec.whatwg.org/multipage/#specially-focusable + + // <a>, <input>, <select>, and <textrea> are inherently focusable. match node.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { - true - } + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLAnchorElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLSelectElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement, + )) => true, _ => false, } } @@ -972,13 +1327,21 @@ impl Element { pub fn is_actually_disabled(&self) -> bool { let node = self.upcast::<Node>(); match node.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptionElement)) => { - self.disabled_state() - } + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLButtonElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLSelectElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLOptionElement, + )) => self.disabled_state(), // TODO: // an optgroup element that has a disabled attribute // a menuitem element that has a disabled attribute @@ -987,55 +1350,102 @@ impl Element { } } - pub fn push_new_attribute(&self, - local_name: LocalName, - value: AttrValue, - name: LocalName, - namespace: Namespace, - prefix: Option<Prefix>) { - let window = window_from_node(self); - let attr = Attr::new(&window, - local_name, - value, - name, - namespace, - prefix, - Some(self)); + pub fn push_new_attribute( + &self, + local_name: LocalName, + value: AttrValue, + name: LocalName, + namespace: Namespace, + prefix: Option<Prefix>, + ) { + let attr = Attr::new( + &self.node.owner_doc(), + local_name, + value, + name, + namespace, + prefix, + Some(self), + ); self.push_attribute(&attr); } pub fn push_attribute(&self, attr: &Attr) { - assert!(attr.GetOwnerElement().r() == Some(self)); + let name = attr.local_name().clone(); + let namespace = attr.namespace().clone(); + let mutation = Mutation::Attribute { + name: name.clone(), + namespace: namespace.clone(), + old_value: None, + }; + + MutationObserver::queue_a_mutation_record(&self.node, mutation); + + if self.get_custom_element_definition().is_some() { + let value = DOMString::from(&**attr.value()); + let reaction = CallbackReaction::AttributeChanged(name, None, Some(value), namespace); + ScriptThread::enqueue_callback_reaction(self, reaction, None); + } + + assert!(attr.GetOwnerElement().as_deref() == Some(self)); self.will_mutate_attr(attr); - self.attrs.borrow_mut().push(JS::from_ref(attr)); + self.attrs.borrow_mut().push(Dom::from_ref(attr)); if attr.namespace() == &ns!() { vtable_for(self.upcast()).attribute_mutated(attr, AttributeMutation::Set(None)); } } - pub fn get_attribute(&self, namespace: &Namespace, local_name: &LocalName) -> Option<Root<Attr>> { + pub fn get_attribute( + &self, + namespace: &Namespace, + local_name: &LocalName, + ) -> Option<DomRoot<Attr>> { self.attrs .borrow() .iter() .find(|attr| attr.local_name() == local_name && attr.namespace() == namespace) - .map(|js| Root::from_ref(&**js)) + .map(|js| DomRoot::from_ref(&**js)) } // https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name - pub fn get_attribute_by_name(&self, name: DOMString) -> Option<Root<Attr>> { + pub fn get_attribute_by_name(&self, name: DOMString) -> Option<DomRoot<Attr>> { let name = &self.parsed_name(name); - self.attrs.borrow().iter().find(|a| a.name() == name).map(|js| Root::from_ref(&**js)) + let maybe_attribute = self + .attrs + .borrow() + .iter() + .find(|a| a.name() == name) + .map(|js| DomRoot::from_ref(&**js)); + fn id_and_name_must_be_atoms(name: &LocalName, maybe_attr: &Option<DomRoot<Attr>>) -> bool { + if *name == local_name!("id") || *name == local_name!("name") { + match maybe_attr { + None => true, + Some(ref attr) => match *attr.value() { + AttrValue::Atom(_) => true, + _ => false, + }, + } + } else { + true + } + } + debug_assert!(id_and_name_must_be_atoms(name, &maybe_attribute)); + maybe_attribute } - pub fn set_attribute_from_parser(&self, - qname: QualName, - value: DOMString, - prefix: Option<Prefix>) { + pub fn set_attribute_from_parser( + &self, + qname: QualName, + value: DOMString, + prefix: Option<Prefix>, + ) { // Don't set if the attribute already exists, so we can handle add_attrs_if_missing - if self.attrs - .borrow() - .iter() - .any(|a| *a.local_name() == qname.local && *a.namespace() == qname.ns) { + if self + .attrs + .borrow() + .iter() + .any(|a| *a.local_name() == qname.local && *a.namespace() == qname.ns) + { return; } @@ -1054,12 +1464,9 @@ impl Element { assert!(name == &name.to_ascii_lowercase()); assert!(!name.contains(":")); - self.set_first_matching_attribute(name.clone(), - value, - name.clone(), - ns!(), - None, - |attr| attr.local_name() == name); + self.set_first_matching_attribute(name.clone(), value, name.clone(), ns!(), None, |attr| { + attr.local_name() == name + }); } // https://html.spec.whatwg.org/multipage/#attr-data-* @@ -1072,31 +1479,29 @@ impl Element { // Steps 2-5. let name = LocalName::from(name); let value = self.parse_attribute(&ns!(), &name, value); - self.set_first_matching_attribute(name.clone(), - value, - name.clone(), - ns!(), - None, - |attr| { - *attr.name() == name && *attr.namespace() == ns!() - }); + self.set_first_matching_attribute(name.clone(), value, name.clone(), ns!(), None, |attr| { + *attr.name() == name && *attr.namespace() == ns!() + }); Ok(()) } - fn set_first_matching_attribute<F>(&self, - local_name: LocalName, - value: AttrValue, - name: LocalName, - namespace: Namespace, - prefix: Option<Prefix>, - find: F) - where F: Fn(&Attr) -> bool + fn set_first_matching_attribute<F>( + &self, + local_name: LocalName, + value: AttrValue, + name: LocalName, + namespace: Namespace, + prefix: Option<Prefix>, + find: F, + ) where + F: Fn(&Attr) -> bool, { - let attr = self.attrs - .borrow() - .iter() - .find(|attr| find(&attr)) - .map(|js| Root::from_ref(&**js)); + let attr = self + .attrs + .borrow() + .iter() + .find(|attr| find(&attr)) + .map(|js| DomRoot::from_ref(&**js)); if let Some(attr) = attr { attr.set_value(value, self); } else { @@ -1104,11 +1509,12 @@ impl Element { }; } - pub fn parse_attribute(&self, - namespace: &Namespace, - local_name: &LocalName, - value: DOMString) - -> AttrValue { + pub fn parse_attribute( + &self, + namespace: &Namespace, + local_name: &LocalName, + value: DOMString, + ) -> AttrValue { if *namespace == ns!() { vtable_for(self.upcast()).parse_plain_attribute(local_name, value) } else { @@ -1116,24 +1522,44 @@ impl Element { } } - pub fn remove_attribute(&self, namespace: &Namespace, local_name: &LocalName) -> Option<Root<Attr>> { + pub fn remove_attribute( + &self, + namespace: &Namespace, + local_name: &LocalName, + ) -> Option<DomRoot<Attr>> { self.remove_first_matching_attribute(|attr| { attr.namespace() == namespace && attr.local_name() == local_name }) } - pub fn remove_attribute_by_name(&self, name: &LocalName) -> Option<Root<Attr>> { + pub fn remove_attribute_by_name(&self, name: &LocalName) -> Option<DomRoot<Attr>> { self.remove_first_matching_attribute(|attr| attr.name() == name) } - fn remove_first_matching_attribute<F>(&self, find: F) -> Option<Root<Attr>> - where F: Fn(&Attr) -> bool + fn remove_first_matching_attribute<F>(&self, find: F) -> Option<DomRoot<Attr>> + where + F: Fn(&Attr) -> bool, { let idx = self.attrs.borrow().iter().position(|attr| find(&attr)); - idx.map(|idx| { - let attr = Root::from_ref(&*(*self.attrs.borrow())[idx]); + let attr = DomRoot::from_ref(&*(*self.attrs.borrow())[idx]); self.will_mutate_attr(&attr); + + let name = attr.local_name().clone(); + let namespace = attr.namespace().clone(); + let old_value = DOMString::from(&**attr.value()); + let mutation = Mutation::Attribute { + name: name.clone(), + namespace: namespace.clone(), + old_value: Some(old_value.clone()), + }; + + MutationObserver::queue_a_mutation_record(&self.node, mutation); + + let reaction = + CallbackReaction::AttributeChanged(name, Some(old_value), None, namespace); + ScriptThread::enqueue_callback_reaction(self, reaction, None); + self.attrs.borrow_mut().remove(idx); attr.set_owner(None); if attr.namespace() == &ns!() { @@ -1143,16 +1569,14 @@ impl Element { }) } - pub fn has_class(&self, name: &Atom) -> bool { - let quirks_mode = document_from_node(self).quirks_mode(); - let is_equal = |lhs: &Atom, rhs: &Atom| { - match quirks_mode { - QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => lhs == rhs, - QuirksMode::Quirks => lhs.eq_ignore_ascii_case(&rhs), - } - }; + pub fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { self.get_attribute(&ns!(), &local_name!("class")) - .map_or(false, |attr| attr.value().as_tokens().iter().any(|atom| is_equal(name, atom))) + .map_or(false, |attr| { + attr.value() + .as_tokens() + .iter() + .any(|atom| case_sensitivity.eq_atom(name, atom)) + }) } pub fn set_atomic_attribute(&self, local_name: &LocalName, value: DOMString) { @@ -1180,23 +1604,24 @@ impl Element { } } - pub fn get_url_attribute(&self, local_name: &LocalName) -> DOMString { + pub fn get_url_attribute(&self, local_name: &LocalName) -> USVString { assert!(*local_name == local_name.to_ascii_lowercase()); - if !self.has_attribute(local_name) { - return DOMString::new(); - } - let url = self.get_string_attribute(local_name); - let doc = document_from_node(self); - let base = doc.base_url(); - // https://html.spec.whatwg.org/multipage/#reflect + let attr = match self.get_attribute(&ns!(), local_name) { + Some(attr) => attr, + None => return USVString::default(), + }; + let value = &**attr.value(); // XXXManishearth this doesn't handle `javascript:` urls properly - match base.join(&url) { - Ok(parsed) => DOMString::from(parsed.into_string()), - Err(_) => DOMString::from(""), - } + document_from_node(self) + .base_url() + .join(value) + .map(|parsed| USVString(parsed.into_string())) + .unwrap_or_else(|_| USVString(value.to_owned())) } - pub fn set_url_attribute(&self, local_name: &LocalName, value: DOMString) { - self.set_string_attribute(local_name, value); + + pub fn set_url_attribute(&self, local_name: &LocalName, value: USVString) { + assert!(*local_name == local_name.to_ascii_lowercase()); + self.set_attribute(local_name, AttrValue::String(value.to_string())); } pub fn get_string_attribute(&self, local_name: &LocalName) -> DOMString { @@ -1205,23 +1630,24 @@ impl Element { None => DOMString::new(), } } + pub fn set_string_attribute(&self, local_name: &LocalName, value: DOMString) { assert!(*local_name == local_name.to_ascii_lowercase()); self.set_attribute(local_name, AttrValue::String(value.into())); } pub fn get_tokenlist_attribute(&self, local_name: &LocalName) -> Vec<Atom> { - self.get_attribute(&ns!(), local_name).map(|attr| { - attr.value() - .as_tokens() - .to_vec() - }).unwrap_or(vec!()) + self.get_attribute(&ns!(), local_name) + .map(|attr| attr.value().as_tokens().to_vec()) + .unwrap_or(vec![]) } pub fn set_tokenlist_attribute(&self, local_name: &LocalName, value: DOMString) { assert!(*local_name == local_name.to_ascii_lowercase()); - self.set_attribute(local_name, - AttrValue::from_serialized_tokenlist(value.into())); + self.set_attribute( + local_name, + AttrValue::from_serialized_tokenlist(value.into()), + ); } pub fn set_atomic_tokenlist_attribute(&self, local_name: &LocalName, tokens: Vec<Atom>) { @@ -1231,19 +1657,19 @@ impl Element { pub fn get_int_attribute(&self, local_name: &LocalName, default: i32) -> i32 { // TODO: Is this assert necessary? - assert!(local_name.chars().all(|ch| { - !ch.is_ascii() || ch.to_ascii_lowercase() == ch - })); + assert!(local_name + .chars() + .all(|ch| !ch.is_ascii() || ch.to_ascii_lowercase() == ch)); let attribute = self.get_attribute(&ns!(), local_name); match attribute { - Some(ref attribute) => { - match *attribute.value() { - AttrValue::Int(_, value) => value, - _ => panic!("Expected an AttrValue::Int: \ - implement parse_plain_attribute"), - } - } + Some(ref attribute) => match *attribute.value() { + AttrValue::Int(_, value) => value, + _ => panic!( + "Expected an AttrValue::Int: \ + implement parse_plain_attribute" + ), + }, None => default, } } @@ -1254,15 +1680,15 @@ impl Element { } pub fn get_uint_attribute(&self, local_name: &LocalName, default: u32) -> u32 { - assert!(local_name.chars().all(|ch| !ch.is_ascii() || ch.to_ascii_lowercase() == ch)); + assert!(local_name + .chars() + .all(|ch| !ch.is_ascii() || ch.to_ascii_lowercase() == ch)); let attribute = self.get_attribute(&ns!(), local_name); match attribute { - Some(ref attribute) => { - match *attribute.value() { - AttrValue::UInt(_, value) => value, - _ => panic!("Expected an AttrValue::UInt: implement parse_plain_attribute"), - } - } + Some(ref attribute) => match *attribute.value() { + AttrValue::UInt(_, value) => value, + _ => panic!("Expected an AttrValue::UInt: implement parse_plain_attribute"), + }, None => default, } } @@ -1277,8 +1703,11 @@ impl Element { } // https://dom.spec.whatwg.org/#insert-adjacent - pub fn insert_adjacent(&self, where_: AdjacentPosition, node: &Node) - -> Fallible<Option<Root<Node>>> { + pub fn insert_adjacent( + &self, + where_: AdjacentPosition, + node: &Node, + ) -> Fallible<Option<DomRoot<Node>>> { let self_node = self.upcast::<Node>(); match where_ { AdjacentPosition::BeforeBegin => { @@ -1287,20 +1716,18 @@ impl Element { } else { Ok(None) } - } + }, AdjacentPosition::AfterBegin => { - Node::pre_insert(node, &self_node, self_node.GetFirstChild().r()).map(Some) - } - AdjacentPosition::BeforeEnd => { - Node::pre_insert(node, &self_node, None).map(Some) - } + Node::pre_insert(node, &self_node, self_node.GetFirstChild().as_deref()).map(Some) + }, + AdjacentPosition::BeforeEnd => Node::pre_insert(node, &self_node, None).map(Some), AdjacentPosition::AfterEnd => { if let Some(parent) = self_node.GetParentNode() { - Node::pre_insert(node, &parent, self_node.GetNextSibling().r()).map(Some) + Node::pre_insert(node, &parent, self_node.GetNextSibling().as_deref()).map(Some) } else { Ok(None) } - } + }, } } @@ -1336,21 +1763,25 @@ impl Element { } // Step 9 - if doc.GetBody().r() == self.downcast::<HTMLElement>() && - doc.quirks_mode() == QuirksMode::Quirks && - !self.potentially_scrollable() { - win.scroll(x, y, behavior); - return; + if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() && + doc.quirks_mode() == QuirksMode::Quirks && + !self.potentially_scrollable() + { + win.scroll(x, y, behavior); + return; } - // Step 10 (TODO) + // Step 10 + if !self.has_css_layout_box() || !self.has_scrolling_box() || !self.has_overflow() { + return; + } // Step 11 - win.scroll_node(node.to_trusted_node_address(), x, y, behavior); + win.scroll_node(node, x, y, behavior); } // https://w3c.github.io/DOM-Parsing/#parsing - pub fn parse_fragment(&self, markup: DOMString) -> Fallible<Root<DocumentFragment>> { + pub fn parse_fragment(&self, markup: DOMString) -> Fallible<DomRoot<DocumentFragment>> { // Steps 1-2. let context_document = document_from_node(self); // TODO(#11995): XML case. @@ -1365,21 +1796,22 @@ impl Element { Ok(fragment) } - pub fn fragment_parsing_context(owner_doc: &Document, element: Option<&Self>) -> Root<Self> { + pub fn fragment_parsing_context(owner_doc: &Document, element: Option<&Self>) -> DomRoot<Self> { match element { - Some(elem) if elem.local_name() != &local_name!("html") || !elem.html_element_in_html_document() => { - Root::from_ref(elem) + Some(elem) + if elem.local_name() != &local_name!("html") || + !elem.html_element_in_html_document() => + { + DomRoot::from_ref(elem) }, - _ => { - Root::upcast(HTMLBodyElement::new(local_name!("body"), None, owner_doc)) - } + _ => DomRoot::upcast(HTMLBodyElement::new(local_name!("body"), None, owner_doc)), } } // https://fullscreen.spec.whatwg.org/#fullscreen-element-ready-check pub fn fullscreen_element_ready_check(&self) -> bool { if !self.is_connected() { - return false + return false; } let document = document_from_node(self); document.get_allow_fullscreen() @@ -1387,11 +1819,81 @@ impl Element { // https://html.spec.whatwg.org/multipage/#home-subtree pub fn is_in_same_home_subtree<T>(&self, other: &T) -> bool - where T: DerivedFrom<Element> + DomObject + where + T: DerivedFrom<Element> + DomObject, { let other = other.upcast::<Element>(); self.root_element() == other.root_element() } + + pub fn get_id(&self) -> Option<Atom> { + self.id_attribute.borrow().clone() + } + + pub fn get_name(&self) -> Option<Atom> { + self.rare_data().as_ref()?.name_attribute.clone() + } + + fn is_sequentially_focusable(&self) -> bool { + let element = self.upcast::<Element>(); + let node = self.upcast::<Node>(); + if !node.is_connected() { + return false; + } + + if element.has_attribute(&local_name!("hidden")) { + return false; + } + + if self.disabled_state() { + return false; + } + + if element.has_attribute(&local_name!("tabindex")) { + return true; + } + + match node.type_id() { + // <button>, <select>, <iframe>, and <textarea> are implicitly focusable. + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLButtonElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLSelectElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLIFrameElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement, + )) => true, + + // Links that generate actual links are focusable. + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLAnchorElement, + )) => element.has_attribute(&local_name!("href")), + + //TODO focusable if editing host + //TODO focusable if "sorting interface th elements" + _ => { + // Draggable elements are focusable. + element.get_string_attribute(&local_name!("draggable")) == "true" + }, + } + } + + pub(crate) fn update_sequentially_focusable_status(&self) { + let node = self.upcast::<Node>(); + let is_sequentially_focusable = self.is_sequentially_focusable(); + node.set_flag(NodeFlags::SEQUENTIALLY_FOCUSABLE, is_sequentially_focusable); + + // https://html.spec.whatwg.org/multipage/#focus-fixup-rule + if !is_sequentially_focusable { + let document = document_from_node(self); + document.perform_focus_fixup_rule(self); + } + } } impl ElementMethods for Element { @@ -1408,17 +1910,15 @@ impl ElementMethods for Element { // https://dom.spec.whatwg.org/#dom-element-prefix fn GetPrefix(&self) -> Option<DOMString> { - self.prefix.clone() + self.prefix.borrow().as_ref().map(|p| DOMString::from(&**p)) } // https://dom.spec.whatwg.org/#dom-element-tagname fn TagName(&self) -> DOMString { let name = self.tag_name.or_init(|| { - let qualified_name = match self.prefix { - Some(ref prefix) => { - Cow::Owned(format!("{}:{}", &**prefix, &*self.local_name)) - }, - None => Cow::Borrowed(&*self.local_name) + let qualified_name = match *self.prefix.borrow() { + Some(ref prefix) => Cow::Owned(format!("{}:{}", &**prefix, &*self.local_name)), + None => Cow::Borrowed(&*self.local_name), }; if self.html_element_in_html_document() { LocalName::from(qualified_name.to_ascii_uppercase()) @@ -1430,6 +1930,8 @@ impl ElementMethods for Element { } // https://dom.spec.whatwg.org/#dom-element-id + // This always returns a string; if you'd rather see None + // on a null id, call get_id fn Id(&self) -> DOMString { self.get_string_attribute(&local_name!("id")) } @@ -1450,13 +1952,15 @@ impl ElementMethods for Element { } // https://dom.spec.whatwg.org/#dom-element-classlist - fn ClassList(&self) -> Root<DOMTokenList> { - self.class_list.or_init(|| DOMTokenList::new(self, &local_name!("class"))) + fn ClassList(&self) -> DomRoot<DOMTokenList> { + self.class_list + .or_init(|| DOMTokenList::new(self, &local_name!("class"), None)) } // https://dom.spec.whatwg.org/#dom-element-attributes - fn Attributes(&self) -> Root<NamedNodeMap> { - self.attr_list.or_init(|| NamedNodeMap::new(&window_from_node(self), self)) + fn Attributes(&self) -> DomRoot<NamedNodeMap> { + self.attr_list + .or_init(|| NamedNodeMap::new(&window_from_node(self), self)) } // https://dom.spec.whatwg.org/#dom-element-hasattributes @@ -1471,33 +1975,76 @@ impl ElementMethods for Element { // https://dom.spec.whatwg.org/#dom-element-getattribute fn GetAttribute(&self, name: DOMString) -> Option<DOMString> { - self.GetAttributeNode(name) - .map(|s| s.Value()) + self.GetAttributeNode(name).map(|s| s.Value()) } // https://dom.spec.whatwg.org/#dom-element-getattributens - fn GetAttributeNS(&self, - namespace: Option<DOMString>, - local_name: DOMString) - -> Option<DOMString> { + fn GetAttributeNS( + &self, + namespace: Option<DOMString>, + local_name: DOMString, + ) -> Option<DOMString> { self.GetAttributeNodeNS(namespace, local_name) .map(|attr| attr.Value()) } // https://dom.spec.whatwg.org/#dom-element-getattributenode - fn GetAttributeNode(&self, name: DOMString) -> Option<Root<Attr>> { + fn GetAttributeNode(&self, name: DOMString) -> Option<DomRoot<Attr>> { self.get_attribute_by_name(name) } // https://dom.spec.whatwg.org/#dom-element-getattributenodens - fn GetAttributeNodeNS(&self, - namespace: Option<DOMString>, - local_name: DOMString) - -> Option<Root<Attr>> { + fn GetAttributeNodeNS( + &self, + namespace: Option<DOMString>, + local_name: DOMString, + ) -> Option<DomRoot<Attr>> { let namespace = &namespace_from_domstring(namespace); self.get_attribute(namespace, &LocalName::from(local_name)) } + // https://dom.spec.whatwg.org/#dom-element-toggleattribute + fn ToggleAttribute(&self, name: DOMString, force: Option<bool>) -> Fallible<bool> { + // Step 1. + if xml_name_type(&name) == InvalidXMLName { + return Err(Error::InvalidCharacter); + } + + // Step 3. + let attribute = self.GetAttribute(name.clone()); + + // Step 2. + let name = self.parsed_name(name); + match attribute { + // Step 4 + None => match force { + // Step 4.1. + None | Some(true) => { + self.set_first_matching_attribute( + name.clone(), + AttrValue::String(String::new()), + name.clone(), + ns!(), + None, + |attr| *attr.name() == name, + ); + Ok(true) + }, + // Step 4.2. + Some(false) => Ok(false), + }, + Some(_index) => match force { + // Step 5. + None | Some(false) => { + self.remove_attribute_by_name(&name); + Ok(false) + }, + // Step 6. + Some(true) => Ok(true), + }, + } + } + // https://dom.spec.whatwg.org/#dom-element-setattribute fn SetAttribute(&self, name: DOMString, value: DOMString) -> ErrorResult { // Step 1. @@ -1510,29 +2057,35 @@ impl ElementMethods for Element { // Step 3-5. let value = self.parse_attribute(&ns!(), &name, value); - self.set_first_matching_attribute( - name.clone(), value, name.clone(), ns!(), None, - |attr| *attr.name() == name); + self.set_first_matching_attribute(name.clone(), value, name.clone(), ns!(), None, |attr| { + *attr.name() == name + }); Ok(()) } // https://dom.spec.whatwg.org/#dom-element-setattributens - fn SetAttributeNS(&self, - namespace: Option<DOMString>, - qualified_name: DOMString, - value: DOMString) -> ErrorResult { - let (namespace, prefix, local_name) = - try!(validate_and_extract(namespace, &qualified_name)); + fn SetAttributeNS( + &self, + namespace: Option<DOMString>, + qualified_name: DOMString, + value: DOMString, + ) -> ErrorResult { + let (namespace, prefix, local_name) = validate_and_extract(namespace, &qualified_name)?; let qualified_name = LocalName::from(qualified_name); let value = self.parse_attribute(&namespace, &local_name, value); self.set_first_matching_attribute( - local_name.clone(), value, qualified_name, namespace.clone(), prefix, - |attr| *attr.local_name() == local_name && *attr.namespace() == namespace); + local_name.clone(), + value, + qualified_name, + namespace.clone(), + prefix, + |attr| *attr.local_name() == local_name && *attr.namespace() == namespace, + ); Ok(()) } // https://dom.spec.whatwg.org/#dom-element-setattributenode - fn SetAttributeNode(&self, attr: &Attr) -> Fallible<Option<Root<Attr>>> { + fn SetAttributeNode(&self, attr: &Attr) -> Fallible<Option<DomRoot<Attr>>> { // Step 1. if let Some(owner) = attr.GetOwnerElement() { if &*owner != self { @@ -1540,27 +2093,46 @@ impl ElementMethods for Element { } } + let vtable = vtable_for(self.upcast()); + + // This ensures that the attribute is of the expected kind for this + // specific element. This is inefficient and should probably be done + // differently. + attr.swap_value(&mut vtable.parse_plain_attribute(attr.local_name(), attr.Value())); + // Step 2. let position = self.attrs.borrow().iter().position(|old_attr| { attr.namespace() == old_attr.namespace() && attr.local_name() == old_attr.local_name() }); if let Some(position) = position { - let old_attr = Root::from_ref(&*self.attrs.borrow()[position]); + let old_attr = DomRoot::from_ref(&*self.attrs.borrow()[position]); // Step 3. if &*old_attr == attr { - return Ok(Some(Root::from_ref(attr))); + return Ok(Some(DomRoot::from_ref(attr))); } // Step 4. + if self.get_custom_element_definition().is_some() { + let old_name = old_attr.local_name().clone(); + let old_value = DOMString::from(&**old_attr.value()); + let new_value = DOMString::from(&**attr.value()); + let namespace = old_attr.namespace().clone(); + let reaction = CallbackReaction::AttributeChanged( + old_name, + Some(old_value), + Some(new_value), + namespace, + ); + ScriptThread::enqueue_callback_reaction(self, reaction, None); + } self.will_mutate_attr(attr); attr.set_owner(Some(self)); - self.attrs.borrow_mut()[position] = JS::from_ref(attr); + self.attrs.borrow_mut()[position] = Dom::from_ref(attr); old_attr.set_owner(None); if attr.namespace() == &ns!() { - vtable_for(self.upcast()).attribute_mutated( - &attr, AttributeMutation::Set(Some(&old_attr.value()))); + vtable.attribute_mutated(&attr, AttributeMutation::Set(Some(&old_attr.value()))); } // Step 6. @@ -1576,7 +2148,7 @@ impl ElementMethods for Element { } // https://dom.spec.whatwg.org/#dom-element-setattributenodens - fn SetAttributeNodeNS(&self, attr: &Attr) -> Fallible<Option<Root<Attr>>> { + fn SetAttributeNodeNS(&self, attr: &Attr) -> Fallible<Option<DomRoot<Attr>>> { self.SetAttributeNode(attr) } @@ -1594,7 +2166,7 @@ impl ElementMethods for Element { } // https://dom.spec.whatwg.org/#dom-element-removeattributenode - fn RemoveAttributeNode(&self, attr: &Attr) -> Fallible<Root<Attr>> { + fn RemoveAttributeNode(&self, attr: &Attr) -> Fallible<DomRoot<Attr>> { self.remove_first_matching_attribute(|a| a == attr) .ok_or(Error::NotFound) } @@ -1610,48 +2182,56 @@ impl ElementMethods for Element { } // https://dom.spec.whatwg.org/#dom-element-getelementsbytagname - fn GetElementsByTagName(&self, localname: DOMString) -> Root<HTMLCollection> { + fn GetElementsByTagName(&self, localname: DOMString) -> DomRoot<HTMLCollection> { let window = window_from_node(self); HTMLCollection::by_qualified_name(&window, self.upcast(), LocalName::from(&*localname)) } // https://dom.spec.whatwg.org/#dom-element-getelementsbytagnamens - fn GetElementsByTagNameNS(&self, - maybe_ns: Option<DOMString>, - localname: DOMString) - -> Root<HTMLCollection> { + fn GetElementsByTagNameNS( + &self, + maybe_ns: Option<DOMString>, + localname: DOMString, + ) -> DomRoot<HTMLCollection> { let window = window_from_node(self); HTMLCollection::by_tag_name_ns(&window, self.upcast(), localname, maybe_ns) } // https://dom.spec.whatwg.org/#dom-element-getelementsbyclassname - fn GetElementsByClassName(&self, classes: DOMString) -> Root<HTMLCollection> { + fn GetElementsByClassName(&self, classes: DOMString) -> DomRoot<HTMLCollection> { let window = window_from_node(self); HTMLCollection::by_class_name(&window, self.upcast(), classes) } // https://drafts.csswg.org/cssom-view/#dom-element-getclientrects - fn GetClientRects(&self) -> Vec<Root<DOMRect>> { + fn GetClientRects(&self) -> Vec<DomRoot<DOMRect>> { let win = window_from_node(self); let raw_rects = self.upcast::<Node>().content_boxes(); - raw_rects.iter().map(|rect| { - DOMRect::new(win.upcast(), - rect.origin.x.to_f64_px(), - rect.origin.y.to_f64_px(), - rect.size.width.to_f64_px(), - rect.size.height.to_f64_px()) - }).collect() + raw_rects + .iter() + .map(|rect| { + DOMRect::new( + win.upcast(), + rect.origin.x.to_f64_px(), + rect.origin.y.to_f64_px(), + rect.size.width.to_f64_px(), + rect.size.height.to_f64_px(), + ) + }) + .collect() } // https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect - fn GetBoundingClientRect(&self) -> Root<DOMRect> { + fn GetBoundingClientRect(&self) -> DomRoot<DOMRect> { let win = window_from_node(self); let rect = self.upcast::<Node>().bounding_content_box_or_zero(); - DOMRect::new(win.upcast(), - rect.origin.x.to_f64_px(), - rect.origin.y.to_f64_px(), - rect.size.width.to_f64_px(), - rect.size.height.to_f64_px()) + DOMRect::new( + win.upcast(), + rect.origin.x.to_f64_px(), + rect.origin.y.to_f64_px(), + rect.size.width.to_f64_px(), + rect.size.height.to_f64_px(), + ) } // https://drafts.csswg.org/cssom-view/#dom-element-scroll @@ -1684,8 +2264,7 @@ impl ElementMethods for Element { let delta_top = options.top.unwrap_or(0.0f64); let left = self.ScrollLeft(); let top = self.ScrollTop(); - self.scroll(left + delta_left, top + delta_top, - options.parent.behavior); + self.scroll(left + delta_left, top + delta_top, options.parent.behavior); } // https://drafts.csswg.org/cssom-view/#dom-element-scrollby @@ -1724,13 +2303,13 @@ impl ElementMethods for Element { } // Step 7 - if doc.GetBody().r() == self.downcast::<HTMLElement>() && - doc.quirks_mode() == QuirksMode::Quirks && - !self.potentially_scrollable() { - return win.ScrollY() as f64; + if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() && + doc.quirks_mode() == QuirksMode::Quirks && + !self.potentially_scrollable() + { + return win.ScrollY() as f64; } - // Step 8 if !self.has_css_layout_box() { return 0.0; @@ -1774,17 +2353,21 @@ impl ElementMethods for Element { } // Step 9 - if doc.GetBody().r() == self.downcast::<HTMLElement>() && - doc.quirks_mode() == QuirksMode::Quirks && - !self.potentially_scrollable() { - win.scroll(win.ScrollX() as f64, y, behavior); - return; + if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() && + doc.quirks_mode() == QuirksMode::Quirks && + !self.potentially_scrollable() + { + win.scroll(win.ScrollX() as f64, y, behavior); + return; } - // Step 10 (TODO) + // Step 10 + if !self.has_css_layout_box() || !self.has_scrolling_box() || !self.has_overflow() { + return; + } // Step 11 - win.scroll_node(node.to_trusted_node_address(), self.ScrollLeft(), y, behavior); + win.scroll_node(node, self.ScrollLeft(), y, behavior); } // https://drafts.csswg.org/cssom-view/#dom-element-scrolltop @@ -1816,13 +2399,13 @@ impl ElementMethods for Element { } // Step 7 - if doc.GetBody().r() == self.downcast::<HTMLElement>() && - doc.quirks_mode() == QuirksMode::Quirks && - !self.potentially_scrollable() { - return win.ScrollX() as f64; + if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() && + doc.quirks_mode() == QuirksMode::Quirks && + !self.potentially_scrollable() + { + return win.ScrollX() as f64; } - // Step 8 if !self.has_css_layout_box() { return 0.0; @@ -1867,17 +2450,21 @@ impl ElementMethods for Element { } // Step 9 - if doc.GetBody().r() == self.downcast::<HTMLElement>() && - doc.quirks_mode() == QuirksMode::Quirks && - !self.potentially_scrollable() { - win.scroll(x, win.ScrollY() as f64, behavior); - return; + if doc.GetBody().as_deref() == self.downcast::<HTMLElement>() && + doc.quirks_mode() == QuirksMode::Quirks && + !self.potentially_scrollable() + { + win.scroll(x, win.ScrollY() as f64, behavior); + return; } - // Step 10 (TODO) + // Step 10 + if !self.has_css_layout_box() || !self.has_scrolling_box() || !self.has_overflow() { + return; + } // Step 11 - win.scroll_node(node.to_trusted_node_address(), x, self.ScrollTop(), behavior); + win.scroll_node(node, x, self.ScrollTop(), behavior); } // https://drafts.csswg.org/cssom-view/#dom-element-scrollwidth @@ -1892,48 +2479,75 @@ impl ElementMethods for Element { // https://drafts.csswg.org/cssom-view/#dom-element-clienttop fn ClientTop(&self) -> i32 { - self.upcast::<Node>().client_rect().origin.y + self.client_rect().origin.y } // https://drafts.csswg.org/cssom-view/#dom-element-clientleft fn ClientLeft(&self) -> i32 { - self.upcast::<Node>().client_rect().origin.x + self.client_rect().origin.x } // https://drafts.csswg.org/cssom-view/#dom-element-clientwidth fn ClientWidth(&self) -> i32 { - self.upcast::<Node>().client_rect().size.width + self.client_rect().size.width } // https://drafts.csswg.org/cssom-view/#dom-element-clientheight fn ClientHeight(&self) -> i32 { - self.upcast::<Node>().client_rect().size.height + self.client_rect().size.height } - /// https://w3c.github.io/DOM-Parsing/#widl-Element-innerHTML + /// <https://w3c.github.io/DOM-Parsing/#widl-Element-innerHTML> fn GetInnerHTML(&self) -> Fallible<DOMString> { - // XXX TODO: XML case - self.serialize(ChildrenOnly) + let qname = QualName::new( + self.prefix().clone(), + self.namespace().clone(), + self.local_name().clone(), + ); + if document_from_node(self).is_html_document() { + return self.serialize(ChildrenOnly(Some(qname))); + } else { + return self.xmlSerialize(XmlChildrenOnly(Some(qname))); + } } - /// https://w3c.github.io/DOM-Parsing/#widl-Element-innerHTML + /// <https://w3c.github.io/DOM-Parsing/#widl-Element-innerHTML> fn SetInnerHTML(&self, value: DOMString) -> ErrorResult { - // Step 1. - let frag = try!(self.parse_fragment(value)); // Step 2. // https://github.com/w3c/DOM-Parsing/issues/1 let target = if let Some(template) = self.downcast::<HTMLTemplateElement>() { - Root::upcast(template.Content()) + DomRoot::upcast(template.Content()) } else { - Root::from_ref(self.upcast()) + DomRoot::from_ref(self.upcast()) }; + + // Fast path for when the value is small, doesn't contain any markup and doesn't require + // extra work to set innerHTML. + if !self.node.has_weird_parser_insertion_mode() && + value.len() < 100 && + !value + .as_bytes() + .iter() + .any(|c| matches!(*c, b'&' | b'\0' | b'<' | b'\r')) + { + Node::SetTextContent(&target, Some(value)); + return Ok(()); + } + + // Step 1. + let frag = self.parse_fragment(value)?; + Node::replace_all(Some(frag.upcast()), &target); Ok(()) } // https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-outerHTML fn GetOuterHTML(&self) -> Fallible<DOMString> { - self.serialize(IncludeNode) + if document_from_node(self).is_html_document() { + return self.serialize(IncludeNode); + } else { + return self.xmlSerialize(XmlIncludeNode); + } } // https://w3c.github.io/DOM-Parsing/#dom-element-outerhtml @@ -1954,46 +2568,59 @@ impl ElementMethods for Element { NodeTypeId::Document(_) => return Err(Error::NoModificationAllowed), // Step 4. - NodeTypeId::DocumentFragment => { - let body_elem = Element::create(QualName::new(ns!(html), local_name!("body")), - None, &context_document, - ElementCreator::ScriptCreated); - Root::upcast(body_elem) + NodeTypeId::DocumentFragment(_) => { + let body_elem = Element::create( + QualName::new(None, ns!(html), local_name!("body")), + None, + &context_document, + ElementCreator::ScriptCreated, + CustomElementCreationMode::Synchronous, + ); + DomRoot::upcast(body_elem) }, - _ => context_node.GetParentElement().unwrap() + _ => context_node.GetParentElement().unwrap(), }; // Step 5. - let frag = try!(parent.parse_fragment(value)); + let frag = parent.parse_fragment(value)?; // Step 6. - try!(context_parent.ReplaceChild(frag.upcast(), context_node)); + context_parent.ReplaceChild(frag.upcast(), context_node)?; Ok(()) } // https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-previouselementsibling - fn GetPreviousElementSibling(&self) -> Option<Root<Element>> { - self.upcast::<Node>().preceding_siblings().filter_map(Root::downcast).next() + fn GetPreviousElementSibling(&self) -> Option<DomRoot<Element>> { + self.upcast::<Node>() + .preceding_siblings() + .filter_map(DomRoot::downcast) + .next() } // https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-nextelementsibling - fn GetNextElementSibling(&self) -> Option<Root<Element>> { - self.upcast::<Node>().following_siblings().filter_map(Root::downcast).next() + fn GetNextElementSibling(&self) -> Option<DomRoot<Element>> { + self.upcast::<Node>() + .following_siblings() + .filter_map(DomRoot::downcast) + .next() } // https://dom.spec.whatwg.org/#dom-parentnode-children - fn Children(&self) -> Root<HTMLCollection> { + fn Children(&self) -> DomRoot<HTMLCollection> { let window = window_from_node(self); HTMLCollection::children(&window, self.upcast()) } // https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild - fn GetFirstElementChild(&self) -> Option<Root<Element>> { + fn GetFirstElementChild(&self) -> Option<DomRoot<Element>> { self.upcast::<Node>().child_elements().next() } // https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild - fn GetLastElementChild(&self) -> Option<Root<Element>> { - self.upcast::<Node>().rev_children().filter_map(Root::downcast::<Element>).next() + fn GetLastElementChild(&self) -> Option<DomRoot<Element>> { + self.upcast::<Node>() + .rev_children() + .filter_map(DomRoot::downcast::<Element>) + .next() } // https://dom.spec.whatwg.org/#dom-parentnode-childelementcount @@ -2011,14 +2638,19 @@ impl ElementMethods for Element { self.upcast::<Node>().append(nodes) } + // https://dom.spec.whatwg.org/#dom-parentnode-replacechildren + fn ReplaceChildren(&self, nodes: Vec<NodeOrString>) -> ErrorResult { + self.upcast::<Node>().replace_children(nodes) + } + // https://dom.spec.whatwg.org/#dom-parentnode-queryselector - fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> { + fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> { let root = self.upcast::<Node>(); root.query_selector(selectors) } // https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall - fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<Root<NodeList>> { + fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<DomRoot<NodeList>> { let root = self.upcast::<Node>(); root.query_selector_all(selectors) } @@ -2045,12 +2677,15 @@ impl ElementMethods for Element { // https://dom.spec.whatwg.org/#dom-element-matches fn Matches(&self, selectors: DOMString) -> Fallible<bool> { - match SelectorParser::parse_author_origin_no_namespace(&selectors) { - Err(()) => Err(Error::Syntax), - Ok(selectors) => { - Ok(matches_selector_list(&selectors.0, &Root::from_ref(self), None)) - } - } + let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors) { + Err(_) => return Err(Error::Syntax), + Ok(selectors) => selectors, + }; + + let quirks_mode = document_from_node(self).quirks_mode(); + let element = DomRoot::from_ref(self); + + Ok(dom_apis::element_matches(&element, &selectors, quirks_mode)) } // https://dom.spec.whatwg.org/#dom-element-webkitmatchesselector @@ -2059,73 +2694,71 @@ impl ElementMethods for Element { } // https://dom.spec.whatwg.org/#dom-element-closest - fn Closest(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> { - match SelectorParser::parse_author_origin_no_namespace(&selectors) { - Err(()) => Err(Error::Syntax), - Ok(selectors) => { - let root = self.upcast::<Node>(); - for element in root.inclusive_ancestors() { - if let Some(element) = Root::downcast::<Element>(element) { - if matches_selector_list(&selectors.0, &element, None) - { - return Ok(Some(element)); - } - } - } - Ok(None) - } - } + fn Closest(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> { + let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors) { + Err(_) => return Err(Error::Syntax), + Ok(selectors) => selectors, + }; + + let quirks_mode = document_from_node(self).quirks_mode(); + Ok(dom_apis::element_closest( + DomRoot::from_ref(self), + &selectors, + quirks_mode, + )) } // https://dom.spec.whatwg.org/#dom-element-insertadjacentelement - fn InsertAdjacentElement(&self, where_: DOMString, element: &Element) - -> Fallible<Option<Root<Element>>> { - let where_ = try!(AdjacentPosition::try_from(&*where_)); - let inserted_node = try!(self.insert_adjacent(where_, element.upcast())); - Ok(inserted_node.map(|node| Root::downcast(node).unwrap())) + fn InsertAdjacentElement( + &self, + where_: DOMString, + element: &Element, + ) -> Fallible<Option<DomRoot<Element>>> { + let where_ = where_.parse::<AdjacentPosition>()?; + let inserted_node = self.insert_adjacent(where_, element.upcast())?; + Ok(inserted_node.map(|node| DomRoot::downcast(node).unwrap())) } // https://dom.spec.whatwg.org/#dom-element-insertadjacenttext - fn InsertAdjacentText(&self, where_: DOMString, data: DOMString) - -> ErrorResult { + fn InsertAdjacentText(&self, where_: DOMString, data: DOMString) -> ErrorResult { // Step 1. let text = Text::new(data, &document_from_node(self)); // Step 2. - let where_ = try!(AdjacentPosition::try_from(&*where_)); + let where_ = where_.parse::<AdjacentPosition>()?; self.insert_adjacent(where_, text.upcast()).map(|_| ()) } // https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml - fn InsertAdjacentHTML(&self, position: DOMString, text: DOMString) - -> ErrorResult { + fn InsertAdjacentHTML(&self, position: DOMString, text: DOMString) -> ErrorResult { // Step 1. - let position = try!(AdjacentPosition::try_from(&*position)); + let position = position.parse::<AdjacentPosition>()?; let context = match position { AdjacentPosition::BeforeBegin | AdjacentPosition::AfterEnd => { match self.upcast::<Node>().GetParentNode() { Some(ref node) if node.is::<Document>() => { return Err(Error::NoModificationAllowed) - } + }, None => return Err(Error::NoModificationAllowed), Some(node) => node, } - } + }, AdjacentPosition::AfterBegin | AdjacentPosition::BeforeEnd => { - Root::from_ref(self.upcast::<Node>()) - } + DomRoot::from_ref(self.upcast::<Node>()) + }, }; // Step 2. - let context = Element::fragment_parsing_context( - &context.owner_doc(), context.downcast::<Element>()); + let context = + Element::fragment_parsing_context(&context.owner_doc(), context.downcast::<Element>()); // Step 3. - let fragment = try!(context.parse_fragment(text)); + let fragment = context.parse_fragment(text)?; // Step 4. - self.insert_adjacent(position, fragment.upcast()).map(|_| ()) + self.insert_adjacent(position, fragment.upcast()) + .map(|_| ()) } // check-tidy: no specs after this line @@ -2135,7 +2768,7 @@ impl ElementMethods for Element { a.enter_formal_activation_state(); return Ok(()); }, - None => return Err(Error::NotSupported) + None => return Err(Error::NotSupported), } } @@ -2145,21 +2778,39 @@ impl ElementMethods for Element { a.exit_formal_activation_state(); return Ok(()); }, - None => return Err(Error::NotSupported) + None => return Err(Error::NotSupported), } } // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen - #[allow(unrooted_must_root)] fn RequestFullscreen(&self) -> Rc<Promise> { let doc = document_from_node(self); doc.enter_fullscreen(self) } + + // XXX Hidden under dom.shadowdom.enabled pref. Only exposed to be able + // to test partial Shadow DOM support for UA widgets. + // https://dom.spec.whatwg.org/#dom-element-attachshadow + fn AttachShadow(&self) -> Fallible<DomRoot<ShadowRoot>> { + self.attach_shadow(IsUserAgentWidget::No) + } } impl VirtualMethods for Element { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<Node>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<Node>() as &dyn VirtualMethods) + } + + fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool { + // FIXME: This should be more fine-grained, not all elements care about these. + if attr.local_name() == &local_name!("width") || attr.local_name() == &local_name!("height") + { + return true; + } + + self.super_type() + .unwrap() + .attribute_affects_presentational_hints(attr) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -2167,6 +2818,9 @@ impl VirtualMethods for Element { let node = self.upcast::<Node>(); let doc = node.owner_doc(); match attr.local_name() { + &local_name!("tabindex") | &local_name!("draggable") | &local_name!("hidden") => { + self.update_sequentially_focusable_status() + }, &local_name!("style") => { // Modifying the `style` attribute might change style. *self.style_attribute.borrow_mut() = match mutation { @@ -2196,18 +2850,62 @@ impl VirtualMethods for Element { Arc::new(doc.style_shared_lock().wrap(parse_style_attribute( &attr.value(), &doc.base_url(), - win.css_error_reporter()))) + win.css_error_reporter(), + doc.quirks_mode(), + CssRuleType::Style, + ))) }; Some(block) - } - AttributeMutation::Removed => { - None - } + }, + AttributeMutation::Removed => None, }; }, &local_name!("id") => { - *self.id_attribute.borrow_mut() = + *self.id_attribute.borrow_mut() = mutation.new_value(attr).and_then(|value| { + let value = value.as_atom(); + if value != &atom!("") { + Some(value.clone()) + } else { + None + } + }); + let containing_shadow_root = self.upcast::<Node>().containing_shadow_root(); + if node.is_connected() { + let value = attr.value().as_atom().clone(); + match mutation { + AttributeMutation::Set(old_value) => { + if let Some(old_value) = old_value { + let old_value = old_value.as_atom().clone(); + if let Some(ref shadow_root) = containing_shadow_root { + shadow_root.unregister_element_id(self, old_value); + } else { + doc.unregister_element_id(self, old_value); + } + } + if value != atom!("") { + if let Some(ref shadow_root) = containing_shadow_root { + shadow_root.register_element_id(self, value); + } else { + doc.register_element_id(self, value); + } + } + }, + AttributeMutation::Removed => { + if value != atom!("") { + if let Some(ref shadow_root) = containing_shadow_root { + shadow_root.unregister_element_id(self, value); + } else { + doc.unregister_element_id(self, value); + } + } + }, + } + } + }, + &local_name!("name") => { + // Keep the name in rare data for fast access + self.ensure_rare_data().name_attribute = mutation.new_value(attr).and_then(|value| { let value = value.as_atom(); if value != &atom!("") { @@ -2216,31 +2914,32 @@ impl VirtualMethods for Element { None } }); - if node.is_in_doc() { + // Keep the document name_map up to date + // (if we're not in shadow DOM) + if node.is_connected() && node.containing_shadow_root().is_none() { let value = attr.value().as_atom().clone(); match mutation { AttributeMutation::Set(old_value) => { if let Some(old_value) = old_value { let old_value = old_value.as_atom().clone(); - doc.unregister_named_element(self, old_value); + doc.unregister_element_name(self, old_value); } if value != atom!("") { - doc.register_named_element(self, value); + doc.register_element_name(self, value); } }, AttributeMutation::Removed => { if value != atom!("") { - doc.unregister_named_element(self, value); + doc.unregister_element_name(self, value); } - } + }, } } }, _ => { // FIXME(emilio): This is pretty dubious, and should be done in // the relevant super-classes. - if attr.namespace() == &ns!() && - attr.local_name() == &local_name!("src") { + if attr.namespace() == &ns!() && attr.local_name() == &local_name!("src") { node.dirty(NodeDamage::OtherNodeDamage); } }, @@ -2255,28 +2954,55 @@ impl VirtualMethods for Element { fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match name { &local_name!("id") => AttrValue::from_atomic(value.into()), + &local_name!("name") => AttrValue::from_atomic(value.into()), &local_name!("class") => AttrValue::from_serialized_tokenlist(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } if let Some(f) = self.as_maybe_form_control() { f.bind_form_control_to_tree(); } - if !tree_in_doc { + let doc = document_from_node(self); + + if let Some(ref shadow_root) = self.shadow_root() { + doc.register_shadow_root(&shadow_root); + let shadow_root = shadow_root.upcast::<Node>(); + shadow_root.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected); + for node in shadow_root.children() { + node.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected); + node.bind_to_tree(context); + } + } + + if !context.tree_connected { return; } - let doc = document_from_node(self); - if let Some(ref value) = *self.id_attribute.borrow() { - doc.register_named_element(self, value.clone()); + self.update_sequentially_focusable_status(); + + if let Some(ref id) = *self.id_attribute.borrow() { + if let Some(shadow_root) = self.upcast::<Node>().containing_shadow_root() { + shadow_root.register_element_id(self, id.clone()); + } else { + doc.register_element_id(self, id.clone()); + } } + if let Some(ref name) = self.name_attribute() { + if self.upcast::<Node>().containing_shadow_root().is_none() { + doc.register_element_name(self, name.clone()); + } + } + // This is used for layout optimization. doc.increment_dom_count(); } @@ -2288,17 +3014,33 @@ impl VirtualMethods for Element { f.unbind_form_control_from_tree(); } - if !context.tree_in_doc { + if !context.tree_connected { return; } + self.update_sequentially_focusable_status(); + let doc = document_from_node(self); + + if let Some(ref shadow_root) = self.shadow_root() { + doc.unregister_shadow_root(&shadow_root); + let shadow_root = shadow_root.upcast::<Node>(); + shadow_root.set_flag(NodeFlags::IS_CONNECTED, false); + for node in shadow_root.children() { + node.set_flag(NodeFlags::IS_CONNECTED, false); + node.unbind_from_tree(context); + } + } + let fullscreen = doc.GetFullscreenElement(); - if fullscreen.r() == Some(self) { + if fullscreen.as_deref() == Some(self) { doc.exit_fullscreen(); } if let Some(ref value) = *self.id_attribute.borrow() { - doc.unregister_named_element(self, value.clone()); + doc.unregister_element_id(self, value.clone()); + } + if let Some(ref value) = self.name_attribute() { + doc.unregister_element_name(self, value.clone()); } // This is used for layout optimization. doc.decrement_dom_count(); @@ -2310,11 +3052,11 @@ impl VirtualMethods for Element { } let flags = self.selector_flags.get(); - if flags.intersects(HAS_SLOW_SELECTOR) { + if flags.intersects(ElementSelectorFlags::HAS_SLOW_SELECTOR) { // All children of this node need to be restyled when any child changes. self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } else { - if flags.intersects(HAS_SLOW_SELECTOR_LATER_SIBLINGS) { + if flags.intersects(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) { if let Some(next_child) = mutation.next_child() { for child in next_child.inclusively_following_siblings() { if child.is::<Element>() { @@ -2323,7 +3065,7 @@ impl VirtualMethods for Element { } } } - if flags.intersects(HAS_EDGE_CHILD_SELECTOR) { + if flags.intersects(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) { if let Some(child) = mutation.modified_edge_element() { child.dirty(NodeDamage::OtherNodeDamage); } @@ -2340,55 +3082,73 @@ impl VirtualMethods for Element { } } -impl<'a> ::selectors::MatchAttrGeneric for Root<Element> { +impl<'a> SelectorsElement for DomRoot<Element> { type Impl = SelectorImpl; - fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool - where F: Fn(&str) -> bool - { - use ::selectors::Element; - let local_name = { - if self.is_html_element_in_html_document() { - &attr.lower_name - } else { - &attr.name - } - }; - match attr.namespace { - NamespaceConstraint::Specific(ref ns) => { - self.get_attribute(&ns.url, local_name) - .map_or(false, |attr| { - test(&attr.value()) - }) - }, - NamespaceConstraint::Any => { - self.attrs.borrow().iter().any(|attr| { - attr.local_name() == local_name && test(&attr.value()) - }) - } - } + #[allow(unsafe_code)] + fn opaque(&self) -> ::selectors::OpaqueElement { + ::selectors::OpaqueElement::new(unsafe { &*self.reflector().get_jsobject().get() }) } -} -impl<'a> ::selectors::Element for Root<Element> { - fn parent_element(&self) -> Option<Root<Element>> { + fn parent_element(&self) -> Option<DomRoot<Element>> { self.upcast::<Node>().GetParentElement() } - fn first_child_element(&self) -> Option<Root<Element>> { - self.node.child_elements().next() + fn parent_node_is_shadow_root(&self) -> bool { + match self.upcast::<Node>().GetParentNode() { + None => false, + Some(node) => node.is::<ShadowRoot>(), + } + } + + fn containing_shadow_host(&self) -> Option<Self> { + if let Some(shadow_root) = self.upcast::<Node>().containing_shadow_root() { + Some(shadow_root.Host()) + } else { + None + } + } + + fn is_pseudo_element(&self) -> bool { + false } - fn last_child_element(&self) -> Option<Root<Element>> { - self.node.rev_children().filter_map(Root::downcast).next() + fn match_pseudo_element( + &self, + _pseudo: &PseudoElement, + _context: &mut MatchingContext<Self::Impl>, + ) -> bool { + false } - fn prev_sibling_element(&self) -> Option<Root<Element>> { - self.node.preceding_siblings().filter_map(Root::downcast).next() + fn prev_sibling_element(&self) -> Option<DomRoot<Element>> { + self.node + .preceding_siblings() + .filter_map(DomRoot::downcast) + .next() } - fn next_sibling_element(&self) -> Option<Root<Element>> { - self.node.following_siblings().filter_map(Root::downcast).next() + fn next_sibling_element(&self) -> Option<DomRoot<Element>> { + self.node + .following_siblings() + .filter_map(DomRoot::downcast) + .next() + } + + fn attr_matches( + &self, + ns: &NamespaceConstraint<&style::Namespace>, + local_name: &style::LocalName, + operation: &AttrSelectorOperation<&AtomString>, + ) -> bool { + match *ns { + NamespaceConstraint::Specific(ref ns) => self + .get_attribute(ns, local_name) + .map_or(false, |attr| attr.value().eval_selector(operation)), + NamespaceConstraint::Any => self.attrs.borrow().iter().any(|attr| { + *attr.local_name() == **local_name && attr.value().eval_selector(operation) + }), + } } fn is_root(&self) -> bool { @@ -2399,115 +3159,163 @@ impl<'a> ::selectors::Element for Root<Element> { } fn is_empty(&self) -> bool { - self.node.children().all(|node| !node.is::<Element>() && match node.downcast::<Text>() { - None => true, - Some(text) => text.upcast::<CharacterData>().data().is_empty() + self.node.children().all(|node| { + !node.is::<Element>() && + match node.downcast::<Text>() { + None => true, + Some(text) => text.upcast::<CharacterData>().data().is_empty(), + } }) } - fn get_local_name(&self) -> &LocalName { - self.local_name() + fn has_local_name(&self, local_name: &LocalName) -> bool { + Element::local_name(self) == local_name + } + + fn has_namespace(&self, ns: &Namespace) -> bool { + Element::namespace(self) == ns } - fn get_namespace(&self) -> &Namespace { - self.namespace() + fn is_same_type(&self, other: &Self) -> bool { + Element::local_name(self) == Element::local_name(other) && + Element::namespace(self) == Element::namespace(other) } - fn match_non_ts_pseudo_class<F>(&self, - pseudo_class: &NonTSPseudoClass, - _: &mut StyleRelations, - _: &mut F) - -> bool - where F: FnMut(&Self, ElementSelectorFlags), + fn match_non_ts_pseudo_class<F>( + &self, + pseudo_class: &NonTSPseudoClass, + _: &mut MatchingContext<Self::Impl>, + _: &mut F, + ) -> bool + where + F: FnMut(&Self, ElementSelectorFlags), { match *pseudo_class { // https://github.com/servo/servo/issues/8718 - NonTSPseudoClass::Link | - NonTSPseudoClass::AnyLink => self.is_link(), + NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(), NonTSPseudoClass::Visited => false, - NonTSPseudoClass::ServoNonZeroBorder => { - match self.downcast::<HTMLTableElement>() { - None => false, - Some(this) => { - match this.get_border() { - None | Some(0) => false, - Some(_) => true, - } - } - } + NonTSPseudoClass::ServoNonZeroBorder => match self.downcast::<HTMLTableElement>() { + None => false, + Some(this) => match this.get_border() { + None | Some(0) => false, + Some(_) => true, + }, }, - // FIXME(#15746): This is wrong, we need to instead use extended filtering as per RFC4647 - // https://tools.ietf.org/html/rfc4647#section-3.3.2 + // FIXME(heycam): This is wrong, since extended_filtering accepts + // a string containing commas (separating each language tag in + // a list) but the pseudo-class instead should be parsing and + // storing separate <ident> or <string>s for each language tag. NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.get_lang(), &*lang), - NonTSPseudoClass::ReadOnly => - !Element::state(self).contains(pseudo_class.state_flag()), + NonTSPseudoClass::ReadOnly => !Element::state(self).contains(pseudo_class.state_flag()), NonTSPseudoClass::Active | NonTSPseudoClass::Focus | NonTSPseudoClass::Fullscreen | NonTSPseudoClass::Hover | + NonTSPseudoClass::Defined | NonTSPseudoClass::Enabled | NonTSPseudoClass::Disabled | NonTSPseudoClass::Checked | NonTSPseudoClass::Indeterminate | NonTSPseudoClass::ReadWrite | NonTSPseudoClass::PlaceholderShown | - NonTSPseudoClass::Target => - Element::state(self).contains(pseudo_class.state_flag()), + NonTSPseudoClass::Target => Element::state(self).contains(pseudo_class.state_flag()), } } - fn get_id(&self) -> Option<Atom> { - self.id_attribute.borrow().clone() + fn is_link(&self) -> bool { + // FIXME: This is HTML only. + let node = self.upcast::<Node>(); + match node.type_id() { + // https://html.spec.whatwg.org/multipage/#selector-link + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLAnchorElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) | + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => { + self.has_attribute(&local_name!("href")) + }, + _ => false, + } } - fn has_class(&self, name: &Atom) -> bool { - Element::has_class(&**self, name) + fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { + self.id_attribute + .borrow() + .as_ref() + .map_or(false, |atom| case_sensitivity.eq_atom(&*id, atom)) } - fn each_class<F>(&self, mut callback: F) - where F: FnMut(&Atom) - { - if let Some(ref attr) = self.get_attribute(&ns!(), &local_name!("class")) { - let tokens = attr.value(); - let tokens = tokens.as_tokens(); - for token in tokens { - callback(token); - } - } + fn is_part(&self, _name: &AtomIdent) -> bool { + false + } + + fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> { + None + } + + fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { + Element::has_class(&**self, &name, case_sensitivity) } fn is_html_element_in_html_document(&self) -> bool { self.html_element_in_html_document() } -} + fn is_html_slot_element(&self) -> bool { + self.is_html_element() && self.local_name() == &local_name!("slot") + } +} impl Element { - pub fn as_maybe_activatable(&self) -> Option<&Activatable> { + fn client_rect(&self) -> Rect<i32> { + if let Some(rect) = self + .rare_data() + .as_ref() + .and_then(|data| data.client_rect.as_ref()) + .and_then(|rect| rect.get().ok()) + { + return rect; + } + let rect = self.upcast::<Node>().client_rect(); + self.ensure_rare_data().client_rect = Some(window_from_node(self).cache_layout_value(rect)); + rect + } + + pub fn as_maybe_activatable(&self) -> Option<&dyn Activatable> { let element = match self.upcast::<Node>().type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement, + )) => { let element = self.downcast::<HTMLInputElement>().unwrap(); - Some(element as &Activatable) + Some(element as &dyn Activatable) }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLButtonElement, + )) => { let element = self.downcast::<HTMLButtonElement>().unwrap(); - Some(element as &Activatable) + Some(element as &dyn Activatable) }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLAnchorElement, + )) => { let element = self.downcast::<HTMLAnchorElement>().unwrap(); - Some(element as &Activatable) + Some(element as &dyn Activatable) }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLLabelElement, + )) => { let element = self.downcast::<HTMLLabelElement>().unwrap(); - Some(element as &Activatable) + Some(element as &dyn Activatable) }, - _ => { - None - } + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLElement)) => { + let element = self.downcast::<HTMLElement>().unwrap(); + Some(element as &dyn Activatable) + }, + _ => None, }; element.and_then(|elem| { if elem.is_instance_activatable() { @@ -2518,145 +3326,110 @@ impl Element { }) } - pub fn as_stylesheet_owner(&self) -> Option<&StylesheetOwner> { + pub fn as_stylesheet_owner(&self) -> Option<&dyn StylesheetOwner> { if let Some(s) = self.downcast::<HTMLStyleElement>() { - return Some(s as &StylesheetOwner) + return Some(s as &dyn StylesheetOwner); } if let Some(l) = self.downcast::<HTMLLinkElement>() { - return Some(l as &StylesheetOwner) + return Some(l as &dyn StylesheetOwner); } None } // https://html.spec.whatwg.org/multipage/#category-submit - pub fn as_maybe_validatable(&self) -> Option<&Validatable> { + pub fn as_maybe_validatable(&self) -> Option<&dyn Validatable> { let element = match self.upcast::<Node>().type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement, + )) => { let element = self.downcast::<HTMLInputElement>().unwrap(); - Some(element as &Validatable) + Some(element as &dyn Validatable) }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLButtonElement, + )) => { let element = self.downcast::<HTMLButtonElement>().unwrap(); - Some(element as &Validatable) + Some(element as &dyn Validatable) }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLObjectElement, + )) => { let element = self.downcast::<HTMLObjectElement>().unwrap(); - Some(element as &Validatable) + Some(element as &dyn Validatable) }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLSelectElement, + )) => { let element = self.downcast::<HTMLSelectElement>().unwrap(); - Some(element as &Validatable) + Some(element as &dyn Validatable) }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement, + )) => { let element = self.downcast::<HTMLTextAreaElement>().unwrap(); - Some(element as &Validatable) + Some(element as &dyn Validatable) }, - _ => { - None - } + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLFieldSetElement, + )) => { + let element = self.downcast::<HTMLFieldSetElement>().unwrap(); + Some(element as &dyn Validatable) + }, + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLOutputElement, + )) => { + let element = self.downcast::<HTMLOutputElement>().unwrap(); + Some(element as &dyn Validatable) + }, + _ => None, }; element } pub fn click_in_progress(&self) -> bool { - self.upcast::<Node>().get_flag(CLICK_IN_PROGRESS) + self.upcast::<Node>().get_flag(NodeFlags::CLICK_IN_PROGRESS) } pub fn set_click_in_progress(&self, click: bool) { - self.upcast::<Node>().set_flag(CLICK_IN_PROGRESS, click) + self.upcast::<Node>() + .set_flag(NodeFlags::CLICK_IN_PROGRESS, click) } // https://html.spec.whatwg.org/multipage/#nearest-activatable-element - pub fn nearest_activable_element(&self) -> Option<Root<Element>> { + pub fn nearest_activable_element(&self) -> Option<DomRoot<Element>> { match self.as_maybe_activatable() { - Some(el) => Some(Root::from_ref(el.as_element())), + Some(el) => Some(DomRoot::from_ref(el.as_element())), None => { let node = self.upcast::<Node>(); for node in node.ancestors() { if let Some(node) = node.downcast::<Element>() { if node.as_maybe_activatable().is_some() { - return Some(Root::from_ref(node)); + return Some(DomRoot::from_ref(node)); } } } None - } - } - } - - fn is_link(&self) -> bool { - // FIXME: This is HTML only. - let node = self.upcast::<Node>(); - match node.type_id() { - // https://html.spec.whatwg.org/multipage/#selector-link - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => { - self.has_attribute(&local_name!("href")) - }, - _ => false, - } - } - - /// Please call this method *only* for real click events - /// - /// https://html.spec.whatwg.org/multipage/#run-authentic-click-activation-steps - /// - /// Use an element's synthetic click activation (or handle_event) for any script-triggered clicks. - /// If the spec says otherwise, check with Manishearth first - pub fn authentic_click_activation(&self, event: &Event) { - // Not explicitly part of the spec, however this helps enforce the invariants - // required to save state between pre-activation and post-activation - // since we cannot nest authentic clicks (unlike synthetic click activation, where - // the script can generate more click events from the handler) - assert!(!self.click_in_progress()); - - let target = self.upcast(); - // Step 2 (requires canvas support) - // Step 3 - self.set_click_in_progress(true); - // Step 4 - let e = self.nearest_activable_element(); - match e { - Some(ref el) => match el.as_maybe_activatable() { - Some(elem) => { - // Step 5-6 - elem.pre_click_activation(); - event.fire(target); - if !event.DefaultPrevented() { - // post click activation - elem.activation_behavior(event, target); - } else { - elem.canceled_activation(); - } - } - // Step 6 - None => { - event.fire(target); - } }, - // Step 6 - None => { - event.fire(target); - } } - // Step 7 - self.set_click_in_progress(false); } // https://html.spec.whatwg.org/multipage/#language pub fn get_lang(&self) -> String { - self.upcast::<Node>().inclusive_ancestors().filter_map(|node| { - node.downcast::<Element>().and_then(|el| { - el.get_attribute(&ns!(xml), &local_name!("lang")).or_else(|| { - el.get_attribute(&ns!(), &local_name!("lang")) - }).map(|attr| String::from(attr.Value())) + self.upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(|node| { + node.downcast::<Element>().and_then(|el| { + el.get_attribute(&ns!(xml), &local_name!("lang")) + .or_else(|| el.get_attribute(&ns!(), &local_name!("lang"))) + .map(|attr| String::from(attr.Value())) + }) + // TODO: Check meta tags for a pragma-set default language + // TODO: Check HTTP Content-Language header }) - // TODO: Check meta tags for a pragma-set default language - // TODO: Check HTTP Content-Language header - }).next().unwrap_or(String::new()) + .next() + .unwrap_or(String::new()) } pub fn state(&self) -> ElementState { @@ -2678,13 +3451,9 @@ impl Element { self.state.set(state); } - pub fn active_state(&self) -> bool { - self.state.get().contains(IN_ACTIVE_STATE) - } - - /// https://html.spec.whatwg.org/multipage/#concept-selector-active + /// <https://html.spec.whatwg.org/multipage/#concept-selector-active> pub fn set_active_state(&self, value: bool) { - self.set_state(IN_ACTIVE_STATE, value); + self.set_state(ElementState::IN_ACTIVE_STATE, value); if let Some(parent) = self.upcast::<Node>().GetParentElement() { parent.set_active_state(value); @@ -2692,78 +3461,82 @@ impl Element { } pub fn focus_state(&self) -> bool { - self.state.get().contains(IN_FOCUS_STATE) + self.state.get().contains(ElementState::IN_FOCUS_STATE) } pub fn set_focus_state(&self, value: bool) { - self.set_state(IN_FOCUS_STATE, value); + self.set_state(ElementState::IN_FOCUS_STATE, value); self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } pub fn hover_state(&self) -> bool { - self.state.get().contains(IN_HOVER_STATE) + self.state.get().contains(ElementState::IN_HOVER_STATE) } pub fn set_hover_state(&self, value: bool) { - self.set_state(IN_HOVER_STATE, value) + self.set_state(ElementState::IN_HOVER_STATE, value) } pub fn enabled_state(&self) -> bool { - self.state.get().contains(IN_ENABLED_STATE) + self.state.get().contains(ElementState::IN_ENABLED_STATE) } pub fn set_enabled_state(&self, value: bool) { - self.set_state(IN_ENABLED_STATE, value) + self.set_state(ElementState::IN_ENABLED_STATE, value) } pub fn disabled_state(&self) -> bool { - self.state.get().contains(IN_DISABLED_STATE) + self.state.get().contains(ElementState::IN_DISABLED_STATE) } pub fn set_disabled_state(&self, value: bool) { - self.set_state(IN_DISABLED_STATE, value) + self.set_state(ElementState::IN_DISABLED_STATE, value) } pub fn read_write_state(&self) -> bool { - self.state.get().contains(IN_READ_WRITE_STATE) + self.state.get().contains(ElementState::IN_READ_WRITE_STATE) } pub fn set_read_write_state(&self, value: bool) { - self.set_state(IN_READ_WRITE_STATE, value) + self.set_state(ElementState::IN_READ_WRITE_STATE, value) } pub fn placeholder_shown_state(&self) -> bool { - self.state.get().contains(IN_PLACEHOLDER_SHOWN_STATE) + self.state + .get() + .contains(ElementState::IN_PLACEHOLDER_SHOWN_STATE) } pub fn set_placeholder_shown_state(&self, value: bool) { if self.placeholder_shown_state() != value { - self.set_state(IN_PLACEHOLDER_SHOWN_STATE, value); + self.set_state(ElementState::IN_PLACEHOLDER_SHOWN_STATE, value); self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } } - pub fn target_state(&self) -> bool { - self.state.get().contains(IN_TARGET_STATE) - } - pub fn set_target_state(&self, value: bool) { - self.set_state(IN_TARGET_STATE, value) - } - - pub fn fullscreen_state(&self) -> bool { - self.state.get().contains(IN_FULLSCREEN_STATE) + self.set_state(ElementState::IN_TARGET_STATE, value) } pub fn set_fullscreen_state(&self, value: bool) { - self.set_state(IN_FULLSCREEN_STATE, value) + self.set_state(ElementState::IN_FULLSCREEN_STATE, value) } - /// https://dom.spec.whatwg.org/#connected + /// <https://dom.spec.whatwg.org/#connected> pub fn is_connected(&self) -> bool { - let node = self.upcast::<Node>(); - let root = node.GetRootNode(); - root.is::<Document>() + self.upcast::<Node>().is_connected() + } + + // https://html.spec.whatwg.org/multipage/#cannot-navigate + pub fn cannot_navigate(&self) -> bool { + let document = document_from_node(self); + + // Step 1. + !document.is_fully_active() || + ( + // Step 2. + !self.is::<HTMLAnchorElement>() && !self.is_connected() + ) } } @@ -2804,7 +3577,8 @@ impl Element { let node = self.upcast::<Node>(); if let Some(ref parent) = node.GetParentNode() { if parent.is::<HTMLOptGroupElement>() && - parent.downcast::<Element>().unwrap().disabled_state() { + parent.downcast::<Element>().unwrap().disabled_state() + { self.set_disabled_state(true); self.set_enabled_state(false); } @@ -2821,11 +3595,11 @@ impl Element { #[derive(Clone, Copy)] pub enum AttributeMutation<'a> { /// The attribute is set, keep track of old value. - /// https://dom.spec.whatwg.org/#attribute-is-set + /// <https://dom.spec.whatwg.org/#attribute-is-set> Set(Option<&'a AttrValue>), /// The attribute is removed. - /// https://dom.spec.whatwg.org/#attribute-is-removed + /// <https://dom.spec.whatwg.org/#attribute-is-removed> Removed, } @@ -2848,20 +3622,23 @@ impl<'a> AttributeMutation<'a> { /// A holder for an element's "tag name", which will be lazily /// resolved and cached. Should be reset when the document /// owner changes. -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] struct TagName { - ptr: DOMRefCell<Option<LocalName>>, + ptr: DomRefCell<Option<LocalName>>, } impl TagName { fn new() -> TagName { - TagName { ptr: DOMRefCell::new(None) } + TagName { + ptr: DomRefCell::new(None), + } } /// Retrieve a copy of the current inner value. If it is `None`, it is /// initialized with the result of `cb` first. fn or_init<F>(&self, cb: F) -> LocalName - where F: FnOnce() -> LocalName + where + F: FnOnce() -> LocalName, { match &mut *self.ptr.borrow_mut() { &mut Some(ref name) => name.clone(), @@ -2869,7 +3646,7 @@ impl TagName { let name = cb(); *ptr = Some(name.clone()); name - } + }, } } @@ -2887,53 +3664,50 @@ pub struct ElementPerformFullscreenEnter { } impl ElementPerformFullscreenEnter { - pub fn new(element: Trusted<Element>, promise: TrustedPromise, error: bool) -> Box<ElementPerformFullscreenEnter> { - box ElementPerformFullscreenEnter { + pub fn new( + element: Trusted<Element>, + promise: TrustedPromise, + error: bool, + ) -> Box<ElementPerformFullscreenEnter> { + Box::new(ElementPerformFullscreenEnter { element: element, promise: promise, error: error, - } + }) } } -impl Runnable for ElementPerformFullscreenEnter { - fn name(&self) -> &'static str { "ElementPerformFullscreenEnter" } - +impl TaskOnce for ElementPerformFullscreenEnter { #[allow(unrooted_must_root)] - fn handler(self: Box<ElementPerformFullscreenEnter>) { + fn run_once(self) { let element = self.element.root(); - let document = document_from_node(element.r()); + let promise = self.promise.root(); + let document = document_from_node(&*element); // Step 7.1 if self.error || !element.fullscreen_element_ready_check() { - // JSAutoCompartment needs to be manually made. - // Otherwise, Servo will crash. - let promise = self.promise.root(); - let promise_cx = promise.global().get_cx(); - let _ac = JSAutoCompartment::new(promise_cx, promise.reflector().get_jsobject().get()); - document.upcast::<EventTarget>().fire_event(atom!("fullscreenerror")); - promise.reject_error(promise.global().get_cx(), Error::Type(String::from("fullscreen is not connected"))); - return + document + .upcast::<EventTarget>() + .fire_event(atom!("fullscreenerror")); + promise.reject_error(Error::Type(String::from("fullscreen is not connected"))); + return; } // TODO Step 7.2-4 // Step 7.5 element.set_fullscreen_state(true); document.set_fullscreen_element(Some(&element)); - document.window().reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::ElementStateChanged); + document + .window() + .reflow(ReflowGoal::Full, ReflowReason::ElementStateChanged); // Step 7.6 - document.upcast::<EventTarget>().fire_event(atom!("fullscreenchange")); + document + .upcast::<EventTarget>() + .fire_event(atom!("fullscreenchange")); // Step 7.7 - // JSAutoCompartment needs to be manually made. - // Otherwise, Servo will crash. - let promise = self.promise.root(); - let promise_cx = promise.global().get_cx(); - let _ac = JSAutoCompartment::new(promise_cx, promise.reflector().get_jsobject().get()); - promise.resolve(promise.global().get_cx(), HandleValue::undefined()); + promise.resolve_native(&()); } } @@ -2943,41 +3717,39 @@ pub struct ElementPerformFullscreenExit { } impl ElementPerformFullscreenExit { - pub fn new(element: Trusted<Element>, promise: TrustedPromise) -> Box<ElementPerformFullscreenExit> { - box ElementPerformFullscreenExit { + pub fn new( + element: Trusted<Element>, + promise: TrustedPromise, + ) -> Box<ElementPerformFullscreenExit> { + Box::new(ElementPerformFullscreenExit { element: element, promise: promise, - } + }) } } -impl Runnable for ElementPerformFullscreenExit { - fn name(&self) -> &'static str { "ElementPerformFullscreenExit" } - +impl TaskOnce for ElementPerformFullscreenExit { #[allow(unrooted_must_root)] - fn handler(self: Box<ElementPerformFullscreenExit>) { + fn run_once(self) { let element = self.element.root(); - let document = document_from_node(element.r()); + let document = document_from_node(&*element); // TODO Step 9.1-5 // Step 9.6 element.set_fullscreen_state(false); - document.window().reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::ElementStateChanged); + document + .window() + .reflow(ReflowGoal::Full, ReflowReason::ElementStateChanged); document.set_fullscreen_element(None); // Step 9.8 - document.upcast::<EventTarget>().fire_event(atom!("fullscreenchange")); + document + .upcast::<EventTarget>() + .fire_event(atom!("fullscreenchange")); // Step 9.10 - let promise = self.promise.root(); - // JSAutoCompartment needs to be manually made. - // Otherwise, Servo will crash. - let promise_cx = promise.global().get_cx(); - let _ac = JSAutoCompartment::new(promise_cx, promise.reflector().get_jsobject().get()); - promise.resolve(promise.global().get_cx(), HandleValue::undefined()); + self.promise.root().resolve_native(&()); } } @@ -2999,16 +3771,42 @@ pub fn set_cross_origin_attribute(element: &Element, value: Option<DOMString>) { Some(val) => element.set_string_attribute(&local_name!("crossorigin"), val), None => { element.remove_attribute(&ns!(), &local_name!("crossorigin")); - } + }, } } -pub fn cors_setting_for_element(element: &Element) -> Option<CorsSettings> { - reflect_cross_origin_attribute(element).map_or(None, |attr| { - match &*attr { - "anonymous" => Some(CorsSettings::Anonymous), - "use-credentials" => Some(CorsSettings::UseCredentials), - _ => unreachable!() - } +pub fn reflect_referrer_policy_attribute(element: &Element) -> DOMString { + let attr = + element.get_attribute_by_name(DOMString::from_string(String::from("referrerpolicy"))); + + if let Some(mut val) = attr.map(|v| v.Value()) { + val.make_ascii_lowercase(); + if val == "no-referrer" || + val == "no-referrer-when-downgrade" || + val == "same-origin" || + val == "origin" || + val == "strict-origin" || + val == "origin-when-cross-origin" || + val == "strict-origin-when-cross-origin" || + val == "unsafe-url" + { + return val; + } + } + return DOMString::new(); +} + +pub(crate) fn referrer_policy_for_element(element: &Element) -> Option<ReferrerPolicy> { + element + .get_attribute_by_name(DOMString::from_string(String::from("referrerpolicy"))) + .and_then(|attribute: DomRoot<Attr>| determine_policy_for_token(&attribute.Value())) + .or_else(|| document_from_node(element).get_referrer_policy()) +} + +pub(crate) fn cors_setting_for_element(element: &Element) -> Option<CorsSettings> { + reflect_cross_origin_attribute(element).map_or(None, |attr| match &*attr { + "anonymous" => Some(CorsSettings::Anonymous), + "use-credentials" => Some(CorsSettings::UseCredentials), + _ => unreachable!(), }) } diff --git a/components/script/dom/errorevent.rs b/components/script/dom/errorevent.rs index 170a979d996..129f96dc910 100644 --- a/components/script/dom/errorevent.rs +++ b/components/script/dom/errorevent.rs @@ -1,33 +1,35 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::ErrorEventBinding; -use dom::bindings::codegen::Bindings::ErrorEventBinding::ErrorEventMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::bindings::trace::RootedTraceableBox; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::ErrorEventBinding; +use crate::dom::bindings::codegen::Bindings::ErrorEventBinding::ErrorEventMethods; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{Heap, HandleValue, JSContext}; +use js::jsapi::Heap; use js::jsval::JSVal; +use js::rust::HandleValue; use servo_atoms::Atom; use std::cell::Cell; #[dom_struct] pub struct ErrorEvent { event: Event, - message: DOMRefCell<DOMString>, - filename: DOMRefCell<DOMString>, + message: DomRefCell<DOMString>, + filename: DomRefCell<DOMString>, lineno: Cell<u32>, colno: Cell<u32>, - #[ignore_heap_size_of = "Defined in rust-mozjs"] + #[ignore_malloc_size_of = "Defined in rust-mozjs"] error: Heap<JSVal>, } @@ -35,34 +37,33 @@ impl ErrorEvent { fn new_inherited() -> ErrorEvent { ErrorEvent { event: Event::new_inherited(), - message: DOMRefCell::new(DOMString::new()), - filename: DOMRefCell::new(DOMString::new()), + message: DomRefCell::new(DOMString::new()), + filename: DomRefCell::new(DOMString::new()), lineno: Cell::new(0), colno: Cell::new(0), - error: Heap::default() + error: Heap::default(), } } - pub fn new_uninitialized(global: &GlobalScope) -> Root<ErrorEvent> { - reflect_dom_object(box ErrorEvent::new_inherited(), - global, - ErrorEventBinding::Wrap) + pub fn new_uninitialized(global: &GlobalScope) -> DomRoot<ErrorEvent> { + reflect_dom_object(Box::new(ErrorEvent::new_inherited()), global) } - pub fn new(global: &GlobalScope, - type_: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable, - message: DOMString, - filename: DOMString, - lineno: u32, - colno: u32, - error: HandleValue) -> Root<ErrorEvent> { + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + message: DOMString, + filename: DOMString, + lineno: u32, + colno: u32, + error: HandleValue, + ) -> DomRoot<ErrorEvent> { let ev = ErrorEvent::new_uninitialized(global); { let event = ev.upcast::<Event>(); - event.init_event(type_, bool::from(bubbles), - bool::from(cancelable)); + event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); *ev.message.borrow_mut() = message; *ev.filename.borrow_mut() = filename; ev.lineno.set(lineno); @@ -72,10 +73,12 @@ impl ErrorEvent { ev } - pub fn Constructor(global: &GlobalScope, - type_: DOMString, - init: RootedTraceableBox<ErrorEventBinding::ErrorEventInit>) - -> Fallible<Root<ErrorEvent>>{ + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + type_: DOMString, + init: RootedTraceableBox<ErrorEventBinding::ErrorEventInit>, + ) -> Fallible<DomRoot<ErrorEvent>> { let msg = match init.message.as_ref() { Some(message) => message.clone(), None => DOMString::new(), @@ -95,18 +98,18 @@ impl ErrorEvent { let cancelable = EventCancelable::from(init.parent.cancelable); let event = ErrorEvent::new( - global, - Atom::from(type_), - bubbles, - cancelable, - msg, - file_name, - line_num, - col_num, - init.error.handle()); + global, + Atom::from(type_), + bubbles, + cancelable, + msg, + file_name, + line_num, + col_num, + init.error.handle(), + ); Ok(event) } - } impl ErrorEventMethods for ErrorEvent { @@ -130,9 +133,8 @@ impl ErrorEventMethods for ErrorEvent { self.filename.borrow().clone() } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-errorevent-error - unsafe fn Error(&self, _cx: *mut JSContext) -> JSVal { + fn Error(&self, _cx: JSContext) -> JSVal { self.error.get() } diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index d6ba1d85109..8093bd852a9 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -1,37 +1,44 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EventBinding; +use crate::dom::bindings::codegen::Bindings::EventBinding::{EventConstants, EventMethods}; +use crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; +use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceBinding::PerformanceMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::eventtarget::{CompiledEventListener, EventTarget, ListenerPhase}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlinputelement::InputActivationState; +use crate::dom::mouseevent::MouseEvent; +use crate::dom::node::{Node, ShadowIncluding}; +use crate::dom::performance::reduce_timing_resolution; +use crate::dom::virtualmethods::vtable_for; +use crate::dom::window::Window; +use crate::task::TaskOnce; use devtools_traits::{TimelineMarker, TimelineMarkerType}; -use dom::bindings::callback::ExceptionHandling; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::EventBinding; -use dom::bindings::codegen::Bindings::EventBinding::{EventConstants, EventMethods}; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::eventtarget::{CompiledEventListener, EventTarget, ListenerPhase}; -use dom::globalscope::GlobalScope; -use dom::node::Node; -use dom::virtualmethods::vtable_for; -use dom::window::Window; use dom_struct::dom_struct; -use script_thread::Runnable; +use metrics::ToMs; use servo_atoms::Atom; use std::cell::Cell; use std::default::Default; -use time; #[dom_struct] pub struct Event { reflector_: Reflector, - current_target: MutNullableJS<EventTarget>, - target: MutNullableJS<EventTarget>, - type_: DOMRefCell<Atom>, + current_target: MutNullableDom<EventTarget>, + target: MutNullableDom<EventTarget>, + type_: DomRefCell<Atom>, phase: Cell<EventPhase>, canceled: Cell<EventDefault>, stop_propagation: Cell<bool>, @@ -41,7 +48,7 @@ pub struct Event { trusted: Cell<bool>, dispatching: Cell<bool>, initialized: Cell<bool>, - timestamp: u64, + precise_time_ns: u64, } impl Event { @@ -50,7 +57,7 @@ impl Event { reflector_: Reflector::new(), current_target: Default::default(), target: Default::default(), - type_: DOMRefCell::new(atom!("")), + type_: DomRefCell::new(atom!("")), phase: Cell::new(EventPhase::None), canceled: Cell::new(EventDefault::Allowed), stop_propagation: Cell::new(false), @@ -60,28 +67,31 @@ impl Event { trusted: Cell::new(false), dispatching: Cell::new(false), initialized: Cell::new(false), - timestamp: time::get_time().sec as u64, + precise_time_ns: time::precise_time_ns(), } } - pub fn new_uninitialized(global: &GlobalScope) -> Root<Event> { - reflect_dom_object(box Event::new_inherited(), - global, - EventBinding::Wrap) + pub fn new_uninitialized(global: &GlobalScope) -> DomRoot<Event> { + reflect_dom_object(Box::new(Event::new_inherited()), global) } - pub fn new(global: &GlobalScope, - type_: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable) -> Root<Event> { + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + ) -> DomRoot<Event> { let event = Event::new_uninitialized(global); event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); event } - pub fn Constructor(global: &GlobalScope, - type_: DOMString, - init: &EventBinding::EventInit) -> Fallible<Root<Event>> { + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + type_: DOMString, + init: &EventBinding::EventInit, + ) -> Fallible<DomRoot<Event>> { let bubbles = EventBubbles::from(init.bubbles); let cancelable = EventCancelable::from(init.cancelable); Ok(Event::new(global, Atom::from(type_), bubbles, cancelable)) @@ -103,73 +113,245 @@ impl Event { self.cancelable.set(cancelable); } - // https://dom.spec.whatwg.org/#concept-event-dispatch - pub fn dispatch(&self, - target: &EventTarget, - target_override: Option<&EventTarget>) - -> EventStatus { - assert!(!self.dispatching()); - assert!(self.initialized()); - assert_eq!(self.phase.get(), EventPhase::None); - assert!(self.GetCurrentTarget().is_none()); + // Determine if there are any listeners for a given target and type. + // See https://github.com/whatwg/dom/issues/453 + pub fn has_listeners_for(&self, target: &EventTarget, type_: &Atom) -> bool { + // TODO: take 'removed' into account? Not implemented in Servo yet. + // https://dom.spec.whatwg.org/#event-listener-removed + let mut event_path = self.construct_event_path(&target); + event_path.push(DomRoot::from_ref(target)); + event_path + .iter() + .any(|target| target.has_listeners_for(type_)) + } + + // https://dom.spec.whatwg.org/#event-path + // TODO: shadow roots put special flags in the path, + // and it will stop just being a list of bare EventTargets + fn construct_event_path(&self, target: &EventTarget) -> Vec<DomRoot<EventTarget>> { + let mut event_path = vec![]; + if let Some(target_node) = target.downcast::<Node>() { + // ShadowIncluding::Yes might be closer to right than ::No, + // but still wrong since things about the path change when crossing + // shadow attachments; getting it right needs to change + // more than just that. + for ancestor in target_node.inclusive_ancestors(ShadowIncluding::No) { + event_path.push(DomRoot::from_ref(ancestor.upcast::<EventTarget>())); + } + // Most event-target-to-parent relationships are node parent + // relationships, but the document-to-global one is not, + // so that's handled separately here. + // (an EventTarget.get_parent_event_target could save + // some redundancy, especially when shadow DOM relationships + // also need to be respected) + let top_most_ancestor_or_target = event_path + .last() + .cloned() + .unwrap_or(DomRoot::from_ref(target)); + if let Some(document) = DomRoot::downcast::<Document>(top_most_ancestor_or_target) { + if self.type_() != atom!("load") && document.browsing_context().is_some() { + event_path.push(DomRoot::from_ref(document.window().upcast())); + } + } + } else { + // a non-node EventTarget, likely a global. + // No parent to propagate up to, but we still + // need it on the path. + event_path.push(DomRoot::from_ref(target)); + } + event_path + } + // https://dom.spec.whatwg.org/#concept-event-dispatch + pub fn dispatch( + &self, + target: &EventTarget, + legacy_target_override: bool, + // TODO legacy_did_output_listeners_throw_flag for indexeddb + ) -> EventStatus { // Step 1. self.dispatching.set(true); // Step 2. - self.target.set(Some(target_override.unwrap_or(target))); + let target_override_document; // upcasted EventTarget's lifetime depends on this + let target_override = if legacy_target_override { + target_override_document = target + .downcast::<Window>() + .expect("legacy_target_override must be true only when target is a Window") + .Document(); + target_override_document.upcast::<EventTarget>() + } else { + target + }; + + // Step 3 - since step 5 always happens, we can wait until 5.5 + + // Step 4 TODO: "retargeting" concept depends on shadow DOM + + // Step 5, outer if-statement, is always true until step 4 is implemented + // Steps 5.1-5.2 TODO: touch target lists don't exist yet + + // Steps 5.3 and most of 5.9 + // A change in whatwg/dom#240 specifies that + // the event path belongs to the event itself, rather than being + // a local variable of the dispatch algorithm, but this is mostly + // related to shadow DOM requirements that aren't otherwise + // implemented right now. The path also needs to contain + // various flags instead of just bare event targets. + let path = self.construct_event_path(&target); + rooted_vec!(let event_path <- path.into_iter()); + + // Step 5.4 + let is_activation_event = self.is::<MouseEvent>() && self.type_() == atom!("click"); + + // Step 5.5 + let mut activation_target = if is_activation_event { + target + .downcast::<Element>() + .and_then(|e| e.as_maybe_activatable()) + } else { + // Step 3 + None + }; + + // Steps 5-6 - 5.7 are shadow DOM slot things + + // Step 5.9.8.1, not covered in construct_event_path + // This what makes sure that clicking on e.g. an <img> inside + // an <a> will cause activation of the activatable ancestor. + if is_activation_event && activation_target.is_none() && self.bubbles.get() { + for object in event_path.iter() { + if let Some(activatable_ancestor) = object + .downcast::<Element>() + .and_then(|e| e.as_maybe_activatable()) + { + activation_target = Some(activatable_ancestor); + // once activation_target isn't null, we stop + // looking at ancestors for it. + break; + } + } + } - if self.stop_propagation.get() { - // If the event's stop propagation flag is set, we can skip everything because - // it prevents the calls of the invoke algorithm in the spec. + // Steps 5.10-5.11 are shadow DOM - // Step 10-12. - self.clear_dispatching_flags(); + // Not specified in dispatch spec overtly; this is because + // the legacy canceled activation behavior of a checkbox + // or radio button needs to know what happened in the + // corresponding pre-activation behavior. + let mut pre_activation_result: Option<InputActivationState> = None; - // Step 14. - return self.status(); + // Step 5.12 + if is_activation_event { + if let Some(maybe_checkbox) = activation_target { + pre_activation_result = maybe_checkbox.legacy_pre_activation_behavior(); + } } - // Step 3. The "invoke" algorithm is only used on `target` separately, - // so we don't put it in the path. - rooted_vec!(let mut event_path); + let timeline_window = match DomRoot::downcast::<Window>(target.global()) { + Some(window) => { + if window.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + Some(window) + } else { + None + } + }, + _ => None, + }; + + // Step 5.13 + for object in event_path.iter().rev() { + if &**object == &*target { + self.phase.set(EventPhase::AtTarget); + } else { + self.phase.set(EventPhase::Capturing); + } - // Step 4. - if let Some(target_node) = target.downcast::<Node>() { - for ancestor in target_node.ancestors() { - event_path.push(JS::from_ref(ancestor.upcast::<EventTarget>())); + // setting self.target is step 1 of invoke, + // but done here because our event_path isn't a member of self + // (without shadow DOM, target_override is always the + // target to set to) + self.target.set(Some(target_override)); + invoke( + timeline_window.as_deref(), + object, + self, + Some(ListenerPhase::Capturing), + ); + } + + // Step 5.14 + for object in event_path.iter() { + let at_target = &**object == &*target; + if at_target || self.bubbles.get() { + self.phase.set(if at_target { + EventPhase::AtTarget + } else { + EventPhase::Bubbling + }); + + self.target.set(Some(target_override)); + invoke( + timeline_window.as_deref(), + object, + self, + Some(ListenerPhase::Bubbling), + ); } - let top_most_ancestor_or_target = - Root::from_ref(event_path.r().last().cloned().unwrap_or(target)); - if let Some(document) = Root::downcast::<Document>(top_most_ancestor_or_target) { - if self.type_() != atom!("load") && document.browsing_context().is_some() { - event_path.push(JS::from_ref(document.window().upcast())); + } + + // Step 6 + self.phase.set(EventPhase::None); + + // FIXME: The UIEvents spec still expects firing an event + // to carry a "default action" semantic, but the HTML spec + // has removed this concept. Nothing in either spec currently + // (as of Jan 11 2020) says that, e.g., a keydown event on an + // input element causes a character to be typed; the UIEvents + // spec assumes the HTML spec is covering it, and the HTML spec + // no longer specifies any UI event other than mouse click as + // causing an element to perform an action. + // Compare: + // https://w3c.github.io/uievents/#default-action + // https://dom.spec.whatwg.org/#action-versus-occurance + if !self.DefaultPrevented() { + if let Some(target) = self.GetTarget() { + if let Some(node) = target.downcast::<Node>() { + let vtable = vtable_for(&node); + vtable.handle_event(self); } } } - // Steps 5-9. In a separate function to short-circuit various things easily. - dispatch_to_listeners(self, target, event_path.r()); + // Step 7 + self.current_target.set(None); + + // Step 8 TODO: if path were in the event struct, we'd clear it now + + // Step 9 + self.dispatching.set(false); + self.stop_propagation.set(false); + self.stop_immediate.set(false); + + // Step 10 TODO: condition is always false until there's shadow DOM - // Default action. - if let Some(target) = self.GetTarget() { - if let Some(node) = target.downcast::<Node>() { - let vtable = vtable_for(&node); - vtable.handle_event(self); + // Step 11 + if let Some(activation_target) = activation_target { + if self.DefaultPrevented() { + activation_target.legacy_canceled_activation_behavior(pre_activation_result); + } else { + activation_target.activation_behavior(self, target); } } - // Step 10-12. - self.clear_dispatching_flags(); - - // Step 14. - self.status() + return self.status(); } pub fn status(&self) -> EventStatus { - match self.DefaultPrevented() { - true => EventStatus::Canceled, - false => EventStatus::NotCanceled + if self.DefaultPrevented() { + EventStatus::Canceled + } else { + EventStatus::NotCanceled } } @@ -179,18 +361,6 @@ impl Event { } #[inline] - // https://dom.spec.whatwg.org/#concept-event-dispatch Steps 10-12. - fn clear_dispatching_flags(&self) { - assert!(self.dispatching.get()); - - self.dispatching.set(false); - self.stop_propagation.set(false); - self.stop_immediate.set(false); - self.phase.set(EventPhase::None); - self.current_target.set(None); - } - - #[inline] pub fn initialized(&self) -> bool { self.initialized.get() } @@ -233,12 +403,17 @@ impl EventMethods for Event { } // https://dom.spec.whatwg.org/#dom-event-target - fn GetTarget(&self) -> Option<Root<EventTarget>> { + fn GetTarget(&self) -> Option<DomRoot<EventTarget>> { + self.target.get() + } + + // https://dom.spec.whatwg.org/#dom-event-srcelement + fn GetSrcElement(&self) -> Option<DomRoot<EventTarget>> { self.target.get() } // https://dom.spec.whatwg.org/#dom-event-currenttarget - fn GetCurrentTarget(&self) -> Option<Root<EventTarget>> { + fn GetCurrentTarget(&self) -> Option<DomRoot<EventTarget>> { self.current_target.get() } @@ -275,17 +450,41 @@ impl EventMethods for Event { self.cancelable.get() } + // https://dom.spec.whatwg.org/#dom-event-returnvalue + fn ReturnValue(&self) -> bool { + self.canceled.get() == EventDefault::Allowed + } + + // https://dom.spec.whatwg.org/#dom-event-returnvalue + fn SetReturnValue(&self, val: bool) { + if !val { + self.PreventDefault(); + } + } + + // https://dom.spec.whatwg.org/#dom-event-cancelbubble + fn CancelBubble(&self) -> bool { + self.stop_propagation.get() + } + + // https://dom.spec.whatwg.org/#dom-event-cancelbubble + fn SetCancelBubble(&self, value: bool) { + if value { + self.stop_propagation.set(true) + } + } + // https://dom.spec.whatwg.org/#dom-event-timestamp - fn TimeStamp(&self) -> u64 { - self.timestamp + fn TimeStamp(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution( + (self.precise_time_ns - (*self.global().performance().TimeOrigin()).round() as u64) + .to_ms(), + ) } // https://dom.spec.whatwg.org/#dom-event-initevent - fn InitEvent(&self, - type_: DOMString, - bubbles: bool, - cancelable: bool) { - self.init_event(Atom::from(type_), bubbles, cancelable) + fn InitEvent(&self, type_: DOMString, bubbles: bool, cancelable: bool) { + self.init_event(Atom::from(type_), bubbles, cancelable) } // https://dom.spec.whatwg.org/#dom-event-istrusted @@ -294,17 +493,18 @@ impl EventMethods for Event { } } -#[derive(PartialEq, HeapSizeOf, Copy, Clone)] +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub enum EventBubbles { Bubbles, - DoesNotBubble + DoesNotBubble, } impl From<bool> for EventBubbles { fn from(boolean: bool) -> Self { - match boolean { - true => EventBubbles::Bubbles, - false => EventBubbles::DoesNotBubble + if boolean { + EventBubbles::Bubbles + } else { + EventBubbles::DoesNotBubble } } } @@ -313,22 +513,23 @@ impl From<EventBubbles> for bool { fn from(bubbles: EventBubbles) -> Self { match bubbles { EventBubbles::Bubbles => true, - EventBubbles::DoesNotBubble => false + EventBubbles::DoesNotBubble => false, } } } -#[derive(PartialEq, HeapSizeOf, Copy, Clone)] +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub enum EventCancelable { Cancelable, - NotCancelable + NotCancelable, } impl From<bool> for EventCancelable { fn from(boolean: bool) -> Self { - match boolean { - true => EventCancelable::Cancelable, - false => EventCancelable::NotCancelable + if boolean { + EventCancelable::Cancelable + } else { + EventCancelable::NotCancelable } } } @@ -337,19 +538,19 @@ impl From<EventCancelable> for bool { fn from(bubbles: EventCancelable) -> Self { match bubbles { EventCancelable::Cancelable => true, - EventCancelable::NotCancelable => false + EventCancelable::NotCancelable => false, } } } -#[derive(JSTraceable, Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Eq, JSTraceable, PartialEq)] #[repr(u16)] -#[derive(HeapSizeOf)] +#[derive(MallocSizeOf)] pub enum EventPhase { - None = EventConstants::NONE, + None = EventConstants::NONE, Capturing = EventConstants::CAPTURING_PHASE, - AtTarget = EventConstants::AT_TARGET, - Bubbling = EventConstants::BUBBLING_PHASE, + AtTarget = EventConstants::AT_TARGET, + Bubbling = EventConstants::BUBBLING_PHASE, } /// An enum to indicate whether the default action of an event is allowed. @@ -361,9 +562,9 @@ pub enum EventPhase { /// helps us to prevent such events from being [sent to the constellation][msg] where it will be /// handled once again for page scrolling (which is definitely not what we'd want). /// -/// [msg]: https://doc.servo.org/script_traits/enum.ConstellationMsg.html#variant.KeyEvent +/// [msg]: https://doc.servo.org/compositing/enum.ConstellationMsg.html#variant.KeyEvent /// -#[derive(JSTraceable, HeapSizeOf, Copy, Clone, PartialEq)] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub enum EventDefault { /// The default action of the event is allowed (constructor's default) Allowed, @@ -377,21 +578,19 @@ pub enum EventDefault { #[derive(PartialEq)] pub enum EventStatus { Canceled, - NotCanceled + NotCanceled, } // https://dom.spec.whatwg.org/#concept-event-fire -pub struct EventRunnable { +pub struct EventTask { pub target: Trusted<EventTarget>, pub name: Atom, pub bubbles: EventBubbles, pub cancelable: EventCancelable, } -impl Runnable for EventRunnable { - fn name(&self) -> &'static str { "EventRunnable" } - - fn handler(self: Box<EventRunnable>) { +impl TaskOnce for EventTask { + fn run_once(self) { let target = self.target.root(); let bubbles = self.bubbles; let cancelable = self.cancelable; @@ -400,129 +599,141 @@ impl Runnable for EventRunnable { } // https://html.spec.whatwg.org/multipage/#fire-a-simple-event -pub struct SimpleEventRunnable { +pub struct SimpleEventTask { pub target: Trusted<EventTarget>, pub name: Atom, } -impl Runnable for SimpleEventRunnable { - fn name(&self) -> &'static str { "SimpleEventRunnable" } - - fn handler(self: Box<SimpleEventRunnable>) { +impl TaskOnce for SimpleEventTask { + fn run_once(self) { let target = self.target.root(); target.fire_event(self.name); } } -// See dispatch_event. -// https://dom.spec.whatwg.org/#concept-event-dispatch -fn dispatch_to_listeners(event: &Event, target: &EventTarget, event_path: &[&EventTarget]) { - assert!(!event.stop_propagation.get()); - assert!(!event.stop_immediate.get()); - - let window = match Root::downcast::<Window>(target.global()) { - Some(window) => { - if window.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { - Some(window) - } else { - None - } - }, - _ => None, - }; - - // Step 5. - event.phase.set(EventPhase::Capturing); +// https://dom.spec.whatwg.org/#concept-event-listener-invoke +fn invoke( + timeline_window: Option<&Window>, + object: &EventTarget, + event: &Event, + phase: Option<ListenerPhase>, + // TODO legacy_output_did_listeners_throw for indexeddb +) { + // Step 1: Until shadow DOM puts the event path in the + // event itself, this is easier to do in dispatch before + // calling invoke. - // Step 6. - for object in event_path.iter().rev() { - invoke(window.r(), object, event, Some(ListenerPhase::Capturing)); - if event.stop_propagation.get() { - return; - } - } - assert!(!event.stop_propagation.get()); - assert!(!event.stop_immediate.get()); + // Step 2 TODO: relatedTarget only matters for shadow DOM - // Step 7. - event.phase.set(EventPhase::AtTarget); + // Step 3 TODO: touch target lists not implemented - // Step 8. - invoke(window.r(), target, event, None); + // Step 4. if event.stop_propagation.get() { return; } - assert!(!event.stop_propagation.get()); - assert!(!event.stop_immediate.get()); - - if !event.bubbles.get() { - return; - } + // Step 5. + event.current_target.set(Some(object)); - // Step 9.1. - event.phase.set(EventPhase::Bubbling); + // Step 6 + let listeners = object.get_listeners_for(&event.type_(), phase); - // Step 9.2. - for object in event_path { - invoke(window.r(), object, event, Some(ListenerPhase::Bubbling)); - if event.stop_propagation.get() { - return; + // Step 7. + let found = inner_invoke(timeline_window, object, event, &listeners); + + // Step 8 + if !found && event.trusted.get() { + if let Some(legacy_type) = match event.type_() { + atom!("animationend") => Some(atom!("webkitAnimationEnd")), + atom!("animationiteration") => Some(atom!("webkitAnimationIteration")), + atom!("animationstart") => Some(atom!("webkitAnimationStart")), + atom!("transitionend") => Some(atom!("webkitTransitionEnd")), + atom!("transitionrun") => Some(atom!("webkitTransitionRun")), + _ => None, + } { + let original_type = event.type_(); + *event.type_.borrow_mut() = legacy_type; + inner_invoke(timeline_window, object, event, &listeners); + *event.type_.borrow_mut() = original_type; } } } -// https://dom.spec.whatwg.org/#concept-event-listener-invoke -fn invoke(window: Option<&Window>, - object: &EventTarget, - event: &Event, - specific_listener_phase: Option<ListenerPhase>) { - // Step 1. - assert!(!event.stop_propagation.get()); - - // Steps 2-3. - let listeners = object.get_listeners_for(&event.type_(), specific_listener_phase); - - // Step 4. - event.current_target.set(Some(object)); - - // Step 5. - inner_invoke(window, object, event, &listeners); - - // TODO: step 6. -} - // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke -fn inner_invoke(window: Option<&Window>, - object: &EventTarget, - event: &Event, - listeners: &[CompiledEventListener]) - -> bool { +fn inner_invoke( + timeline_window: Option<&Window>, + object: &EventTarget, + event: &Event, + listeners: &[CompiledEventListener], +) -> bool { // Step 1. let mut found = false; // Step 2. for listener in listeners { + // FIXME(#25479): We need an "if !listener.removed()" here, + // but there's a subtlety. Where Servo is currently using the + // CompiledEventListener, we really need something that maps to + // https://dom.spec.whatwg.org/#concept-event-listener + // which is not the same thing as the EventListener interface. + // script::dom::eventtarget::EventListenerEntry is the closest + // match we have, and is already holding the "once" flag, + // but it's not a drop-in replacement. + // Steps 2.1 and 2.3-2.4 are not done because `listeners` contain only the // relevant ones for this invoke call during the dispatch algorithm. // Step 2.2. found = true; - // TODO: step 2.5. + // Step 2.5. + if let CompiledEventListener::Listener(event_listener) = listener { + object.remove_listener_if_once(&event.type_(), &event_listener); + } + + // Step 2.6 + let global = listener.associated_global(); - // Step 2.6. + // Step 2.7-2.8 + let current_event = if let Some(window) = global.downcast::<Window>() { + window.set_current_event(Some(event)) + } else { + None + }; + + // Step 2.9 TODO: EventListener passive option not implemented + + // Step 2.10 let marker = TimelineMarker::start("DOMEvent".to_owned()); + + // Step 2.10 listener.call_or_handle_event(object, event, ExceptionHandling::Report); - if let Some(window) = window { + + if let Some(window) = timeline_window { window.emit_timeline_marker(marker.end()); } + + // Step 2.11 TODO: passive not implemented + + // Step 2.12 + if let Some(window) = global.downcast::<Window>() { + window.set_current_event(current_event.as_ref().map(|e| &**e)); + } + + // Step 2.13: short-circuit instead of going to next listener if event.stop_immediate.get() { return found; } - - // TODO: step 2.7. } // Step 3. found } + +impl Default for EventBinding::EventInit { + fn default() -> EventBinding::EventInit { + EventBinding::EventInit { + bubbles: false, + cancelable: false, + } + } +} diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index 7945eec0b7d..49e612cd6eb 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -1,81 +1,85 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventSourceBinding::{EventSourceInit, EventSourceMethods, Wrap}; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::messageevent::MessageEvent; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EventSourceBinding::{ + EventSourceInit, EventSourceMethods, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageevent::MessageEvent; +use crate::dom::performanceresourcetiming::InitiatorType; +use crate::fetch::{create_a_potential_cors_request, FetchCanceller}; +use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; +use crate::realms::enter_realm; +use crate::task_source::{TaskSource, TaskSourceName}; +use crate::timers::OneshotTimerCallback; use dom_struct::dom_struct; -use encoding::Encoding; -use encoding::all::UTF_8; -use euclid::length::Length; -use hyper::header::{Accept, qitem}; +use euclid::Length; +use headers::ContentType; +use http::header::{self, HeaderName, HeaderValue}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use js::conversions::ToJSValConvertible; -use js::jsapi::JSAutoCompartment; use js::jsval::UndefinedValue; -use mime::{Mime, TopLevel, SubLevel}; -use net_traits::{CoreResourceMsg, FetchMetadata, FetchResponseMsg, FetchResponseListener, NetworkError}; -use net_traits::request::{CacheMode, CorsSettings, CredentialsMode}; -use net_traits::request::{RequestInit, RequestMode}; -use network_listener::{NetworkListener, PreInvoke}; -use script_thread::Runnable; +use mime::{self, Mime}; +use net_traits::request::{CacheMode, CorsSettings, Destination, RequestBuilder}; +use net_traits::{CoreResourceMsg, FetchChannels, FetchMetadata, FilteredMetadata}; +use net_traits::{FetchResponseListener, FetchResponseMsg, NetworkError}; +use net_traits::{ResourceFetchTiming, ResourceTimingType}; use servo_atoms::Atom; use servo_url::ServoUrl; use std::cell::Cell; use std::mem; use std::str::{Chars, FromStr}; use std::sync::{Arc, Mutex}; -use task_source::TaskSource; -use timers::OneshotTimerCallback; - -header! { (LastEventId, "Last-Event-ID") => [String] } +use utf8; const DEFAULT_RECONNECTION_TIME: u64 = 5000; -#[derive(JSTraceable, PartialEq, Copy, Clone, Debug, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] struct GenerationId(u32); -#[derive(JSTraceable, PartialEq, Copy, Clone, Debug, HeapSizeOf)] -/// https://html.spec.whatwg.org/multipage/#dom-eventsource-readystate +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] +/// <https://html.spec.whatwg.org/multipage/#dom-eventsource-readystate> enum ReadyState { Connecting = 0, Open = 1, - Closed = 2 + Closed = 2, } #[dom_struct] pub struct EventSource { eventtarget: EventTarget, url: ServoUrl, - request: DOMRefCell<Option<RequestInit>>, - last_event_id: DOMRefCell<DOMString>, + request: DomRefCell<Option<RequestBuilder>>, + last_event_id: DomRefCell<DOMString>, reconnection_time: Cell<u64>, generation_id: Cell<GenerationId>, ready_state: Cell<ReadyState>, with_credentials: bool, + canceller: DomRefCell<FetchCanceller>, } enum ParserState { Field, Comment, Value, - Eol + Eol, } struct EventSourceContext { + incomplete_utf8: Option<utf8::Incomplete>, + event_source: Trusted<EventSource>, gen_id: GenerationId, action_sender: ipc::IpcSender<FetchResponseMsg>, @@ -88,29 +92,39 @@ struct EventSourceContext { event_type: String, data: String, last_event_id: String, + + resource_timing: ResourceFetchTiming, } impl EventSourceContext { + /// <https://html.spec.whatwg.org/multipage/#announce-the-connection> fn announce_the_connection(&self) { let event_source = self.event_source.root(); if self.gen_id != event_source.generation_id.get() { return; } - let runnable = box AnnounceConnectionRunnable { - event_source: self.event_source.clone() - }; - let _ = event_source.global().networking_task_source().queue(runnable, &*event_source.global()); + let global = event_source.global(); + let event_source = self.event_source.clone(); + // FIXME(nox): Why are errors silenced here? + let _ = global.remote_event_task_source().queue( + task!(announce_the_event_source_connection: move || { + let event_source = event_source.root(); + if event_source.ready_state.get() != ReadyState::Closed { + event_source.ready_state.set(ReadyState::Open); + event_source.upcast::<EventTarget>().fire_event(atom!("open")); + } + }), + &global, + ); } + /// <https://html.spec.whatwg.org/multipage/#fail-the-connection> fn fail_the_connection(&self) { let event_source = self.event_source.root(); if self.gen_id != event_source.generation_id.get() { return; } - let runnable = box FailConnectionRunnable { - event_source: self.event_source.clone() - }; - let _ = event_source.global().networking_task_source().queue(runnable, &*event_source.global()); + event_source.fail_the_connection(); } // https://html.spec.whatwg.org/multipage/#reestablish-the-connection @@ -121,12 +135,43 @@ impl EventSourceContext { return; } - // Step 1 - let runnable = box ReestablishConnectionRunnable { - event_source: self.event_source.clone(), - action_sender: self.action_sender.clone() - }; - let _ = event_source.global().networking_task_source().queue(runnable, &*event_source.global()); + let trusted_event_source = self.event_source.clone(); + let action_sender = self.action_sender.clone(); + let global = event_source.global(); + // FIXME(nox): Why are errors silenced here? + let _ = global.remote_event_task_source().queue( + task!(reestablish_the_event_source_onnection: move || { + let event_source = trusted_event_source.root(); + + // Step 1.1. + if event_source.ready_state.get() == ReadyState::Closed { + return; + } + + // Step 1.2. + event_source.ready_state.set(ReadyState::Connecting); + + // Step 1.3. + event_source.upcast::<EventTarget>().fire_event(atom!("error")); + + // Step 2. + let duration = Length::new(event_source.reconnection_time.get()); + + // Step 3. + // TODO: Optionally wait some more. + + // Steps 4-5. + let callback = OneshotTimerCallback::EventSourceTimeout( + EventSourceTimeoutCallback { + event_source: trusted_event_source, + action_sender, + } + ); + // FIXME(nox): Why are errors silenced here? + let _ = event_source.global().schedule_callback(callback, duration); + }), + &global, + ); } // https://html.spec.whatwg.org/multipage/#processField @@ -136,12 +181,14 @@ impl EventSourceContext { "data" => { self.data.push_str(&self.value); self.data.push('\n'); - } + }, "id" => mem::swap(&mut self.last_event_id, &mut self.value), - "retry" => if let Ok(time) = u64::from_str(&self.value) { - self.event_source.root().reconnection_time.set(time); + "retry" => { + if let Ok(time) = u64::from_str(&self.value) { + self.event_source.root().reconnection_time.set(time); + } }, - _ => () + _ => (), } self.field.clear(); @@ -174,23 +221,42 @@ impl EventSourceContext { }; // Steps 4-5 let event = { - let _ac = JSAutoCompartment::new(event_source.global().get_cx(), - event_source.reflector().get_jsobject().get()); - rooted!(in(event_source.global().get_cx()) let mut data = UndefinedValue()); - unsafe { self.data.to_jsval(event_source.global().get_cx(), data.handle_mut()) }; - MessageEvent::new(&*event_source.global(), type_, false, false, data.handle(), - DOMString::from(self.origin.clone()), - event_source.last_event_id.borrow().clone()) + let _ac = enter_realm(&*event_source); + rooted!(in(*event_source.global().get_cx()) let mut data = UndefinedValue()); + unsafe { + self.data + .to_jsval(*event_source.global().get_cx(), data.handle_mut()) + }; + MessageEvent::new( + &*event_source.global(), + type_, + false, + false, + data.handle(), + DOMString::from(self.origin.clone()), + None, + event_source.last_event_id.borrow().clone(), + Vec::with_capacity(0), + ) }; // Step 7 self.event_type.clear(); self.data.clear(); - // Step 8 - let runnable = box DispatchEventRunnable { - event_source: self.event_source.clone(), - event: Trusted::new(&event) - }; - let _ = event_source.global().networking_task_source().queue(runnable, &*event_source.global()); + + // Step 8. + let global = event_source.global(); + let event_source = self.event_source.clone(); + let event = Trusted::new(&*event); + // FIXME(nox): Why are errors silenced here? + let _ = global.remote_event_task_source().queue( + task!(dispatch_the_event_source_event: move || { + let event_source = event_source.root(); + if event_source.ready_state.get() != ReadyState::Closed { + event.root().upcast::<Event>().fire(&event_source.upcast()); + } + }), + &global, + ); } // https://html.spec.whatwg.org/multipage/#event-stream-interpretation @@ -205,31 +271,31 @@ impl EventSourceContext { if let Some(&' ') = stream.peek() { stream.next(); } - } + }, ('\n', &ParserState::Value) => { self.parser_state = ParserState::Eol; self.process_field(); - } + }, ('\r', &ParserState::Value) => { if let Some(&'\n') = stream.peek() { continue; } self.parser_state = ParserState::Eol; self.process_field(); - } + }, ('\n', &ParserState::Field) => { self.parser_state = ParserState::Eol; self.process_field(); - } + }, ('\r', &ParserState::Field) => { if let Some(&'\n') = stream.peek() { continue; } self.parser_state = ParserState::Eol; self.process_field(); - } + }, ('\n', &ParserState::Eol) => self.dispatch_event(), ('\r', &ParserState::Eol) => { @@ -237,7 +303,7 @@ impl EventSourceContext { continue; } self.dispatch_event(); - } + }, ('\n', &ParserState::Comment) => self.parser_state = ParserState::Eol, ('\r', &ParserState::Comment) => { @@ -245,14 +311,14 @@ impl EventSourceContext { continue; } self.parser_state = ParserState::Eol; - } + }, (_, &ParserState::Field) => self.field.push(ch), (_, &ParserState::Value) => self.value.push(ch), (_, &ParserState::Eol) => { self.parser_state = ParserState::Field; self.field.push(ch); - } + }, (_, &ParserState::Comment) => (), } } @@ -273,34 +339,102 @@ impl FetchResponseListener for EventSourceContext { Ok(fm) => { let meta = match fm { FetchMetadata::Unfiltered(m) => m, - FetchMetadata::Filtered { unsafe_, .. } => unsafe_ + FetchMetadata::Filtered { unsafe_, filtered } => match filtered { + FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_) => { + return self.fail_the_connection() + }, + _ => unsafe_, + }, }; - match meta.content_type { - None => self.fail_the_connection(), - Some(ct) => match ct.into_inner().0 { - Mime(TopLevel::Text, SubLevel::EventStream, _) => { - self.origin = meta.final_url.origin().unicode_serialization(); - self.announce_the_connection(); - } - _ => self.fail_the_connection() - } + let mime = match meta.content_type { + None => return self.fail_the_connection(), + Some(ct) => <ContentType as Into<Mime>>::into(ct.into_inner()), + }; + if (mime.type_(), mime.subtype()) != (mime::TEXT, mime::EVENT_STREAM) { + return self.fail_the_connection(); } - } + self.origin = meta.final_url.origin().ascii_serialization(); + self.announce_the_connection(); + }, Err(_) => { - self.reestablish_the_connection(); - } + // The spec advises failing here if reconnecting would be + // "futile", with no more specific advice; WPT tests + // consider a non-http(s) scheme to be futile. + match self.event_source.root().url.scheme() { + "http" | "https" => self.reestablish_the_connection(), + _ => self.fail_the_connection(), + } + }, } } fn process_response_chunk(&mut self, chunk: Vec<u8>) { - let mut stream = String::new(); - UTF_8.raw_decoder().raw_feed(&chunk, &mut stream); - self.parse(stream.chars()) + let mut input = &*chunk; + if let Some(mut incomplete) = self.incomplete_utf8.take() { + match incomplete.try_complete(input) { + None => return, + Some((result, remaining_input)) => { + self.parse(result.unwrap_or("\u{FFFD}").chars()); + input = remaining_input; + }, + } + } + + while !input.is_empty() { + match utf8::decode(&input) { + Ok(s) => { + self.parse(s.chars()); + return; + }, + Err(utf8::DecodeError::Invalid { + valid_prefix, + remaining_input, + .. + }) => { + self.parse(valid_prefix.chars()); + self.parse("\u{FFFD}".chars()); + input = remaining_input; + }, + Err(utf8::DecodeError::Incomplete { + valid_prefix, + incomplete_suffix, + }) => { + self.parse(valid_prefix.chars()); + self.incomplete_utf8 = Some(incomplete_suffix); + return; + }, + } + } } - fn process_response_eof(&mut self, _response: Result<(), NetworkError>) { + fn process_response_eof(&mut self, _response: Result<ResourceFetchTiming, NetworkError>) { + if let Some(_) = self.incomplete_utf8.take() { + self.parse("\u{FFFD}".chars()); + } self.reestablish_the_connection(); } + + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } + + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } + + fn submit_resource_timing(&mut self) { + network_listener::submit_timing(self) + } +} + +impl ResourceTimingListener for EventSourceContext { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + (InitiatorType::Other, self.event_source.root().url().clone()) + } + + fn resource_timing_global(&self) -> DomRoot<GlobalScope> { + self.event_source.root().global() + } } impl PreInvoke for EventSourceContext { @@ -314,39 +448,77 @@ impl EventSource { EventSource { eventtarget: EventTarget::new_inherited(), url: url, - request: DOMRefCell::new(None), - last_event_id: DOMRefCell::new(DOMString::from("")), + request: DomRefCell::new(None), + last_event_id: DomRefCell::new(DOMString::from("")), reconnection_time: Cell::new(DEFAULT_RECONNECTION_TIME), generation_id: Cell::new(GenerationId(0)), ready_state: Cell::new(ReadyState::Connecting), with_credentials: with_credentials, + canceller: DomRefCell::new(Default::default()), } } - fn new(global: &GlobalScope, url: ServoUrl, with_credentials: bool) -> Root<EventSource> { - reflect_dom_object(box EventSource::new_inherited(url, with_credentials), - global, - Wrap) + fn new(global: &GlobalScope, url: ServoUrl, with_credentials: bool) -> DomRoot<EventSource> { + reflect_dom_object( + Box::new(EventSource::new_inherited(url, with_credentials)), + global, + ) } - pub fn request(&self) -> RequestInit { + // https://html.spec.whatwg.org/multipage/#sse-processing-model:fail-the-connection-3 + pub fn cancel(&self) { + self.canceller.borrow_mut().cancel(); + self.fail_the_connection(); + } + + /// <https://html.spec.whatwg.org/multipage/#fail-the-connection> + pub fn fail_the_connection(&self) { + let global = self.global(); + let event_source = Trusted::new(self); + // FIXME(nox): Why are errors silenced here? + let _ = global.remote_event_task_source().queue( + task!(fail_the_event_source_connection: move || { + let event_source = event_source.root(); + if event_source.ready_state.get() != ReadyState::Closed { + event_source.ready_state.set(ReadyState::Closed); + event_source.upcast::<EventTarget>().fire_event(atom!("error")); + } + }), + &global, + ); + } + + pub fn request(&self) -> RequestBuilder { self.request.borrow().clone().unwrap() } - pub fn Constructor(global: &GlobalScope, - url: DOMString, - event_source_init: &EventSourceInit) -> Fallible<Root<EventSource>> { + pub fn url(&self) -> &ServoUrl { + &self.url + } + + // https://html.spec.whatwg.org/multipage/#dom-eventsource + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + url: DOMString, + event_source_init: &EventSourceInit, + ) -> Fallible<DomRoot<EventSource>> { // TODO: Step 2 relevant settings object // Step 3 let base_url = global.api_base_url(); let url_record = match base_url.join(&*url) { Ok(u) => u, // Step 4 - Err(_) => return Err(Error::Syntax) + Err(_) => return Err(Error::Syntax), }; // Step 1, 5 - let ev = EventSource::new(global, url_record.clone(), event_source_init.withCredentials); + let ev = EventSource::new( + global, + url_record.clone(), + event_source_init.withCredentials, + ); + global.track_event_source(&ev); // Steps 6-7 let cors_attribute_state = if event_source_init.withCredentials { CorsSettings::UseCredentials @@ -355,22 +527,22 @@ impl EventSource { }; // Step 8 // TODO: Step 9 set request's client settings - let mut request = RequestInit { - url: url_record, - origin: global.get_url(), - pipeline_id: Some(global.pipeline_id()), - // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request - use_url_credentials: true, - mode: RequestMode::CorsMode, - credentials_mode: if cors_attribute_state == CorsSettings::Anonymous { - CredentialsMode::CredentialsSameOrigin - } else { - CredentialsMode::Include - }, - ..RequestInit::default() - }; + let mut request = create_a_potential_cors_request( + url_record, + Destination::None, + Some(cors_attribute_state), + Some(true), + global.get_referrer(), + ) + .origin(global.origin().immutable().clone()) + .pipeline_id(Some(global.pipeline_id())); + // Step 10 - request.headers.set(Accept(vec![qitem(mime!(Text / EventStream))])); + // TODO(eijebong): Replace once typed headers allow it + request.headers.insert( + header::ACCEPT, + HeaderValue::from_static("text/event-stream"), + ); // Step 11 request.cache_mode = CacheMode::NoStore; // Step 12 @@ -378,6 +550,8 @@ impl EventSource { // Step 14 let (action_sender, action_receiver) = ipc::channel().unwrap(); let context = EventSourceContext { + incomplete_utf8: None, + event_source: Trusted::new(&ev), gen_id: ev.generation_id.get(), action_sender: action_sender.clone(), @@ -390,21 +564,41 @@ impl EventSource { event_type: String::new(), data: String::new(), last_event_id: String::new(), + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), }; let listener = NetworkListener { context: Arc::new(Mutex::new(context)), task_source: global.networking_task_source(), - wrapper: Some(global.get_runnable_wrapper()) + canceller: Some(global.task_canceller(TaskSourceName::Networking)), }; - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_fetch(message.to().unwrap()); - }); - global.core_resource_thread().send(CoreResourceMsg::Fetch(request, action_sender)).unwrap(); + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + listener.notify_fetch(message.to().unwrap()); + }), + ); + let cancel_receiver = ev.canceller.borrow_mut().initialize(); + global + .core_resource_thread() + .send(CoreResourceMsg::Fetch( + request, + FetchChannels::ResponseMsg(action_sender, Some(cancel_receiver)), + )) + .unwrap(); // Step 13 Ok(ev) } } +// https://html.spec.whatwg.org/multipage/#garbage-collection-2 +impl Drop for EventSource { + fn drop(&mut self) { + // If an EventSource object is garbage collected while its connection is still open, + // the user agent must abort any instance of the fetch algorithm opened by this EventSource. + self.canceller.borrow_mut().cancel(); + } +} + impl EventSourceMethods for EventSource { // https://html.spec.whatwg.org/multipage/#handler-eventsource-onopen event_handler!(open, GetOnopen, SetOnopen); @@ -434,80 +628,16 @@ impl EventSourceMethods for EventSource { fn Close(&self) { let GenerationId(prev_id) = self.generation_id.get(); self.generation_id.set(GenerationId(prev_id + 1)); + self.canceller.borrow_mut().cancel(); self.ready_state.set(ReadyState::Closed); } } -pub struct AnnounceConnectionRunnable { - event_source: Trusted<EventSource>, -} - -impl Runnable for AnnounceConnectionRunnable { - fn name(&self) -> &'static str { "EventSource AnnounceConnectionRunnable" } - - // https://html.spec.whatwg.org/multipage/#announce-the-connection - fn handler(self: Box<AnnounceConnectionRunnable>) { - let event_source = self.event_source.root(); - if event_source.ready_state.get() != ReadyState::Closed { - event_source.ready_state.set(ReadyState::Open); - event_source.upcast::<EventTarget>().fire_event(atom!("open")); - } - } -} - -pub struct FailConnectionRunnable { - event_source: Trusted<EventSource>, -} - -impl Runnable for FailConnectionRunnable { - fn name(&self) -> &'static str { "EventSource FailConnectionRunnable" } - - // https://html.spec.whatwg.org/multipage/#fail-the-connection - fn handler(self: Box<FailConnectionRunnable>) { - let event_source = self.event_source.root(); - if event_source.ready_state.get() != ReadyState::Closed { - event_source.ready_state.set(ReadyState::Closed); - event_source.upcast::<EventTarget>().fire_event(atom!("error")); - } - } -} - -pub struct ReestablishConnectionRunnable { - event_source: Trusted<EventSource>, - action_sender: ipc::IpcSender<FetchResponseMsg>, -} - -impl Runnable for ReestablishConnectionRunnable { - fn name(&self) -> &'static str { "EventSource ReestablishConnectionRunnable" } - - // https://html.spec.whatwg.org/multipage/#reestablish-the-connection - fn handler(self: Box<ReestablishConnectionRunnable>) { - let event_source = self.event_source.root(); - // Step 1.1 - if event_source.ready_state.get() == ReadyState::Closed { - return; - } - // Step 1.2 - event_source.ready_state.set(ReadyState::Connecting); - // Step 1.3 - event_source.upcast::<EventTarget>().fire_event(atom!("error")); - // Step 2 - let duration = Length::new(event_source.reconnection_time.get()); - // TODO Step 3: Optionally wait some more - // Steps 4-5 - let callback = OneshotTimerCallback::EventSourceTimeout(EventSourceTimeoutCallback { - event_source: self.event_source.clone(), - action_sender: self.action_sender.clone() - }); - let _ = event_source.global().schedule_callback(callback, duration); - } -} - -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] pub struct EventSourceTimeoutCallback { - #[ignore_heap_size_of = "Because it is non-owning"] + #[ignore_malloc_size_of = "Because it is non-owning"] event_source: Trusted<EventSource>, - #[ignore_heap_size_of = "Because it is non-owning"] + #[ignore_malloc_size_of = "Because it is non-owning"] action_sender: ipc::IpcSender<FetchResponseMsg>, } @@ -524,27 +654,20 @@ impl EventSourceTimeoutCallback { let mut request = event_source.request(); // Step 5.3 if !event_source.last_event_id.borrow().is_empty() { - request.headers.set(LastEventId(String::from(event_source.last_event_id.borrow().clone()))); + //TODO(eijebong): Change this once typed header support custom values + request.headers.insert( + HeaderName::from_static("last-event-id"), + HeaderValue::from_str(&String::from(event_source.last_event_id.borrow().clone())) + .unwrap(), + ); } // Step 5.4 - global.core_resource_thread().send(CoreResourceMsg::Fetch(request, self.action_sender)).unwrap(); - } -} - -pub struct DispatchEventRunnable { - event_source: Trusted<EventSource>, - event: Trusted<MessageEvent>, -} - -impl Runnable for DispatchEventRunnable { - fn name(&self) -> &'static str { "EventSource DispatchEventRunnable" } - - // https://html.spec.whatwg.org/multipage/#dispatchMessage - fn handler(self: Box<DispatchEventRunnable>) { - let event_source = self.event_source.root(); - // Step 8 - if event_source.ready_state.get() != ReadyState::Closed { - self.event.root().upcast::<Event>().fire(&event_source.upcast()); - } + global + .core_resource_thread() + .send(CoreResourceMsg::Fetch( + request, + FetchChannels::ResponseMsg(self.action_sender, None), + )) + .unwrap(); } } diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs index f45942f0f05..eb27f63069f 100644 --- a/components/script/dom/eventtarget.rs +++ b/components/script/dom/eventtarget.rs @@ -1,54 +1,64 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::beforeunloadevent::BeforeUnloadEvent; -use dom::bindings::callback::{CallbackContainer, ExceptionHandling, CallbackFunction}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEventMethods; -use dom::bindings::codegen::Bindings::ErrorEventBinding::ErrorEventMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; -use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::UnionTypes::EventOrString; -use dom::bindings::error::{Error, Fallible, report_pending_exception}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::{DomObject, Reflector}; -use dom::bindings::str::DOMString; -use dom::element::Element; -use dom::errorevent::ErrorEvent; -use dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; -use dom::node::document_from_node; -use dom::virtualmethods::VirtualMethods; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::beforeunloadevent::BeforeUnloadEvent; +use crate::dom::bindings::callback::{CallbackContainer, CallbackFunction, ExceptionHandling}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BeforeUnloadEventBinding::BeforeUnloadEventMethods; +use crate::dom::bindings::codegen::Bindings::ErrorEventBinding::ErrorEventMethods; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; +use crate::dom::bindings::codegen::Bindings::EventTargetBinding::AddEventListenerOptions; +use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventListenerOptions; +use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::UnionTypes::AddEventListenerOptionsOrBoolean; +use crate::dom::bindings::codegen::UnionTypes::EventListenerOptionsOrBoolean; +use crate::dom::bindings::codegen::UnionTypes::EventOrString; +use crate::dom::bindings::error::{report_pending_exception, Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::element::Element; +use crate::dom::errorevent::ErrorEvent; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlformelement::FormControlElementHelpers; +use crate::dom::node::document_from_node; +use crate::dom::virtualmethods::VirtualMethods; +use crate::dom::window::Window; +use crate::dom::workerglobalscope::WorkerGlobalScope; +use crate::realms::{enter_realm, InRealm}; use dom_struct::dom_struct; use fnv::FnvHasher; -use heapsize::HeapSizeOf; -use js::jsapi::{CompileFunction, JS_GetFunctionObject, JSAutoCompartment}; -use js::rust::{AutoObjectVectorWrapper, CompileOptionsWrapper}; -use libc::{c_char, size_t}; +use js::jsapi::JS_GetFunctionObject; +use js::rust::transform_u16_to_source_text; +use js::rust::wrappers::CompileFunction; +use js::rust::{CompileOptionsWrapper, RootedObjectVectorWrapper}; +use libc::c_char; use servo_atoms::Atom; use servo_url::ServoUrl; -use std::collections::HashMap; use std::collections::hash_map::Entry::{Occupied, Vacant}; +use std::collections::HashMap; use std::default::Default; use std::ffi::CString; use std::hash::BuildHasherDefault; use std::mem; use std::ops::{Deref, DerefMut}; -use std::ptr; use std::rc::Rc; -#[derive(PartialEq, Clone, JSTraceable)] +#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] pub enum CommonEventHandler { - EventHandler(Rc<EventHandlerNonNull>), - ErrorEventHandler(Rc<OnErrorEventHandlerNonNull>), - BeforeUnloadEventHandler(Rc<OnBeforeUnloadEventHandlerNonNull>), + EventHandler(#[ignore_malloc_size_of = "Rc"] Rc<EventHandlerNonNull>), + + ErrorEventHandler(#[ignore_malloc_size_of = "Rc"] Rc<OnErrorEventHandlerNonNull>), + + BeforeUnloadEventHandler(#[ignore_malloc_size_of = "Rc"] Rc<OnBeforeUnloadEventHandlerNonNull>), } impl CommonEventHandler { @@ -61,14 +71,14 @@ impl CommonEventHandler { } } -#[derive(JSTraceable, Copy, Clone, PartialEq, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub enum ListenerPhase { Capturing, Bubbling, } -/// https://html.spec.whatwg.org/multipage/#internal-raw-uncompiled-handler -#[derive(JSTraceable, Clone, PartialEq)] +/// <https://html.spec.whatwg.org/multipage/#internal-raw-uncompiled-handler> +#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] struct InternalRawUncompiledHandler { source: DOMString, url: ServoUrl, @@ -76,7 +86,7 @@ struct InternalRawUncompiledHandler { } /// A representation of an event handler, either compiled or uncompiled raw source, or null. -#[derive(JSTraceable, PartialEq, Clone)] +#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] enum InlineEventListener { Uncompiled(InternalRawUncompiledHandler), Compiled(CommonEventHandler), @@ -86,9 +96,12 @@ enum InlineEventListener { impl InlineEventListener { /// Get a compiled representation of this event handler, compiling it from its /// raw source if necessary. - /// https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler - fn get_compiled_handler(&mut self, owner: &EventTarget, ty: &Atom) - -> Option<CommonEventHandler> { + /// <https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler> + fn get_compiled_handler( + &mut self, + owner: &EventTarget, + ty: &Atom, + ) -> Option<CommonEventHandler> { match mem::replace(self, InlineEventListener::Null) { InlineEventListener::Null => None, InlineEventListener::Uncompiled(handler) => { @@ -97,37 +110,34 @@ impl InlineEventListener { *self = InlineEventListener::Compiled(compiled.clone()); } result - } + }, InlineEventListener::Compiled(handler) => { *self = InlineEventListener::Compiled(handler.clone()); Some(handler) - } + }, } } } -#[derive(JSTraceable, Clone, PartialEq)] +#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] enum EventListenerType { - Additive(Rc<EventListener>), + Additive(#[ignore_malloc_size_of = "Rc"] Rc<EventListener>), Inline(InlineEventListener), } -impl HeapSizeOf for EventListenerType { - fn heap_size_of_children(&self) -> usize { - // FIXME: Rc<T> isn't HeapSizeOf and we can't ignore it due to #6870 and #6871 - 0 - } -} - impl EventListenerType { - fn get_compiled_listener(&mut self, owner: &EventTarget, ty: &Atom) - -> Option<CompiledEventListener> { + fn get_compiled_listener( + &mut self, + owner: &EventTarget, + ty: &Atom, + ) -> Option<CompiledEventListener> { match self { - &mut EventListenerType::Inline(ref mut inline) => - inline.get_compiled_handler(owner, ty) - .map(CompiledEventListener::Handler), - &mut EventListenerType::Additive(ref listener) => - Some(CompiledEventListener::Listener(listener.clone())), + &mut EventListenerType::Inline(ref mut inline) => inline + .get_compiled_handler(owner, ty) + .map(CompiledEventListener::Handler), + &mut EventListenerType::Additive(ref listener) => { + Some(CompiledEventListener::Listener(listener.clone())) + }, } } } @@ -141,11 +151,29 @@ pub enum CompiledEventListener { impl CompiledEventListener { #[allow(unsafe_code)] + pub fn associated_global(&self) -> DomRoot<GlobalScope> { + let obj = match self { + CompiledEventListener::Listener(listener) => listener.callback(), + CompiledEventListener::Handler(CommonEventHandler::EventHandler(handler)) => { + handler.callback() + }, + CompiledEventListener::Handler(CommonEventHandler::ErrorEventHandler(handler)) => { + handler.callback() + }, + CompiledEventListener::Handler(CommonEventHandler::BeforeUnloadEventHandler( + handler, + )) => handler.callback(), + }; + unsafe { GlobalScope::from_object(obj) } + } + // https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm - pub fn call_or_handle_event<T: DomObject>(&self, - object: &T, - event: &Event, - exception_handle: ExceptionHandling) { + pub fn call_or_handle_event( + &self, + object: &EventTarget, + event: &Event, + exception_handle: ExceptionHandling, + ) { // Step 3 match *self { CompiledEventListener::Listener(ref listener) => { @@ -155,80 +183,103 @@ impl CompiledEventListener { match *handler { CommonEventHandler::ErrorEventHandler(ref handler) => { if let Some(event) = event.downcast::<ErrorEvent>() { - let cx = object.global().get_cx(); - rooted!(in(cx) let error = unsafe { event.Error(cx) }); - let return_value = handler.Call_(object, - EventOrString::String(event.Message()), - Some(event.Filename()), - Some(event.Lineno()), - Some(event.Colno()), - Some(error.handle()), - exception_handle); - // Step 4 - if let Ok(return_value) = return_value { - rooted!(in(cx) let return_value = return_value); - if return_value.handle().is_boolean() && return_value.handle().to_boolean() == true { - event.upcast::<Event>().PreventDefault(); + if object.is::<Window>() || object.is::<WorkerGlobalScope>() { + let cx = object.global().get_cx(); + rooted!(in(*cx) let error = event.Error(cx)); + let return_value = handler.Call_( + object, + EventOrString::String(event.Message()), + Some(event.Filename()), + Some(event.Lineno()), + Some(event.Colno()), + Some(error.handle()), + exception_handle, + ); + // Step 4 + if let Ok(return_value) = return_value { + rooted!(in(*cx) let return_value = return_value); + if return_value.handle().is_boolean() && + return_value.handle().to_boolean() == true + { + event.upcast::<Event>().PreventDefault(); + } } + return; } - return; } - let _ = handler.Call_(object, EventOrString::Event(Root::from_ref(event)), - None, None, None, None, exception_handle); - } + let _ = handler.Call_( + object, + EventOrString::Event(DomRoot::from_ref(event)), + None, + None, + None, + None, + exception_handle, + ); + }, CommonEventHandler::BeforeUnloadEventHandler(ref handler) => { if let Some(event) = event.downcast::<BeforeUnloadEvent>() { - let rv = event.ReturnValue(); - - if let Ok(value) = handler.Call_(object, - event.upcast::<Event>(), - exception_handle) { - match value { - Some(value) => { - if rv.is_empty() { - event.SetReturnValue(value); - } - } - None => { - event.upcast::<Event>().PreventDefault(); + // Step 5 + if let Ok(value) = + handler.Call_(object, event.upcast::<Event>(), exception_handle) + { + let rv = event.ReturnValue(); + if let Some(v) = value { + if rv.is_empty() { + event.SetReturnValue(v); } + event.upcast::<Event>().PreventDefault(); } } + } else { + // Step 5, "Otherwise" clause + let _ = + handler.Call_(object, event.upcast::<Event>(), exception_handle); } - } + }, CommonEventHandler::EventHandler(ref handler) => { if let Ok(value) = handler.Call_(object, event, exception_handle) { let cx = object.global().get_cx(); - rooted!(in(cx) let value = value); + rooted!(in(*cx) let value = value); let value = value.handle(); - //Step 4 - let should_cancel = match event.type_() { - atom!("mouseover") => value.is_boolean() && value.to_boolean() == true, - _ => value.is_boolean() && value.to_boolean() == false - }; + //Step 5 + let should_cancel = value.is_boolean() && value.to_boolean() == false; + if should_cancel { + // FIXME: spec says to set the cancelled flag directly + // here, not just to prevent default; + // can that ever make a difference? event.PreventDefault(); } } - } + }, } - } + }, } } } -#[derive(Clone, DenyPublicFields, HeapSizeOf, JSTraceable, PartialEq)] +// https://dom.spec.whatwg.org/#concept-event-listener +// (as distinct from https://dom.spec.whatwg.org/#callbackdef-eventlistener) +#[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)] /// A listener in a collection of event listeners. struct EventListenerEntry { phase: ListenerPhase, - listener: EventListenerType + listener: EventListenerType, + once: bool, +} + +impl std::cmp::PartialEq for EventListenerEntry { + fn eq(&self, other: &Self) -> bool { + self.phase == other.phase && self.listener == other.listener + } } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] /// A mix of potentially uncompiled and compiled event listeners of the same type. struct EventListeners(Vec<EventListenerEntry>); @@ -247,7 +298,11 @@ impl DerefMut for EventListeners { impl EventListeners { // https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler - fn get_inline_listener(&mut self, owner: &EventTarget, ty: &Atom) -> Option<CommonEventHandler> { + fn get_inline_listener( + &mut self, + owner: &EventTarget, + ty: &Atom, + ) -> Option<CommonEventHandler> { for entry in &mut self.0 { if let EventListenerType::Inline(ref mut inline) = entry.listener { // Step 1.1-1.8 and Step 2 @@ -260,234 +315,323 @@ impl EventListeners { } // https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler - fn get_listeners(&mut self, phase: Option<ListenerPhase>, owner: &EventTarget, ty: &Atom) - -> Vec<CompiledEventListener> { - self.0.iter_mut().filter_map(|entry| { - if phase.is_none() || Some(entry.phase) == phase { - // Step 1.1-1.8, 2 - entry.listener.get_compiled_listener(owner, ty) - } else { - None - } - }).collect() + fn get_listeners( + &mut self, + phase: Option<ListenerPhase>, + owner: &EventTarget, + ty: &Atom, + ) -> Vec<CompiledEventListener> { + self.0 + .iter_mut() + .filter_map(|entry| { + if phase.is_none() || Some(entry.phase) == phase { + // Step 1.1-1.8, 2 + entry.listener.get_compiled_listener(owner, ty) + } else { + None + } + }) + .collect() + } + + fn has_listeners(&self) -> bool { + // TODO: add, and take into account, a 'removed' field? + // https://dom.spec.whatwg.org/#event-listener-removed + self.0.len() > 0 } } #[dom_struct] pub struct EventTarget { reflector_: Reflector, - handlers: DOMRefCell<HashMap<Atom, EventListeners, BuildHasherDefault<FnvHasher>>>, + handlers: DomRefCell<HashMap<Atom, EventListeners, BuildHasherDefault<FnvHasher>>>, } impl EventTarget { pub fn new_inherited() -> EventTarget { EventTarget { reflector_: Reflector::new(), - handlers: DOMRefCell::new(Default::default()), + handlers: DomRefCell::new(Default::default()), } } - pub fn get_listeners_for(&self, - type_: &Atom, - specific_phase: Option<ListenerPhase>) - -> Vec<CompiledEventListener> { - self.handlers.borrow_mut().get_mut(type_).map_or(vec![], |listeners| { - listeners.get_listeners(specific_phase, self, type_) - }) + fn new(global: &GlobalScope) -> DomRoot<EventTarget> { + reflect_dom_object(Box::new(EventTarget::new_inherited()), global) } - pub fn dispatch_event_with_target(&self, - target: &EventTarget, - event: &Event) -> EventStatus { - event.dispatch(self, Some(target)) + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<EventTarget>> { + Ok(EventTarget::new(global)) + } + + pub fn has_listeners_for(&self, type_: &Atom) -> bool { + match self.handlers.borrow().get(type_) { + Some(listeners) => listeners.has_listeners(), + None => false, + } + } + + pub fn get_listeners_for( + &self, + type_: &Atom, + specific_phase: Option<ListenerPhase>, + ) -> Vec<CompiledEventListener> { + self.handlers + .borrow_mut() + .get_mut(type_) + .map_or(vec![], |listeners| { + listeners.get_listeners(specific_phase, self, type_) + }) } pub fn dispatch_event(&self, event: &Event) -> EventStatus { - event.dispatch(self, None) + if let Some(window) = self.global().downcast::<Window>() { + if window.has_document() { + assert!(window.Document().can_invoke_script()); + } + }; + event.dispatch(self, false) } pub fn remove_all_listeners(&self) { *self.handlers.borrow_mut() = Default::default(); } - /// https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handlers-11 - fn set_inline_event_listener(&self, - ty: Atom, - listener: Option<InlineEventListener>) { + /// <https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handlers-11> + fn set_inline_event_listener(&self, ty: Atom, listener: Option<InlineEventListener>) { let mut handlers = self.handlers.borrow_mut(); let entries = match handlers.entry(ty) { Occupied(entry) => entry.into_mut(), - Vacant(entry) => entry.insert(EventListeners(vec!())), + Vacant(entry) => entry.insert(EventListeners(vec![])), }; - let idx = entries.iter().position(|ref entry| { - match entry.listener { - EventListenerType::Inline(_) => true, - _ => false, - } + let idx = entries.iter().position(|ref entry| match entry.listener { + EventListenerType::Inline(_) => true, + _ => false, }); match idx { - Some(idx) => { - entries[idx].listener = - EventListenerType::Inline(listener.unwrap_or(InlineEventListener::Null)); - } + Some(idx) => match listener { + // Replace if there's something to replace with, + // but remove entirely if there isn't. + Some(listener) => { + entries[idx].listener = EventListenerType::Inline(listener); + }, + None => { + entries.remove(idx); + }, + }, None => { if let Some(listener) = listener { entries.push(EventListenerEntry { phase: ListenerPhase::Bubbling, listener: EventListenerType::Inline(listener), + once: false, }); } - } + }, + } + } + + pub fn remove_listener_if_once(&self, ty: &Atom, listener: &Rc<EventListener>) { + let mut handlers = self.handlers.borrow_mut(); + + let listener = EventListenerType::Additive(listener.clone()); + for entries in handlers.get_mut(ty) { + entries.drain_filter(|e| e.listener == listener && e.once); } } fn get_inline_event_listener(&self, ty: &Atom) -> Option<CommonEventHandler> { let mut handlers = self.handlers.borrow_mut(); - handlers.get_mut(ty).and_then(|entry| entry.get_inline_listener(self, ty)) + handlers + .get_mut(ty) + .and_then(|entry| entry.get_inline_listener(self, ty)) } /// Store the raw uncompiled event handler for on-demand compilation later. - /// https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handler-content-attributes-3 - pub fn set_event_handler_uncompiled(&self, - url: ServoUrl, - line: usize, - ty: &str, - source: DOMString) { + /// <https://html.spec.whatwg.org/multipage/#event-handler-attributes:event-handler-content-attributes-3> + pub fn set_event_handler_uncompiled( + &self, + url: ServoUrl, + line: usize, + ty: &str, + source: DOMString, + ) { let handler = InternalRawUncompiledHandler { source: source, line: line, url: url, }; - self.set_inline_event_listener(Atom::from(ty), - Some(InlineEventListener::Uncompiled(handler))); + self.set_inline_event_listener( + Atom::from(ty), + Some(InlineEventListener::Uncompiled(handler)), + ); } // https://html.spec.whatwg.org/multipage/#getting-the-current-value-of-the-event-handler + // step 3 #[allow(unsafe_code)] - fn get_compiled_event_handler(&self, - handler: InternalRawUncompiledHandler, - ty: &Atom) - -> Option<CommonEventHandler> { - // Step 1.1 + fn get_compiled_event_handler( + &self, + handler: InternalRawUncompiledHandler, + ty: &Atom, + ) -> Option<CommonEventHandler> { + // Step 3.1 let element = self.downcast::<Element>(); let document = match element { Some(element) => document_from_node(element), None => self.downcast::<Window>().unwrap().Document(), }; - // Step 1.2 + // Step 3.2 if !document.is_scripting_enabled() { return None; } - // Step 1.3 + // Step 3.3 let body: Vec<u16> = handler.source.encode_utf16().collect(); - // TODO step 1.5 (form owner) + // Step 3.4 is handler.line + + // Step 3.5 + let form_owner = element + .and_then(|e| e.as_maybe_form_control()) + .and_then(|f| f.form_owner()); - // Step 1.6 + // Step 3.6 TODO: settings objects not implemented + + // Step 3.7 is written as though we call the parser separately + // from the compiler; since we just call CompileFunction with + // source text, we handle parse errors later + + // Step 3.8 TODO: settings objects not implemented let window = document.window(); + let _ac = enter_realm(&*window); + + // Step 3.9 - let url_serialized = CString::new(handler.url.to_string()).unwrap(); - let name = CString::new(&**ty).unwrap(); + let name = CString::new(format!("on{}", &**ty)).unwrap(); - static mut ARG_NAMES: [*const c_char; 1] = [b"event\0" as *const u8 as *const c_char]; - static mut ERROR_ARG_NAMES: [*const c_char; 5] = [b"event\0" as *const u8 as *const c_char, - b"source\0" as *const u8 as *const c_char, - b"lineno\0" as *const u8 as *const c_char, - b"colno\0" as *const u8 as *const c_char, - b"error\0" as *const u8 as *const c_char]; - // step 10 + // Step 3.9, subsection ParameterList + const ARG_NAMES: &[*const c_char] = &[b"event\0" as *const u8 as *const c_char]; + const ERROR_ARG_NAMES: &[*const c_char] = &[ + b"event\0" as *const u8 as *const c_char, + b"source\0" as *const u8 as *const c_char, + b"lineno\0" as *const u8 as *const c_char, + b"colno\0" as *const u8 as *const c_char, + b"error\0" as *const u8 as *const c_char, + ]; let is_error = ty == &atom!("error") && self.is::<Window>(); - let args = unsafe { - if is_error { - &ERROR_ARG_NAMES[..] - } else { - &ARG_NAMES[..] - } - }; + let args = if is_error { ERROR_ARG_NAMES } else { ARG_NAMES }; let cx = window.get_cx(); - let options = CompileOptionsWrapper::new(cx, url_serialized.as_ptr(), handler.line as u32); - // TODO step 1.10.1-3 (document, form owner, element in scope chain) - - let scopechain = AutoObjectVectorWrapper::new(cx); - - let _ac = JSAutoCompartment::new(cx, window.reflector().get_jsobject().get()); - rooted!(in(cx) let mut handler = ptr::null_mut()); - let rv = unsafe { - CompileFunction(cx, - scopechain.ptr, - options.ptr, - name.as_ptr(), - args.len() as u32, - args.as_ptr(), - body.as_ptr(), - body.len() as size_t, - handler.handle_mut()) + let options = unsafe { + CompileOptionsWrapper::new(*cx, &handler.url.to_string(), handler.line as u32) }; - if !rv || handler.get().is_null() { - // Step 1.8.2 + + // Step 3.9, subsection Scope steps 1-6 + let scopechain = RootedObjectVectorWrapper::new(*cx); + + if let Some(element) = element { + scopechain.append(document.reflector().get_jsobject().get()); + if let Some(form_owner) = form_owner { + scopechain.append(form_owner.reflector().get_jsobject().get()); + } + scopechain.append(element.reflector().get_jsobject().get()); + } + + rooted!(in(*cx) let mut handler = unsafe { + CompileFunction( + *cx, + scopechain.handle(), + options.ptr, + name.as_ptr(), + args.len() as u32, + args.as_ptr(), + &mut transform_u16_to_source_text(&body), + ) + }); + if handler.get().is_null() { + // Step 3.7 unsafe { - let _ac = JSAutoCompartment::new(cx, self.reflector().get_jsobject().get()); + let ar = enter_realm(&*self); // FIXME(#13152): dispatch error event. - report_pending_exception(cx, false); + report_pending_exception(*cx, false, InRealm::Entered(&ar)); } - // Step 1.8.1 / 1.8.3 return None; } - // TODO step 1.11-13 + // Step 3.10 happens when we drop _ac + + // TODO Step 3.11 + + // Step 3.12 let funobj = unsafe { JS_GetFunctionObject(handler.get()) }; assert!(!funobj.is_null()); // Step 1.14 if is_error { - Some(CommonEventHandler::ErrorEventHandler(OnErrorEventHandlerNonNull::new(cx, funobj))) + Some(CommonEventHandler::ErrorEventHandler(unsafe { + OnErrorEventHandlerNonNull::new(cx, funobj) + })) } else { if ty == &atom!("beforeunload") { - Some(CommonEventHandler::BeforeUnloadEventHandler( - OnBeforeUnloadEventHandlerNonNull::new(cx, funobj))) + Some(CommonEventHandler::BeforeUnloadEventHandler(unsafe { + OnBeforeUnloadEventHandlerNonNull::new(cx, funobj) + })) } else { - Some(CommonEventHandler::EventHandler(EventHandlerNonNull::new(cx, funobj))) + Some(CommonEventHandler::EventHandler(unsafe { + EventHandlerNonNull::new(cx, funobj) + })) } } } - pub fn set_event_handler_common<T: CallbackContainer>( - &self, ty: &str, listener: Option<Rc<T>>) + #[allow(unsafe_code)] + pub fn set_event_handler_common<T: CallbackContainer>(&self, ty: &str, listener: Option<Rc<T>>) + where + T: CallbackContainer, { let cx = self.global().get_cx(); - let event_listener = listener.map(|listener| - InlineEventListener::Compiled( - CommonEventHandler::EventHandler( - EventHandlerNonNull::new(cx, listener.callback())))); + let event_listener = listener.map(|listener| { + InlineEventListener::Compiled(CommonEventHandler::EventHandler(unsafe { + EventHandlerNonNull::new(cx, listener.callback()) + })) + }); self.set_inline_event_listener(Atom::from(ty), event_listener); } - pub fn set_error_event_handler<T: CallbackContainer>( - &self, ty: &str, listener: Option<Rc<T>>) + #[allow(unsafe_code)] + pub fn set_error_event_handler<T: CallbackContainer>(&self, ty: &str, listener: Option<Rc<T>>) + where + T: CallbackContainer, { let cx = self.global().get_cx(); - let event_listener = listener.map(|listener| - InlineEventListener::Compiled( - CommonEventHandler::ErrorEventHandler( - OnErrorEventHandlerNonNull::new(cx, listener.callback())))); + let event_listener = listener.map(|listener| { + InlineEventListener::Compiled(CommonEventHandler::ErrorEventHandler(unsafe { + OnErrorEventHandlerNonNull::new(cx, listener.callback()) + })) + }); self.set_inline_event_listener(Atom::from(ty), event_listener); } - pub fn set_beforeunload_event_handler<T: CallbackContainer>(&self, ty: &str, - listener: Option<Rc<T>>) { + #[allow(unsafe_code)] + pub fn set_beforeunload_event_handler<T: CallbackContainer>( + &self, + ty: &str, + listener: Option<Rc<T>>, + ) where + T: CallbackContainer, + { let cx = self.global().get_cx(); - let event_listener = listener.map(|listener| - InlineEventListener::Compiled( - CommonEventHandler::BeforeUnloadEventHandler( - OnBeforeUnloadEventHandlerNonNull::new(cx, listener.callback()))) - ); + let event_listener = listener.map(|listener| { + InlineEventListener::Compiled(CommonEventHandler::BeforeUnloadEventHandler(unsafe { + OnBeforeUnloadEventHandlerNonNull::new(cx, listener.callback()) + })) + }); self.set_inline_event_listener(Atom::from(ty), event_listener); } @@ -496,8 +640,9 @@ impl EventTarget { let cx = self.global().get_cx(); let listener = self.get_inline_event_listener(&Atom::from(ty)); unsafe { - listener.map(|listener| - CallbackContainer::new(cx, listener.parent().callback_holder().get())) + listener.map(|listener| { + CallbackContainer::new(cx, listener.parent().callback_holder().get()) + }) } } @@ -506,51 +651,51 @@ impl EventTarget { } // https://dom.spec.whatwg.org/#concept-event-fire - pub fn fire_event(&self, name: Atom) -> Root<Event> { - self.fire_event_with_params(name, - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable) + pub fn fire_event(&self, name: Atom) -> DomRoot<Event> { + self.fire_event_with_params( + name, + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ) } // https://dom.spec.whatwg.org/#concept-event-fire - pub fn fire_bubbling_event(&self, name: Atom) -> Root<Event> { - self.fire_event_with_params(name, - EventBubbles::Bubbles, - EventCancelable::NotCancelable) + pub fn fire_bubbling_event(&self, name: Atom) -> DomRoot<Event> { + self.fire_event_with_params(name, EventBubbles::Bubbles, EventCancelable::NotCancelable) } // https://dom.spec.whatwg.org/#concept-event-fire - pub fn fire_cancelable_event(&self, name: Atom) -> Root<Event> { - self.fire_event_with_params(name, - EventBubbles::DoesNotBubble, - EventCancelable::Cancelable) + pub fn fire_cancelable_event(&self, name: Atom) -> DomRoot<Event> { + self.fire_event_with_params( + name, + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable, + ) } // https://dom.spec.whatwg.org/#concept-event-fire - pub fn fire_bubbling_cancelable_event(&self, name: Atom) -> Root<Event> { - self.fire_event_with_params(name, - EventBubbles::Bubbles, - EventCancelable::Cancelable) + pub fn fire_bubbling_cancelable_event(&self, name: Atom) -> DomRoot<Event> { + self.fire_event_with_params(name, EventBubbles::Bubbles, EventCancelable::Cancelable) } // https://dom.spec.whatwg.org/#concept-event-fire - pub fn fire_event_with_params(&self, - name: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable) - -> Root<Event> { + pub fn fire_event_with_params( + &self, + name: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + ) -> DomRoot<Event> { let event = Event::new(&self.global(), name, bubbles, cancelable); event.fire(self); event } -} - -impl EventTargetMethods for EventTarget { // https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener - fn AddEventListener(&self, - ty: DOMString, - listener: Option<Rc<EventListener>>, - capture: bool) { + pub fn add_event_listener( + &self, + ty: DOMString, + listener: Option<Rc<EventListener>>, + options: AddEventListenerOptions, + ) { let listener = match listener { Some(l) => l, None => return, @@ -558,13 +703,18 @@ impl EventTargetMethods for EventTarget { let mut handlers = self.handlers.borrow_mut(); let entry = match handlers.entry(Atom::from(ty)) { Occupied(entry) => entry.into_mut(), - Vacant(entry) => entry.insert(EventListeners(vec!())), + Vacant(entry) => entry.insert(EventListeners(vec![])), }; - let phase = if capture { ListenerPhase::Capturing } else { ListenerPhase::Bubbling }; + let phase = if options.parent.capture { + ListenerPhase::Capturing + } else { + ListenerPhase::Bubbling + }; let new_entry = EventListenerEntry { phase: phase, - listener: EventListenerType::Additive(listener) + listener: EventListenerType::Additive(listener), + once: options.once, }; if !entry.contains(&new_entry) { entry.push(new_entry); @@ -572,10 +722,12 @@ impl EventTargetMethods for EventTarget { } // https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener - fn RemoveEventListener(&self, - ty: DOMString, - listener: Option<Rc<EventListener>>, - capture: bool) { + pub fn remove_event_listener( + &self, + ty: DOMString, + listener: Option<Rc<EventListener>>, + options: EventListenerOptions, + ) { let ref listener = match listener { Some(l) => l, None => return, @@ -583,16 +735,43 @@ impl EventTargetMethods for EventTarget { let mut handlers = self.handlers.borrow_mut(); let entry = handlers.get_mut(&Atom::from(ty)); for entry in entry { - let phase = if capture { ListenerPhase::Capturing } else { ListenerPhase::Bubbling }; + let phase = if options.capture { + ListenerPhase::Capturing + } else { + ListenerPhase::Bubbling + }; let old_entry = EventListenerEntry { phase: phase, - listener: EventListenerType::Additive(listener.clone()) + listener: EventListenerType::Additive(listener.clone()), + once: false, }; if let Some(position) = entry.iter().position(|e| *e == old_entry) { entry.remove(position); } } } +} + +impl EventTargetMethods for EventTarget { + // https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener + fn AddEventListener( + &self, + ty: DOMString, + listener: Option<Rc<EventListener>>, + options: AddEventListenerOptionsOrBoolean, + ) { + self.add_event_listener(ty, listener, options.into()) + } + + // https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener + fn RemoveEventListener( + &self, + ty: DOMString, + listener: Option<Rc<EventListener>>, + options: EventListenerOptionsOrBoolean, + ) { + self.remove_event_listener(ty, listener, options.into()) + } // https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent fn DispatchEvent(&self, event: &Event) -> Fallible<bool> { @@ -602,13 +781,34 @@ impl EventTargetMethods for EventTarget { event.set_trusted(false); Ok(match self.dispatch_event(event) { EventStatus::Canceled => false, - EventStatus::NotCanceled => true + EventStatus::NotCanceled => true, }) } } impl VirtualMethods for EventTarget { - fn super_type(&self) -> Option<&VirtualMethods> { + fn super_type(&self) -> Option<&dyn VirtualMethods> { None } } + +impl From<AddEventListenerOptionsOrBoolean> for AddEventListenerOptions { + fn from(options: AddEventListenerOptionsOrBoolean) -> Self { + match options { + AddEventListenerOptionsOrBoolean::AddEventListenerOptions(options) => options, + AddEventListenerOptionsOrBoolean::Boolean(capture) => Self { + parent: EventListenerOptions { capture }, + once: false, + }, + } + } +} + +impl From<EventListenerOptionsOrBoolean> for EventListenerOptions { + fn from(options: EventListenerOptionsOrBoolean) -> Self { + match options { + EventListenerOptionsOrBoolean::EventListenerOptions(options) => options, + EventListenerOptionsOrBoolean::Boolean(capture) => Self { capture }, + } + } +} diff --git a/components/script/dom/extendableevent.rs b/components/script/dom/extendableevent.rs index 8cc88706fb1..deb2dce4dfa 100644 --- a/components/script/dom/extendableevent.rs +++ b/components/script/dom/extendableevent.rs @@ -1,40 +1,43 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::ExtendableEventBinding; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::serviceworkerglobalscope::ServiceWorkerGlobalScope; +use crate::dom::bindings::codegen::Bindings::EventBinding::{self, EventMethods}; +use crate::dom::bindings::codegen::Bindings::ExtendableEventBinding; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{HandleValue, JSContext}; +use js::rust::HandleValue; use servo_atoms::Atom; // https://w3c.github.io/ServiceWorker/#extendable-event #[dom_struct] pub struct ExtendableEvent { event: Event, - extensions_allowed: bool + extensions_allowed: bool, } +#[allow(non_snake_case)] impl ExtendableEvent { pub fn new_inherited() -> ExtendableEvent { ExtendableEvent { event: Event::new_inherited(), - extensions_allowed: true + extensions_allowed: true, } } - pub fn new(worker: &ServiceWorkerGlobalScope, - type_: Atom, - bubbles: bool, - cancelable: bool) - -> Root<ExtendableEvent> { - let ev = reflect_dom_object(box ExtendableEvent::new_inherited(), worker, ExtendableEventBinding::Wrap); + pub fn new( + worker: &ServiceWorkerGlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + ) -> DomRoot<ExtendableEvent> { + let ev = reflect_dom_object(Box::new(ExtendableEvent::new_inherited()), worker); { let event = ev.upcast::<Event>(); event.init_event(type_, bubbles, cancelable); @@ -42,17 +45,21 @@ impl ExtendableEvent { ev } - pub fn Constructor(worker: &ServiceWorkerGlobalScope, - type_: DOMString, - init: &ExtendableEventBinding::ExtendableEventInit) -> Fallible<Root<ExtendableEvent>> { - Ok(ExtendableEvent::new(worker, - Atom::from(type_), - init.parent.bubbles, - init.parent.cancelable)) + pub fn Constructor( + worker: &ServiceWorkerGlobalScope, + type_: DOMString, + init: &ExtendableEventBinding::ExtendableEventInit, + ) -> Fallible<DomRoot<ExtendableEvent>> { + Ok(ExtendableEvent::new( + worker, + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + )) } // https://w3c.github.io/ServiceWorker/#wait-until-method - pub fn WaitUntil(&self, _cx: *mut JSContext, _val: HandleValue) -> ErrorResult { + pub fn WaitUntil(&self, _cx: JSContext, _val: HandleValue) -> ErrorResult { // Step 1 if !self.extensions_allowed { return Err(Error::InvalidState); @@ -67,3 +74,11 @@ impl ExtendableEvent { self.event.IsTrusted() } } + +impl Default for ExtendableEventBinding::ExtendableEventInit { + fn default() -> ExtendableEventBinding::ExtendableEventInit { + ExtendableEventBinding::ExtendableEventInit { + parent: EventBinding::EventInit::default(), + } + } +} diff --git a/components/script/dom/extendablemessageevent.rs b/components/script/dom/extendablemessageevent.rs index 49c461083f2..19bca33c6ca 100644 --- a/components/script/dom/extendablemessageevent.rs +++ b/components/script/dom/extendablemessageevent.rs @@ -1,83 +1,153 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::ExtendableMessageEventBinding; -use dom::bindings::codegen::Bindings::ExtendableMessageEventBinding::ExtendableMessageEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::bindings::trace::RootedTraceableBox; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::extendableevent::ExtendableEvent; -use dom::globalscope::GlobalScope; -use dom::serviceworkerglobalscope::ServiceWorkerGlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::ExtendableMessageEventBinding; +use crate::dom::bindings::codegen::Bindings::ExtendableMessageEventBinding::ExtendableMessageEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::bindings::utils::to_frozen_array; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::extendableevent::ExtendableEvent; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageport::MessagePort; +use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{HandleValue, Heap, JSContext}; +use js::jsapi::Heap; use js::jsval::JSVal; +use js::rust::HandleValue; use servo_atoms::Atom; #[dom_struct] +#[allow(non_snake_case)] pub struct ExtendableMessageEvent { + /// https://w3c.github.io/ServiceWorker/#extendableevent event: ExtendableEvent, + /// https://w3c.github.io/ServiceWorker/#dom-extendablemessageevent-data + #[ignore_malloc_size_of = "mozjs"] data: Heap<JSVal>, + /// <https://w3c.github.io/ServiceWorker/#extendablemessage-event-origin> origin: DOMString, + /// https://w3c.github.io/ServiceWorker/#dom-extendablemessageevent-lasteventid lastEventId: DOMString, + /// https://w3c.github.io/ServiceWorker/#dom-extendablemessageevent-ports + ports: Vec<Dom<MessagePort>>, + #[ignore_malloc_size_of = "mozjs"] + frozen_ports: DomRefCell<Option<Heap<JSVal>>>, } +#[allow(non_snake_case)] impl ExtendableMessageEvent { - pub fn new(global: &GlobalScope, type_: Atom, - bubbles: bool, cancelable: bool, - data: HandleValue, origin: DOMString, lastEventId: DOMString) - -> Root<ExtendableMessageEvent> { - let ev = box ExtendableMessageEvent { + pub fn new_inherited( + origin: DOMString, + lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, + ) -> ExtendableMessageEvent { + ExtendableMessageEvent { event: ExtendableEvent::new_inherited(), - data: Heap::new(data.get()), + data: Heap::default(), origin: origin, lastEventId: lastEventId, - }; - let ev = reflect_dom_object(ev, global, ExtendableMessageEventBinding::Wrap); + ports: ports + .into_iter() + .map(|port| Dom::from_ref(&*port)) + .collect(), + frozen_ports: DomRefCell::new(None), + } + } + + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + data: HandleValue, + origin: DOMString, + lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, + ) -> DomRoot<ExtendableMessageEvent> { + let ev = Box::new(ExtendableMessageEvent::new_inherited( + origin, + lastEventId, + ports, + )); + let ev = reflect_dom_object(ev, global); { let event = ev.upcast::<Event>(); event.init_event(type_, bubbles, cancelable); } + ev.data.set(data.get()); + ev } - pub fn Constructor(worker: &ServiceWorkerGlobalScope, - type_: DOMString, - init: RootedTraceableBox<ExtendableMessageEventBinding::ExtendableMessageEventInit>) - -> Fallible<Root<ExtendableMessageEvent>> { + pub fn Constructor( + worker: &ServiceWorkerGlobalScope, + type_: DOMString, + init: RootedTraceableBox<ExtendableMessageEventBinding::ExtendableMessageEventInit>, + ) -> Fallible<DomRoot<ExtendableMessageEvent>> { let global = worker.upcast::<GlobalScope>(); - let ev = ExtendableMessageEvent::new(global, - Atom::from(type_), - init.parent.parent.bubbles, - init.parent.parent.cancelable, - init.data.handle(), - init.origin.clone().unwrap(), - init.lastEventId.clone().unwrap()); + let ev = ExtendableMessageEvent::new( + global, + Atom::from(type_), + init.parent.parent.bubbles, + init.parent.parent.cancelable, + init.data.handle(), + init.origin.clone(), + init.lastEventId.clone(), + vec![], + ); Ok(ev) } } +#[allow(non_snake_case)] impl ExtendableMessageEvent { - pub fn dispatch_jsval(target: &EventTarget, - scope: &GlobalScope, - message: HandleValue) { + pub fn dispatch_jsval( + target: &EventTarget, + scope: &GlobalScope, + message: HandleValue, + ports: Vec<DomRoot<MessagePort>>, + ) { let Extendablemessageevent = ExtendableMessageEvent::new( - scope, atom!("message"), false, false, message, - DOMString::new(), DOMString::new()); + scope, + atom!("message"), + false, + false, + message, + DOMString::new(), + DOMString::new(), + ports, + ); Extendablemessageevent.upcast::<Event>().fire(target); } + + pub fn dispatch_error(target: &EventTarget, scope: &GlobalScope) { + let init = ExtendableMessageEventBinding::ExtendableMessageEventInit::empty(); + let ExtendableMsgEvent = ExtendableMessageEvent::new( + scope, + atom!("messageerror"), + init.parent.parent.bubbles, + init.parent.parent.cancelable, + init.data.handle(), + init.origin.clone(), + init.lastEventId.clone(), + init.ports.clone(), + ); + ExtendableMsgEvent.upcast::<Event>().fire(target); + } } impl ExtendableMessageEventMethods for ExtendableMessageEvent { - #[allow(unsafe_code)] // https://w3c.github.io/ServiceWorker/#extendablemessage-event-data-attribute - unsafe fn Data(&self, _cx: *mut JSContext) -> JSVal { + fn Data(&self, _cx: JSContext) -> JSVal { self.data.get() } @@ -95,4 +165,28 @@ impl ExtendableMessageEventMethods for ExtendableMessageEvent { fn IsTrusted(&self) -> bool { self.event.IsTrusted() } + + /// https://w3c.github.io/ServiceWorker/#extendablemessage-event-ports + fn Ports(&self, cx: JSContext) -> JSVal { + if let Some(ports) = &*self.frozen_ports.borrow() { + return ports.get(); + } + + let ports: Vec<DomRoot<MessagePort>> = self + .ports + .iter() + .map(|port| DomRoot::from_ref(&**port)) + .collect(); + let frozen_ports = to_frozen_array(ports.as_slice(), cx); + + // Safety: need to create the Heap value in its final memory location before setting it. + *self.frozen_ports.borrow_mut() = Some(Heap::default()); + self.frozen_ports + .borrow() + .as_ref() + .unwrap() + .set(frozen_ports); + + frozen_ports + } } diff --git a/components/script/dom/fakexrdevice.rs b/components/script/dom/fakexrdevice.rs new file mode 100644 index 00000000000..811a7e01b42 --- /dev/null +++ b/components/script/dom/fakexrdevice.rs @@ -0,0 +1,331 @@ +/* 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 crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit; +use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::{ + FakeXRDeviceMethods, FakeXRRegionType, FakeXRRigidTransformInit, FakeXRViewInit, + FakeXRWorldInit, +}; +use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::FakeXRInputSourceInit; +use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{ + XRHandedness, XRTargetRayMode, +}; +use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRVisibilityState; +use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::refcounted::TrustedPromise; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::fakexrinputcontroller::FakeXRInputController; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use euclid::{Point2D, Rect, Size2D}; +use euclid::{Point3D, RigidTransform3D, Rotation3D, Transform3D, Vector3D}; +use ipc_channel::ipc::IpcSender; +use ipc_channel::router::ROUTER; +use profile_traits::ipc; +use std::cell::Cell; +use std::rc::Rc; +use webxr_api::{ + EntityType, Handedness, InputId, InputSource, MockDeviceMsg, MockInputInit, MockRegion, + MockViewInit, MockViewsInit, MockWorld, TargetRayMode, Triangle, Visibility, +}; + +#[dom_struct] +pub struct FakeXRDevice { + reflector: Reflector, + #[ignore_malloc_size_of = "defined in ipc-channel"] + sender: IpcSender<MockDeviceMsg>, + #[ignore_malloc_size_of = "defined in webxr-api"] + next_input_id: Cell<InputId>, +} + +impl FakeXRDevice { + pub fn new_inherited(sender: IpcSender<MockDeviceMsg>) -> FakeXRDevice { + FakeXRDevice { + reflector: Reflector::new(), + sender, + next_input_id: Cell::new(InputId(0)), + } + } + + pub fn new(global: &GlobalScope, sender: IpcSender<MockDeviceMsg>) -> DomRoot<FakeXRDevice> { + reflect_dom_object(Box::new(FakeXRDevice::new_inherited(sender)), global) + } + + pub fn disconnect(&self, sender: IpcSender<()>) { + let _ = self.sender.send(MockDeviceMsg::Disconnect(sender)); + } +} + +pub fn view<Eye>(view: &FakeXRViewInit) -> Fallible<MockViewInit<Eye>> { + if view.projectionMatrix.len() != 16 || view.viewOffset.position.len() != 3 { + return Err(Error::Type("Incorrectly sized array".into())); + } + + let mut proj = [0.; 16]; + let v: Vec<_> = view.projectionMatrix.iter().map(|x| **x).collect(); + proj.copy_from_slice(&v); + let projection = Transform3D::from_array(proj); + + // spec defines offsets as origins, but mock API expects the inverse transform + let transform = get_origin(&view.viewOffset)?.inverse(); + + let size = Size2D::new(view.resolution.width, view.resolution.height); + let origin = match view.eye { + XREye::Right => Point2D::new(size.width, 0), + _ => Point2D::new(0, 0), + }; + let viewport = Rect::new(origin, size); + + let fov = if let Some(ref fov) = view.fieldOfView { + Some(( + fov.leftDegrees.to_radians(), + fov.rightDegrees.to_radians(), + fov.upDegrees.to_radians(), + fov.downDegrees.to_radians(), + )) + } else { + None + }; + + Ok(MockViewInit { + projection, + transform, + viewport, + fov, + }) +} + +pub fn get_views(views: &[FakeXRViewInit]) -> Fallible<MockViewsInit> { + match views.len() { + 1 => Ok(MockViewsInit::Mono(view(&views[0])?)), + 2 => { + let (left, right) = match (views[0].eye, views[1].eye) { + (XREye::Left, XREye::Right) => (&views[0], &views[1]), + (XREye::Right, XREye::Left) => (&views[1], &views[0]), + _ => return Err(Error::NotSupported), + }; + Ok(MockViewsInit::Stereo(view(left)?, view(right)?)) + }, + _ => Err(Error::NotSupported), + } +} + +pub fn get_origin<T, U>( + origin: &FakeXRRigidTransformInit, +) -> Fallible<RigidTransform3D<f32, T, U>> { + if origin.position.len() != 3 || origin.orientation.len() != 4 { + return Err(Error::Type("Incorrectly sized array".into())); + } + let p = Vector3D::new( + *origin.position[0], + *origin.position[1], + *origin.position[2], + ); + let o = Rotation3D::unit_quaternion( + *origin.orientation[0], + *origin.orientation[1], + *origin.orientation[2], + *origin.orientation[3], + ); + + Ok(RigidTransform3D::new(o, p)) +} + +pub fn get_point<T>(pt: &DOMPointInit) -> Point3D<f32, T> { + Point3D::new(pt.x / pt.w, pt.y / pt.w, pt.z / pt.w).cast() +} + +pub fn get_world(world: &FakeXRWorldInit) -> Fallible<MockWorld> { + let regions = world + .hitTestRegions + .iter() + .map(|region| { + let ty = region.type_.into(); + let faces = region + .faces + .iter() + .map(|face| { + if face.vertices.len() != 3 { + return Err(Error::Type( + "Incorrectly sized array for triangle list".into(), + )); + } + + Ok(Triangle { + first: get_point(&face.vertices[0]), + second: get_point(&face.vertices[1]), + third: get_point(&face.vertices[2]), + }) + }) + .collect::<Fallible<Vec<_>>>()?; + Ok(MockRegion { faces, ty }) + }) + .collect::<Fallible<Vec<_>>>()?; + + Ok(MockWorld { regions }) +} + +impl From<FakeXRRegionType> for EntityType { + fn from(x: FakeXRRegionType) -> Self { + match x { + FakeXRRegionType::Point => EntityType::Point, + FakeXRRegionType::Plane => EntityType::Plane, + FakeXRRegionType::Mesh => EntityType::Mesh, + } + } +} + +impl FakeXRDeviceMethods for FakeXRDevice { + /// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md + fn SetViews(&self, views: Vec<FakeXRViewInit>) -> Fallible<()> { + let _ = self + .sender + .send(MockDeviceMsg::SetViews(get_views(&views)?)); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setviewerorigin + fn SetViewerOrigin( + &self, + origin: &FakeXRRigidTransformInit, + _emulated_position: bool, + ) -> Fallible<()> { + let _ = self + .sender + .send(MockDeviceMsg::SetViewerOrigin(Some(get_origin(origin)?))); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearviewerorigin + fn ClearViewerOrigin(&self) { + let _ = self.sender.send(MockDeviceMsg::SetViewerOrigin(None)); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearfloororigin + fn ClearFloorOrigin(&self) { + let _ = self.sender.send(MockDeviceMsg::SetFloorOrigin(None)); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setfloororigin + fn SetFloorOrigin(&self, origin: &FakeXRRigidTransformInit) -> Fallible<()> { + let _ = self + .sender + .send(MockDeviceMsg::SetFloorOrigin(Some(get_origin(origin)?))); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearworld + fn ClearWorld(&self) { + let _ = self.sender.send(MockDeviceMsg::ClearWorld); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setworld + fn SetWorld(&self, world: &FakeXRWorldInit) -> Fallible<()> { + let _ = self.sender.send(MockDeviceMsg::SetWorld(get_world(world)?)); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulatevisibilitychange + fn SimulateVisibilityChange(&self, v: XRVisibilityState) { + let v = match v { + XRVisibilityState::Visible => Visibility::Visible, + XRVisibilityState::Visible_blurred => Visibility::VisibleBlurred, + XRVisibilityState::Hidden => Visibility::Hidden, + }; + let _ = self.sender.send(MockDeviceMsg::VisibilityChange(v)); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulateinputsourceconnection + fn SimulateInputSourceConnection( + &self, + init: &FakeXRInputSourceInit, + ) -> Fallible<DomRoot<FakeXRInputController>> { + let id = self.next_input_id.get(); + self.next_input_id.set(InputId(id.0 + 1)); + + let handedness = init.handedness.into(); + let target_ray_mode = init.targetRayMode.into(); + + let pointer_origin = Some(get_origin(&init.pointerOrigin)?); + + let grip_origin = if let Some(ref g) = init.gripOrigin { + Some(get_origin(g)?) + } else { + None + }; + + let profiles = init.profiles.iter().cloned().map(String::from).collect(); + + // XXXManishearth deal with supportedButtons and selection* + + let source = InputSource { + handedness, + target_ray_mode, + id, + supports_grip: true, + profiles, + hand_support: None, + }; + + let init = MockInputInit { + source, + pointer_origin, + grip_origin, + }; + + let global = self.global(); + let _ = self.sender.send(MockDeviceMsg::AddInputSource(init)); + + let controller = FakeXRInputController::new(&global, self.sender.clone(), id); + + Ok(controller) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-disconnect + fn Disconnect(&self) -> Rc<Promise> { + let global = self.global(); + let p = Promise::new(&global); + let mut trusted = Some(TrustedPromise::new(p.clone())); + let (task_source, canceller) = global + .as_window() + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap(); + ROUTER.add_route( + receiver.to_opaque(), + Box::new(move |_| { + let trusted = trusted + .take() + .expect("disconnect callback called multiple times"); + let _ = task_source.queue_with_canceller(trusted.resolve_task(()), &canceller); + }), + ); + self.disconnect(sender); + p + } +} + +impl From<XRHandedness> for Handedness { + fn from(h: XRHandedness) -> Self { + match h { + XRHandedness::None => Handedness::None, + XRHandedness::Left => Handedness::Left, + XRHandedness::Right => Handedness::Right, + } + } +} + +impl From<XRTargetRayMode> for TargetRayMode { + fn from(t: XRTargetRayMode) -> Self { + match t { + XRTargetRayMode::Gaze => TargetRayMode::Gaze, + XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer, + XRTargetRayMode::Screen => TargetRayMode::Screen, + } + } +} diff --git a/components/script/dom/fakexrinputcontroller.rs b/components/script/dom/fakexrinputcontroller.rs new file mode 100644 index 00000000000..5699fa5d747 --- /dev/null +++ b/components/script/dom/fakexrinputcontroller.rs @@ -0,0 +1,135 @@ +/* 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 crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::FakeXRRigidTransformInit; +use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::FakeXRInputControllerMethods; +use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{ + XRHandedness, XRTargetRayMode, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::fakexrdevice::get_origin; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSender; +use webxr_api::{ + Handedness, InputId, MockDeviceMsg, MockInputMsg, SelectEvent, SelectKind, TargetRayMode, +}; + +#[dom_struct] +pub struct FakeXRInputController { + reflector: Reflector, + #[ignore_malloc_size_of = "defined in ipc-channel"] + sender: IpcSender<MockDeviceMsg>, + #[ignore_malloc_size_of = "defined in webxr-api"] + id: InputId, +} + +impl FakeXRInputController { + pub fn new_inherited(sender: IpcSender<MockDeviceMsg>, id: InputId) -> FakeXRInputController { + FakeXRInputController { + reflector: Reflector::new(), + sender, + id, + } + } + + pub fn new( + global: &GlobalScope, + sender: IpcSender<MockDeviceMsg>, + id: InputId, + ) -> DomRoot<FakeXRInputController> { + reflect_dom_object( + Box::new(FakeXRInputController::new_inherited(sender, id)), + global, + ) + } + + fn send_message(&self, msg: MockInputMsg) { + let _ = self + .sender + .send(MockDeviceMsg::MessageInputSource(self.id, msg)); + } +} + +impl FakeXRInputControllerMethods for FakeXRInputController { + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setpointerorigin + fn SetPointerOrigin(&self, origin: &FakeXRRigidTransformInit, _emulated: bool) -> Fallible<()> { + self.send_message(MockInputMsg::SetPointerOrigin(Some(get_origin(origin)?))); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setgriporigin + fn SetGripOrigin(&self, origin: &FakeXRRigidTransformInit, _emulated: bool) -> Fallible<()> { + self.send_message(MockInputMsg::SetGripOrigin(Some(get_origin(origin)?))); + Ok(()) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-cleargriporigin + fn ClearGripOrigin(&self) { + self.send_message(MockInputMsg::SetGripOrigin(None)) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-disconnect + fn Disconnect(&self) { + self.send_message(MockInputMsg::Disconnect) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-reconnect + fn Reconnect(&self) { + self.send_message(MockInputMsg::Reconnect) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-startselection + fn StartSelection(&self) { + self.send_message(MockInputMsg::TriggerSelect( + SelectKind::Select, + SelectEvent::Start, + )) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-endselection + fn EndSelection(&self) { + self.send_message(MockInputMsg::TriggerSelect( + SelectKind::Select, + SelectEvent::End, + )) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-simulateselect + fn SimulateSelect(&self) { + self.send_message(MockInputMsg::TriggerSelect( + SelectKind::Select, + SelectEvent::Select, + )) + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-sethandedness + fn SetHandedness(&self, handedness: XRHandedness) { + let h = match handedness { + XRHandedness::None => Handedness::None, + XRHandedness::Left => Handedness::Left, + XRHandedness::Right => Handedness::Right, + }; + let _ = self.send_message(MockInputMsg::SetHandedness(h)); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-settargetraymode + fn SetTargetRayMode(&self, target_ray_mode: XRTargetRayMode) { + let t = match target_ray_mode { + XRTargetRayMode::Gaze => TargetRayMode::Gaze, + XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer, + XRTargetRayMode::Screen => TargetRayMode::Screen, + }; + let _ = self.send_message(MockInputMsg::SetTargetRayMode(t)); + } + + /// https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setprofiles + fn SetProfiles(&self, profiles: Vec<DOMString>) { + let t = profiles.into_iter().map(String::from).collect(); + let _ = self.send_message(MockInputMsg::SetProfiles(t)); + } +} diff --git a/components/script/dom/file.rs b/components/script/dom/file.rs index 6cf144d10e0..cbbc16bd264 100644 --- a/components/script/dom/file.rs +++ b/components/script/dom/file.rs @@ -1,21 +1,21 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::FileBinding; -use dom::bindings::codegen::Bindings::FileBinding::FileMethods; -use dom::bindings::codegen::UnionTypes::BlobOrString; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::blob::{Blob, BlobImpl, blob_parts_to_bytes}; -use dom::globalscope::GlobalScope; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::FileBinding; +use crate::dom::bindings::codegen::Bindings::FileBinding::FileMethods; +use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::blob::{blob_parts_to_bytes, normalize_type_string, Blob}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use dom_struct::dom_struct; use net_traits::filemanager_thread::SelectedFile; -use time; +use script_traits::serializable::BlobImpl; #[dom_struct] pub struct File { @@ -26,10 +26,9 @@ pub struct File { impl File { #[allow(unrooted_must_root)] - fn new_inherited(blob_impl: BlobImpl, name: DOMString, - modified: Option<i64>, type_string: &str) -> File { + fn new_inherited(blob_impl: &BlobImpl, name: DOMString, modified: Option<i64>) -> File { File { - blob: Blob::new_inherited(blob_impl, type_string.to_owned()), + blob: Blob::new_inherited(blob_impl), name: name, // https://w3c.github.io/FileAPI/#dfn-lastModified modified: match modified { @@ -37,50 +36,74 @@ impl File { None => { let time = time::get_time(); time.sec * 1000 + (time.nsec / 1000000) as i64 - } + }, }, } } #[allow(unrooted_must_root)] - pub fn new(global: &GlobalScope, blob_impl: BlobImpl, - name: DOMString, modified: Option<i64>, typeString: &str) -> Root<File> { - reflect_dom_object(box File::new_inherited(blob_impl, name, modified, typeString), - global, - FileBinding::Wrap) + pub fn new( + global: &GlobalScope, + blob_impl: BlobImpl, + name: DOMString, + modified: Option<i64>, + ) -> DomRoot<File> { + let file = reflect_dom_object( + Box::new(File::new_inherited(&blob_impl, name, modified)), + global, + ); + global.track_file(&file, blob_impl); + file } // Construct from selected file message from file manager thread - pub fn new_from_selected(window: &Window, selected: SelectedFile) -> Root<File> { - let name = DOMString::from(selected.filename.to_str().expect("File name encoding error")); + pub fn new_from_selected(window: &Window, selected: SelectedFile) -> DomRoot<File> { + let name = DOMString::from( + selected + .filename + .to_str() + .expect("File name encoding error"), + ); - File::new(window.upcast(), BlobImpl::new_from_file(selected.id, selected.filename, selected.size), - name, Some(selected.modified as i64), &selected.type_string) + File::new( + window.upcast(), + BlobImpl::new_from_file( + selected.id, + selected.filename, + selected.size, + normalize_type_string(&selected.type_string.to_string()), + ), + name, + Some(selected.modified as i64), + ) } // https://w3c.github.io/FileAPI/#file-constructor - pub fn Constructor(global: &GlobalScope, - fileBits: Vec<BlobOrString>, - filename: DOMString, - filePropertyBag: &FileBinding::FilePropertyBag) - -> Fallible<Root<File>> { + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + fileBits: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>, + filename: DOMString, + filePropertyBag: &FileBinding::FilePropertyBag, + ) -> Fallible<DomRoot<File>> { let bytes: Vec<u8> = match blob_parts_to_bytes(fileBits) { Ok(bytes) => bytes, Err(_) => return Err(Error::InvalidCharacter), }; let ref blobPropertyBag = filePropertyBag.parent; - let ref typeString = blobPropertyBag.type_; let modified = filePropertyBag.lastModified; // NOTE: Following behaviour might be removed in future, // see https://github.com/w3c/FileAPI/issues/41 let replaced_filename = DOMString::from_string(filename.replace("/", ":")); - Ok(File::new(global, - BlobImpl::new_from_bytes(bytes), - replaced_filename, - modified, - typeString)) + let type_string = normalize_type_string(&blobPropertyBag.type_.to_string()); + Ok(File::new( + global, + BlobImpl::new_from_bytes(bytes, type_string), + replaced_filename, + modified, + )) } pub fn name(&self) -> &DOMString { diff --git a/components/script/dom/filelist.rs b/components/script/dom/filelist.rs index 3a5acc41235..de83ccb9db6 100644 --- a/components/script/dom/filelist.rs +++ b/components/script/dom/filelist.rs @@ -1,13 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::FileListBinding; -use dom::bindings::codegen::Bindings::FileListBinding::FileListMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::file::File; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::file::File; +use crate::dom::window::Window; use dom_struct::dom_struct; use std::slice::Iter; @@ -15,26 +14,29 @@ use std::slice::Iter; #[dom_struct] pub struct FileList { reflector_: Reflector, - list: Vec<JS<File>> + list: Vec<Dom<File>>, } impl FileList { #[allow(unrooted_must_root)] - fn new_inherited(files: Vec<JS<File>>) -> FileList { + fn new_inherited(files: Vec<Dom<File>>) -> FileList { FileList { reflector_: Reflector::new(), - list: files + list: files, } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, files: Vec<Root<File>>) -> Root<FileList> { - reflect_dom_object(box FileList::new_inherited(files.iter().map(|r| JS::from_ref(&**r)).collect()), - window, - FileListBinding::Wrap) + pub fn new(window: &Window, files: Vec<DomRoot<File>>) -> DomRoot<FileList> { + reflect_dom_object( + Box::new(FileList::new_inherited( + files.iter().map(|r| Dom::from_ref(&**r)).collect(), + )), + window, + ) } - pub fn iter_files(&self) -> Iter<JS<File>> { + pub fn iter_files(&self) -> Iter<Dom<File>> { self.list.iter() } } @@ -46,16 +48,16 @@ impl FileListMethods for FileList { } // https://w3c.github.io/FileAPI/#dfn-item - fn Item(&self, index: u32) -> Option<Root<File>> { + fn Item(&self, index: u32) -> Option<DomRoot<File>> { if (index as usize) < self.list.len() { - Some(Root::from_ref(&*(self.list[index as usize]))) + Some(DomRoot::from_ref(&*(self.list[index as usize]))) } else { None } } // check-tidy: no specs after this line - fn IndexedGetter(&self, index: u32) -> Option<Root<File>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<File>> { self.Item(index) } } diff --git a/components/script/dom/filereader.rs b/components/script/dom/filereader.rs index 61da63a57e6..084cecc9577 100644 --- a/components/script/dom/filereader.rs +++ b/components/script/dom/filereader.rs @@ -1,45 +1,43 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; +use crate::dom::bindings::codegen::Bindings::FileReaderBinding::{ + FileReaderConstants, FileReaderMethods, +}; +use crate::dom::bindings::codegen::UnionTypes::StringOrObject; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::blob::Blob; +use crate::dom::domexception::{DOMErrorName, DOMException}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::progressevent::ProgressEvent; +use crate::realms::enter_realm; +use crate::script_runtime::JSContext; +use crate::task_source::file_reading::FileReadingTask; +use crate::task_source::{TaskSource, TaskSourceName}; use base64; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::FileReaderBinding::{self, FileReaderConstants, FileReaderMethods}; -use dom::bindings::codegen::UnionTypes::StringOrObject; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::blob::Blob; -use dom::domexception::{DOMErrorName, DOMException}; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::progressevent::ProgressEvent; use dom_struct::dom_struct; -use encoding::all::UTF_8; -use encoding::label::encoding_from_whatwg_label; -use encoding::types::{DecoderTrap, EncodingRef}; -use hyper::mime::{Attr, Mime}; +use encoding_rs::{Encoding, UTF_8}; use js::jsapi::Heap; -use js::jsapi::JSAutoCompartment; -use js::jsapi::JSContext; +use js::jsapi::JSObject; use js::jsval::{self, JSVal}; use js::typedarray::{ArrayBuffer, CreateWith}; -use script_thread::RunnableWrapper; +use mime::{self, Mime}; use servo_atoms::Atom; use std::cell::Cell; use std::ptr; -use std::sync::Arc; -use std::thread; -use task_source::TaskSource; -use task_source::file_reading::{FileReadingTaskSource, FileReadingRunnable, FileReadingTask}; -#[derive(PartialEq, Clone, Copy, JSTraceable, HeapSizeOf)] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub enum FileReaderFunction { ReadAsText, ReadAsDataUrl, @@ -48,16 +46,19 @@ pub enum FileReaderFunction { pub type TrustedFileReader = Trusted<FileReader>; -#[derive(Clone, HeapSizeOf)] +#[derive(Clone, MallocSizeOf)] pub struct ReadMetaData { pub blobtype: String, pub label: Option<String>, - pub function: FileReaderFunction + pub function: FileReaderFunction, } impl ReadMetaData { - pub fn new(blobtype: String, - label: Option<String>, function: FileReaderFunction) -> ReadMetaData { + pub fn new( + blobtype: String, + label: Option<String>, + function: FileReaderFunction, + ) -> ReadMetaData { ReadMetaData { blobtype: blobtype, label: label, @@ -66,29 +67,76 @@ impl ReadMetaData { } } -#[derive(PartialEq, Clone, Copy, JSTraceable, HeapSizeOf)] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub struct GenerationId(u32); #[repr(u16)] -#[derive(Copy, Clone, Debug, PartialEq, JSTraceable, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub enum FileReaderReadyState { Empty = FileReaderConstants::EMPTY, Loading = FileReaderConstants::LOADING, Done = FileReaderConstants::DONE, } -#[derive(HeapSizeOf, JSTraceable)] +#[derive(JSTraceable, MallocSizeOf)] pub enum FileReaderResult { - ArrayBuffer(Heap<JSVal>), + ArrayBuffer(#[ignore_malloc_size_of = "mozjs"] Heap<JSVal>), String(DOMString), } +pub struct FileReaderSharedFunctionality; + +impl FileReaderSharedFunctionality { + pub fn dataurl_format(blob_contents: &[u8], blob_type: String) -> DOMString { + let base64 = base64::encode(&blob_contents); + + let dataurl = if blob_type.is_empty() { + format!("data:base64,{}", base64) + } else { + format!("data:{};base64,{}", blob_type, base64) + }; + + DOMString::from(dataurl) + } + + pub fn text_decode( + blob_contents: &[u8], + blob_type: &str, + blob_label: &Option<String>, + ) -> DOMString { + //https://w3c.github.io/FileAPI/#encoding-determination + // Steps 1 & 2 & 3 + let mut encoding = blob_label + .as_ref() + .map(|string| string.as_bytes()) + .and_then(Encoding::for_label); + + // Step 4 & 5 + encoding = encoding.or_else(|| { + let resultmime = blob_type.parse::<Mime>().ok(); + resultmime.and_then(|mime| { + mime.params() + .find(|(ref k, _)| &mime::CHARSET == k) + .and_then(|(_, ref v)| Encoding::for_label(v.as_ref().as_bytes())) + }) + }); + + // Step 6 + let enc = encoding.unwrap_or(UTF_8); + + let convert = blob_contents; + // Step 7 + let (output, _, _) = enc.decode(convert); + DOMString::from(output) + } +} + #[dom_struct] pub struct FileReader { eventtarget: EventTarget, ready_state: Cell<FileReaderReadyState>, - error: MutNullableJS<DOMException>, - result: DOMRefCell<Option<FileReaderResult>>, + error: MutNullableDom<DOMException>, + result: DomRefCell<Option<FileReaderResult>>, generation_id: Cell<GenerationId>, } @@ -97,23 +145,27 @@ impl FileReader { FileReader { eventtarget: EventTarget::new_inherited(), ready_state: Cell::new(FileReaderReadyState::Empty), - error: MutNullableJS::new(None), - result: DOMRefCell::new(None), + error: MutNullableDom::new(None), + result: DomRefCell::new(None), generation_id: Cell::new(GenerationId(0)), } } - pub fn new(global: &GlobalScope) -> Root<FileReader> { - reflect_dom_object(box FileReader::new_inherited(), - global, FileReaderBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<FileReader> { + reflect_dom_object(Box::new(FileReader::new_inherited()), global) } - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<FileReader>> { + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<FileReader>> { Ok(FileReader::new(global)) } //https://w3c.github.io/FileAPI/#dfn-error-steps - pub fn process_read_error(filereader: TrustedFileReader, gen_id: GenerationId, error: DOMErrorName) { + pub fn process_read_error( + filereader: TrustedFileReader, + gen_id: GenerationId, + error: DOMErrorName, + ) { let fr = filereader.root(); macro_rules! return_on_abort( @@ -174,9 +226,12 @@ impl FileReader { } // https://w3c.github.io/FileAPI/#dfn-readAsText - #[allow(unsafe_code)] - pub fn process_read_eof(filereader: TrustedFileReader, gen_id: GenerationId, - data: ReadMetaData, blob_contents: Arc<Vec<u8>>) { + pub fn process_read_eof( + filereader: TrustedFileReader, + gen_id: GenerationId, + data: ReadMetaData, + blob_contents: Vec<u8>, + ) { let fr = filereader.root(); macro_rules! return_on_abort( @@ -193,13 +248,20 @@ impl FileReader { // Step 8.2 match data.function { - FileReaderFunction::ReadAsDataUrl => - FileReader::perform_readasdataurl(&fr.result, data, &blob_contents), - FileReaderFunction::ReadAsText => - FileReader::perform_readastext(&fr.result, data, &blob_contents), + FileReaderFunction::ReadAsDataUrl => { + FileReader::perform_readasdataurl(&fr.result, data, &blob_contents) + }, + FileReaderFunction::ReadAsText => { + FileReader::perform_readastext(&fr.result, data, &blob_contents) + }, FileReaderFunction::ReadAsArrayBuffer => { - let _ac = JSAutoCompartment::new(fr.global().get_cx(), *fr.reflector().get_jsobject()); - FileReader::perform_readasarraybuffer(&fr.result, fr.global().get_cx(), data, &blob_contents) + let _ac = enter_realm(&*fr); + FileReader::perform_readasarraybuffer( + &fr.result, + fr.global().get_cx(), + data, + &blob_contents, + ) }, }; @@ -211,60 +273,46 @@ impl FileReader { fr.dispatch_progress_event(atom!("loadend"), 0, None); } return_on_abort!(); - // Step 9 - fr.terminate_ongoing_reading(); } // https://w3c.github.io/FileAPI/#dfn-readAsText - fn perform_readastext(result: &DOMRefCell<Option<FileReaderResult>>, data: ReadMetaData, blob_bytes: &[u8]) { + fn perform_readastext( + result: &DomRefCell<Option<FileReaderResult>>, + data: ReadMetaData, + blob_bytes: &[u8], + ) { let blob_label = &data.label; let blob_type = &data.blobtype; - //https://w3c.github.io/FileAPI/#encoding-determination - // Steps 1 & 2 & 3 - let mut encoding = blob_label.as_ref() - .map(|string| &**string) - .and_then(encoding_from_whatwg_label); - - // Step 4 & 5 - encoding = encoding.or_else(|| { - let resultmime = blob_type.parse::<Mime>().ok(); - resultmime.and_then(|Mime(_, _, ref parameters)| { - parameters.iter() - .find(|&&(ref k, _)| &Attr::Charset == k) - .and_then(|&(_, ref v)| encoding_from_whatwg_label(&v.to_string())) - }) - }); - - // Step 6 - let enc = encoding.unwrap_or(UTF_8 as EncodingRef); - - let convert = blob_bytes; - // Step 7 - let output = enc.decode(convert, DecoderTrap::Replace).unwrap(); - *result.borrow_mut() = Some(FileReaderResult::String(DOMString::from(output))); + let output = FileReaderSharedFunctionality::text_decode(blob_bytes, blob_type, blob_label); + *result.borrow_mut() = Some(FileReaderResult::String(output)); } //https://w3c.github.io/FileAPI/#dfn-readAsDataURL - fn perform_readasdataurl(result: &DOMRefCell<Option<FileReaderResult>>, data: ReadMetaData, bytes: &[u8]) { - let base64 = base64::encode(bytes); - - let output = if data.blobtype.is_empty() { - format!("data:base64,{}", base64) - } else { - format!("data:{};base64,{}", data.blobtype, base64) - }; - - *result.borrow_mut() = Some(FileReaderResult::String(DOMString::from(output))); + fn perform_readasdataurl( + result: &DomRefCell<Option<FileReaderResult>>, + data: ReadMetaData, + bytes: &[u8], + ) { + let output = FileReaderSharedFunctionality::dataurl_format(bytes, data.blobtype); + + *result.borrow_mut() = Some(FileReaderResult::String(output)); } // https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer #[allow(unsafe_code)] - fn perform_readasarraybuffer(result: &DOMRefCell<Option<FileReaderResult>>, - cx: *mut JSContext, _: ReadMetaData, bytes: &[u8]) { + fn perform_readasarraybuffer( + result: &DomRefCell<Option<FileReaderResult>>, + cx: JSContext, + _: ReadMetaData, + bytes: &[u8], + ) { unsafe { - rooted!(in(cx) let mut array_buffer = ptr::null_mut()); - assert!(ArrayBuffer::create(cx, CreateWith::Slice(bytes), array_buffer.handle_mut()).is_ok()); + rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>()); + assert!( + ArrayBuffer::create(*cx, CreateWith::Slice(bytes), array_buffer.handle_mut()) + .is_ok() + ); *result.borrow_mut() = Some(FileReaderResult::ArrayBuffer(Heap::default())); @@ -328,19 +376,22 @@ impl FileReaderMethods for FileReader { } // https://w3c.github.io/FileAPI/#dfn-error - fn GetError(&self) -> Option<Root<DOMException>> { + fn GetError(&self) -> Option<DomRoot<DOMException>> { self.error.get() } #[allow(unsafe_code)] // https://w3c.github.io/FileAPI/#dfn-result - unsafe fn GetResult(&self, _: *mut JSContext) -> Option<StringOrObject> { + fn GetResult(&self, _: JSContext) -> Option<StringOrObject> { self.result.borrow().as_ref().map(|r| match *r { - FileReaderResult::String(ref string) => - StringOrObject::String(string.clone()), + FileReaderResult::String(ref string) => StringOrObject::String(string.clone()), FileReaderResult::ArrayBuffer(ref arr_buffer) => { - StringOrObject::Object(Heap::new((*arr_buffer.ptr.get()).to_object())) - } + let result = RootedTraceableBox::new(Heap::default()); + unsafe { + result.set((*arr_buffer.ptr.get()).to_object()); + } + StringOrObject::Object(result) + }, }) } @@ -350,12 +401,17 @@ impl FileReaderMethods for FileReader { } } - impl FileReader { fn dispatch_progress_event(&self, type_: Atom, loaded: u64, total: Option<u64>) { - let progressevent = ProgressEvent::new(&self.global(), - type_, EventBubbles::DoesNotBubble, EventCancelable::NotCancelable, - total.is_some(), loaded, total.unwrap_or(0)); + let progressevent = ProgressEvent::new( + &self.global(), + type_, + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + total.is_some(), + loaded, + total.unwrap_or(0), + ); progressevent.upcast::<Event>().fire(self.upcast()); } @@ -364,7 +420,13 @@ impl FileReader { self.generation_id.set(GenerationId(prev_id + 1)); } - fn read(&self, function: FileReaderFunction, blob: &Blob, label: Option<DOMString>) -> ErrorResult { + /// <https://w3c.github.io/FileAPI/#readOperation> + fn read( + &self, + function: FileReaderFunction, + blob: &Blob, + label: Option<DOMString>, + ) -> ErrorResult { // Step 1 if self.ready_state.get() == FileReaderReadyState::Loading { return Err(Error::InvalidState); @@ -374,22 +436,42 @@ impl FileReader { self.change_ready_state(FileReaderReadyState::Loading); // Step 3 - let blob_contents = Arc::new(blob.get_bytes().unwrap_or(vec![])); + *self.result.borrow_mut() = None; let type_ = blob.Type(); let load_data = ReadMetaData::new(String::from(type_), label.map(String::from), function); - let fr = Trusted::new(self); + let GenerationId(prev_id) = self.generation_id.get(); + self.generation_id.set(GenerationId(prev_id + 1)); let gen_id = self.generation_id.get(); + // Step 10, in parallel, wait on stream promises to resolve and queue tasks. + + // TODO: follow the spec which requires implementing blob `get_stream`, + // see https://github.com/servo/servo/issues/25209 + + // Currently bytes are first read "sync", and then the appropriate tasks are queued. + + // Read the blob bytes "sync". + let blob_contents = blob.get_bytes().unwrap_or_else(|_| vec![]); + + let filereader = Trusted::new(self); let global = self.global(); - let wrapper = global.get_runnable_wrapper(); + let canceller = global.task_canceller(TaskSourceName::FileReading); let task_source = global.file_reading_task_source(); - thread::Builder::new().name("file reader async operation".to_owned()).spawn(move || { - perform_annotated_read_operation(gen_id, load_data, blob_contents, fr, task_source, wrapper) - }).expect("Thread spawning failed"); + // Queue tasks as appropriate. + let task = FileReadingTask::ProcessRead(filereader.clone(), gen_id); + task_source.queue_with_canceller(task, &canceller).unwrap(); + + if !blob_contents.is_empty() { + let task = FileReadingTask::ProcessReadData(filereader.clone(), gen_id); + task_source.queue_with_canceller(task, &canceller).unwrap(); + } + + let task = FileReadingTask::ProcessReadEOF(filereader, gen_id, load_data, blob_contents); + task_source.queue_with_canceller(task, &canceller).unwrap(); Ok(()) } @@ -398,21 +480,3 @@ impl FileReader { self.ready_state.set(state); } } - -// https://w3c.github.io/FileAPI/#thread-read-operation -fn perform_annotated_read_operation(gen_id: GenerationId, - data: ReadMetaData, - blob_contents: Arc<Vec<u8>>, - filereader: TrustedFileReader, - task_source: FileReadingTaskSource, - wrapper: RunnableWrapper) { - // Step 4 - let task = FileReadingRunnable::new(FileReadingTask::ProcessRead(filereader.clone(), gen_id)); - task_source.queue_with_wrapper(task, &wrapper).unwrap(); - - let task = FileReadingRunnable::new(FileReadingTask::ProcessReadData(filereader.clone(), gen_id)); - task_source.queue_with_wrapper(task, &wrapper).unwrap(); - - let task = FileReadingRunnable::new(FileReadingTask::ProcessReadEOF(filereader, gen_id, data, blob_contents)); - task_source.queue_with_wrapper(task, &wrapper).unwrap(); -} diff --git a/components/script/dom/filereadersync.rs b/components/script/dom/filereadersync.rs index 744625474cd..044cb48c27d 100644 --- a/components/script/dom/filereadersync.rs +++ b/components/script/dom/filereadersync.rs @@ -1,33 +1,103 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::FileReaderSyncBinding; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; +use crate::dom::bindings::codegen::Bindings::FileReaderSyncBinding::FileReaderSyncMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::blob::Blob; +use crate::dom::filereader::FileReaderSharedFunctionality; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; +use js::jsapi::JSObject; +use js::typedarray::{ArrayBuffer, CreateWith}; +use std::ptr; +use std::ptr::NonNull; #[dom_struct] pub struct FileReaderSync { - eventtarget: EventTarget + reflector: Reflector, } impl FileReaderSync { pub fn new_inherited() -> FileReaderSync { FileReaderSync { - eventtarget: EventTarget::new_inherited(), + reflector: Reflector::new(), } } - pub fn new(global: &GlobalScope) -> Root<FileReaderSync> { - reflect_dom_object(box FileReaderSync::new_inherited(), - global, FileReaderSyncBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<FileReaderSync> { + reflect_dom_object(Box::new(FileReaderSync::new_inherited()), global) } - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<FileReaderSync>> { + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<FileReaderSync>> { Ok(FileReaderSync::new(global)) } + + fn get_blob_bytes(blob: &Blob) -> Result<Vec<u8>, Error> { + blob.get_bytes().map_err(|_| Error::NotReadable) + } +} + +impl FileReaderSyncMethods for FileReaderSync { + // https://w3c.github.io/FileAPI/#readAsBinaryStringSyncSection + fn ReadAsBinaryString(&self, blob: &Blob) -> Fallible<DOMString> { + // step 1 + let blob_contents = FileReaderSync::get_blob_bytes(blob)?; + + // step 2 + Ok(DOMString::from(String::from_utf8_lossy(&blob_contents))) + } + + // https://w3c.github.io/FileAPI/#readAsTextSync + fn ReadAsText(&self, blob: &Blob, label: Option<DOMString>) -> Fallible<DOMString> { + // step 1 + let blob_contents = FileReaderSync::get_blob_bytes(blob)?; + + // step 2 + let blob_label = label.map(String::from); + let blob_type = String::from(blob.Type()); + + let output = + FileReaderSharedFunctionality::text_decode(&blob_contents, &blob_type, &blob_label); + + Ok(output) + } + + // https://w3c.github.io/FileAPI/#readAsDataURLSync-section + fn ReadAsDataURL(&self, blob: &Blob) -> Fallible<DOMString> { + // step 1 + let blob_contents = FileReaderSync::get_blob_bytes(blob)?; + + // step 2 + let output = + FileReaderSharedFunctionality::dataurl_format(&blob_contents, blob.Type().to_string()); + + Ok(output) + } + + #[allow(unsafe_code)] + // https://w3c.github.io/FileAPI/#readAsArrayBufferSyncSection + fn ReadAsArrayBuffer(&self, cx: JSContext, blob: &Blob) -> Fallible<NonNull<JSObject>> { + // step 1 + let blob_contents = FileReaderSync::get_blob_bytes(blob)?; + + // step 2 + unsafe { + rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>()); + assert!(ArrayBuffer::create( + *cx, + CreateWith::Slice(&blob_contents), + array_buffer.handle_mut() + ) + .is_ok()); + + Ok(NonNull::new_unchecked(array_buffer.get())) + } + } } diff --git a/components/script/dom/focusevent.rs b/components/script/dom/focusevent.rs index eae3920dae0..2d983bcc881 100644 --- a/components/script/dom/focusevent.rs +++ b/components/script/dom/focusevent.rs @@ -1,26 +1,26 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::FocusEventBinding; -use dom::bindings::codegen::Bindings::FocusEventBinding::FocusEventMethods; -use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root, RootedReference}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::{EventBubbles, EventCancelable}; -use dom::eventtarget::EventTarget; -use dom::uievent::UIEvent; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::FocusEventBinding; +use crate::dom::bindings::codegen::Bindings::FocusEventBinding::FocusEventMethods; +use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::uievent::UIEvent; +use crate::dom::window::Window; use dom_struct::dom_struct; use std::default::Default; #[dom_struct] pub struct FocusEvent { uievent: UIEvent, - related_target: MutNullableJS<EventTarget>, + related_target: MutNullableDom<EventTarget>, } impl FocusEvent { @@ -31,47 +31,55 @@ impl FocusEvent { } } - pub fn new_uninitialized(window: &Window) -> Root<FocusEvent> { - reflect_dom_object(box FocusEvent::new_inherited(), - window, - FocusEventBinding::Wrap) + pub fn new_uninitialized(window: &Window) -> DomRoot<FocusEvent> { + reflect_dom_object(Box::new(FocusEvent::new_inherited()), window) } - pub fn new(window: &Window, - type_: DOMString, - can_bubble: EventBubbles, - cancelable: EventCancelable, - view: Option<&Window>, - detail: i32, - related_target: Option<&EventTarget>) -> Root<FocusEvent> { + pub fn new( + window: &Window, + type_: DOMString, + can_bubble: EventBubbles, + cancelable: EventCancelable, + view: Option<&Window>, + detail: i32, + related_target: Option<&EventTarget>, + ) -> DomRoot<FocusEvent> { let ev = FocusEvent::new_uninitialized(window); - ev.upcast::<UIEvent>().InitUIEvent(type_, - bool::from(can_bubble), - bool::from(cancelable), - view, detail); + ev.upcast::<UIEvent>().InitUIEvent( + type_, + bool::from(can_bubble), + bool::from(cancelable), + view, + detail, + ); ev.related_target.set(related_target); ev } - pub fn Constructor(window: &Window, - type_: DOMString, - init: &FocusEventBinding::FocusEventInit) -> Fallible<Root<FocusEvent>> { + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &FocusEventBinding::FocusEventInit, + ) -> Fallible<DomRoot<FocusEvent>> { let bubbles = EventBubbles::from(init.parent.parent.bubbles); let cancelable = EventCancelable::from(init.parent.parent.cancelable); - let event = FocusEvent::new(window, - type_, - bubbles, - cancelable, - init.parent.view.r(), - init.parent.detail, - init.relatedTarget.r()); + let event = FocusEvent::new( + window, + type_, + bubbles, + cancelable, + init.parent.view.as_deref(), + init.parent.detail, + init.relatedTarget.as_deref(), + ); Ok(event) } } impl FocusEventMethods for FocusEvent { // https://w3c.github.io/uievents/#widl-FocusEvent-relatedTarget - fn GetRelatedTarget(&self) -> Option<Root<EventTarget>> { + fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> { self.related_target.get() } diff --git a/components/script/dom/forcetouchevent.rs b/components/script/dom/forcetouchevent.rs deleted file mode 100644 index cf5c622d8e8..00000000000 --- a/components/script/dom/forcetouchevent.rs +++ /dev/null @@ -1,58 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::ForceTouchEventBinding; -use dom::bindings::codegen::Bindings::ForceTouchEventBinding::ForceTouchEventMethods; -use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::num::Finite; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::uievent::UIEvent; -use dom::window::Window; -use dom_struct::dom_struct; - -#[dom_struct] -pub struct ForceTouchEvent { - uievent: UIEvent, - force: f32, -} - -impl ForceTouchEvent { - fn new_inherited(force: f32) -> ForceTouchEvent { - ForceTouchEvent { - uievent: UIEvent::new_inherited(), - force: force, - } - } - - pub fn new(window: &Window, - type_: DOMString, - force: f32) -> Root<ForceTouchEvent> { - let event = box ForceTouchEvent::new_inherited(force); - let ev = reflect_dom_object(event, window, ForceTouchEventBinding::Wrap); - ev.upcast::<UIEvent>().InitUIEvent(type_, true, true, Some(window), 0); - ev - } -} - -impl<'a> ForceTouchEventMethods for &'a ForceTouchEvent { - fn ServoForce(&self) -> Finite<f32> { - Finite::wrap(self.force) - } - - fn SERVO_FORCE_AT_MOUSE_DOWN(&self) -> Finite<f32> { - Finite::wrap(1.0) - } - - fn SERVO_FORCE_AT_FORCE_MOUSE_DOWN(&self) -> Finite<f32> { - Finite::wrap(2.0) - } - - // https://dom.spec.whatwg.org/#dom-event-istrusted - fn IsTrusted(&self) -> bool { - self.uievent.IsTrusted() - } -} diff --git a/components/script/dom/formdata.rs b/components/script/dom/formdata.rs index af4c692e7d9..3a8f3ce4b5b 100644 --- a/components/script/dom/formdata.rs +++ b/components/script/dom/formdata.rs @@ -1,60 +1,64 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods; -use dom::bindings::codegen::Bindings::FormDataBinding::FormDataWrap; -use dom::bindings::codegen::UnionTypes::FileOrUSVString; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::iterable::Iterable; -use dom::bindings::js::Root; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::blob::{Blob, BlobImpl}; -use dom::file::File; -use dom::globalscope::GlobalScope; -use dom::htmlformelement::{HTMLFormElement, FormDatumValue, FormDatum}; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods; +use crate::dom::bindings::codegen::UnionTypes::FileOrUSVString; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::iterable::Iterable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::blob::Blob; +use crate::dom::file::File; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlformelement::{FormDatum, FormDatumValue, HTMLFormElement}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use std::collections::HashMap; -use std::collections::hash_map::Entry::{Occupied, Vacant}; -use std::iter; +use html5ever::LocalName; +use script_traits::serializable::BlobImpl; #[dom_struct] pub struct FormData { reflector_: Reflector, - data: DOMRefCell<HashMap<LocalName, Vec<FormDatum>>>, + data: DomRefCell<Vec<(LocalName, FormDatum)>>, } impl FormData { - fn new_inherited(opt_form: Option<&HTMLFormElement>) -> FormData { - let mut hashmap: HashMap<LocalName, Vec<FormDatum>> = HashMap::new(); - - if let Some(form) = opt_form { - for datum in form.get_form_dataset(None) { - match hashmap.entry(LocalName::from(datum.name.as_ref())) { - Occupied(entry) => entry.into_mut().push(datum), - Vacant(entry) => { entry.insert(vec!(datum)); } - } - } - } + fn new_inherited(form_datums: Option<Vec<FormDatum>>) -> FormData { + let data = match form_datums { + Some(data) => data + .iter() + .map(|datum| (LocalName::from(datum.name.as_ref()), datum.clone())) + .collect::<Vec<(LocalName, FormDatum)>>(), + None => Vec::new(), + }; FormData { reflector_: Reflector::new(), - data: DOMRefCell::new(hashmap), + data: DomRefCell::new(data), } } - pub fn new(form: Option<&HTMLFormElement>, global: &GlobalScope) -> Root<FormData> { - reflect_dom_object(box FormData::new_inherited(form), - global, FormDataWrap) + pub fn new(form_datums: Option<Vec<FormDatum>>, global: &GlobalScope) -> DomRoot<FormData> { + reflect_dom_object(Box::new(FormData::new_inherited(form_datums)), global) } - pub fn Constructor(global: &GlobalScope, form: Option<&HTMLFormElement>) -> Fallible<Root<FormData>> { - // TODO: Construct form data set for form if it is supplied - Ok(FormData::new(form, global)) + // https://xhr.spec.whatwg.org/#dom-formdata + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + form: Option<&HTMLFormElement>, + ) -> Fallible<DomRoot<FormData>> { + if let Some(opt_form) = form { + return match opt_form.get_form_dataset(None, None) { + Some(form_datums) => Ok(FormData::new(Some(form_datums), global)), + None => Err(Error::InvalidState), + }; + } + + Ok(FormData::new(None, global)) } } @@ -67,11 +71,9 @@ impl FormDataMethods for FormData { value: FormDatumValue::String(DOMString::from(str_value.0)), }; - let mut data = self.data.borrow_mut(); - match data.entry(LocalName::from(name.0)) { - Occupied(entry) => entry.into_mut().push(datum), - Vacant(entry) => { entry.insert(vec!(datum)); } - } + self.data + .borrow_mut() + .push((LocalName::from(name.0), datum)); } #[allow(unrooted_must_root)] @@ -80,90 +82,134 @@ impl FormDataMethods for FormData { let datum = FormDatum { ty: DOMString::from("file"), name: DOMString::from(name.0.clone()), - value: FormDatumValue::File(Root::from_ref(&*self.create_an_entry(blob, filename))), + value: FormDatumValue::File(DomRoot::from_ref(&*self.create_an_entry(blob, filename))), }; - let mut data = self.data.borrow_mut(); - - match data.entry(LocalName::from(name.0)) { - Occupied(entry) => entry.into_mut().push(datum), - Vacant(entry) => { entry.insert(vec!(datum)); }, - } + self.data + .borrow_mut() + .push((LocalName::from(name.0), datum)); } // https://xhr.spec.whatwg.org/#dom-formdata-delete fn Delete(&self, name: USVString) { - self.data.borrow_mut().remove(&LocalName::from(name.0)); + self.data + .borrow_mut() + .retain(|(datum_name, _)| datum_name != &LocalName::from(name.0.clone())); } // https://xhr.spec.whatwg.org/#dom-formdata-get fn Get(&self, name: USVString) -> Option<FileOrUSVString> { - self.data.borrow() - .get(&LocalName::from(name.0)) - .map(|entry| match entry[0].value { - FormDatumValue::String(ref s) => FileOrUSVString::USVString(USVString(s.to_string())), - FormDatumValue::File(ref b) => FileOrUSVString::File(Root::from_ref(&*b)), - }) + self.data + .borrow() + .iter() + .filter(|(datum_name, _)| datum_name == &LocalName::from(name.0.clone())) + .next() + .map(|(_, datum)| match &datum.value { + FormDatumValue::String(ref s) => { + FileOrUSVString::USVString(USVString(s.to_string())) + }, + FormDatumValue::File(ref b) => FileOrUSVString::File(DomRoot::from_ref(&*b)), + }) } // https://xhr.spec.whatwg.org/#dom-formdata-getall fn GetAll(&self, name: USVString) -> Vec<FileOrUSVString> { - self.data.borrow() - .get(&LocalName::from(name.0)) - .map_or(vec![], |data| - data.iter().map(|item| match item.value { - FormDatumValue::String(ref s) => FileOrUSVString::USVString(USVString(s.to_string())), - FormDatumValue::File(ref b) => FileOrUSVString::File(Root::from_ref(&*b)), - }).collect() - ) + self.data + .borrow() + .iter() + .filter_map(|datum| { + if datum.0 != LocalName::from(name.0.clone()) { + return None; + } + + Some(match &datum.1.value { + FormDatumValue::String(ref s) => { + FileOrUSVString::USVString(USVString(s.to_string())) + }, + FormDatumValue::File(ref b) => FileOrUSVString::File(DomRoot::from_ref(&*b)), + }) + }) + .collect() } // https://xhr.spec.whatwg.org/#dom-formdata-has fn Has(&self, name: USVString) -> bool { - self.data.borrow().contains_key(&LocalName::from(name.0)) + self.data + .borrow() + .iter() + .any(|(datum_name, _0)| datum_name == &LocalName::from(name.0.clone())) } // https://xhr.spec.whatwg.org/#dom-formdata-set fn Set(&self, name: USVString, str_value: USVString) { - self.data.borrow_mut().insert(LocalName::from(name.0.clone()), vec![FormDatum { - ty: DOMString::from("string"), - name: DOMString::from(name.0), - value: FormDatumValue::String(DOMString::from(str_value.0)), - }]); + let mut data = self.data.borrow_mut(); + let local_name = LocalName::from(name.0.clone()); + + data.retain(|(datum_name, _)| datum_name != &local_name); + + data.push(( + local_name, + FormDatum { + ty: DOMString::from("string"), + name: DOMString::from(name.0), + value: FormDatumValue::String(DOMString::from(str_value.0)), + }, + )); } #[allow(unrooted_must_root)] // https://xhr.spec.whatwg.org/#dom-formdata-set fn Set_(&self, name: USVString, blob: &Blob, filename: Option<USVString>) { - self.data.borrow_mut().insert(LocalName::from(name.0.clone()), vec![FormDatum { - ty: DOMString::from("file"), - name: DOMString::from(name.0), - value: FormDatumValue::File(Root::from_ref(&*self.create_an_entry(blob, filename))), - }]); - } + let mut data = self.data.borrow_mut(); + let local_name = LocalName::from(name.0.clone()); -} + data.retain(|(datum_name, _)| datum_name != &local_name); + data.push(( + LocalName::from(name.0.clone()), + FormDatum { + ty: DOMString::from("file"), + name: DOMString::from(name.0), + value: FormDatumValue::File(DomRoot::from_ref( + &*self.create_an_entry(blob, filename), + )), + }, + )); + } +} impl FormData { // https://xhr.spec.whatwg.org/#create-an-entry - // Steps 3-4. - fn create_an_entry(&self, blob: &Blob, opt_filename: Option<USVString>) -> Root<File> { + fn create_an_entry(&self, blob: &Blob, opt_filename: Option<USVString>) -> DomRoot<File> { + // Steps 3-4 let name = match opt_filename { Some(filename) => DOMString::from(filename.0), - None if blob.downcast::<File>().is_none() => DOMString::from("blob"), - None => DOMString::from(""), + None => match blob.downcast::<File>() { + None => DOMString::from("blob"), + // If it is already a file and no filename was given, + // then neither step 3 nor step 4 happens, so instead of + // creating a new File object we use the existing one. + Some(file) => { + return DomRoot::from_ref(file); + }, + }, }; let bytes = blob.get_bytes().unwrap_or(vec![]); - File::new(&self.global(), BlobImpl::new_from_bytes(bytes), name, None, &blob.type_string()) + File::new( + &self.global(), + BlobImpl::new_from_bytes(bytes, blob.type_string()), + name, + None, + ) } pub fn datums(&self) -> Vec<FormDatum> { - self.data.borrow().values() - .flat_map(|value| value.iter()) - .map(|value| value.clone()) + self.data + .borrow() + .iter() + .map(|(_, datum)| datum.clone()) .collect() } } @@ -173,28 +219,21 @@ impl Iterable for FormData { type Value = FileOrUSVString; fn get_iterable_length(&self) -> u32 { - self.data.borrow().values().map(|value| value.len()).sum::<usize>() as u32 + self.data.borrow().len() as u32 } fn get_value_at_index(&self, n: u32) -> FileOrUSVString { let data = self.data.borrow(); - let value = &data.values() - .flat_map(|value| value.iter()) - .nth(n as usize) - .unwrap() - .value; - match *value { + let datum = &data.get(n as usize).unwrap().1; + match &datum.value { FormDatumValue::String(ref s) => FileOrUSVString::USVString(USVString(s.to_string())), - FormDatumValue::File(ref b) => FileOrUSVString::File(Root::from_ref(&*b)), + FormDatumValue::File(ref b) => FileOrUSVString::File(DomRoot::from_ref(b)), } } fn get_key_at_index(&self, n: u32) -> USVString { let data = self.data.borrow(); - let value = &data.iter() - .flat_map(|(key, value)| iter::repeat(key).take(value.len())) - .nth(n as usize) - .unwrap(); - USVString(value.to_string()) + let key = &data.get(n as usize).unwrap().0; + USVString(key.to_string()) } } diff --git a/components/script/dom/formdataevent.rs b/components/script/dom/formdataevent.rs new file mode 100644 index 00000000000..263d176f66f --- /dev/null +++ b/components/script/dom/formdataevent.rs @@ -0,0 +1,81 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::FormDataEventBinding; +use crate::dom::bindings::codegen::Bindings::FormDataEventBinding::FormDataEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::event::{EventBubbles, EventCancelable}; +use crate::dom::formdata::FormData; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct FormDataEvent { + event: Event, + form_data: Dom<FormData>, +} + +impl FormDataEvent { + pub fn new( + global: &GlobalScope, + type_: Atom, + can_bubble: EventBubbles, + cancelable: EventCancelable, + form_data: &FormData, + ) -> DomRoot<FormDataEvent> { + let ev = reflect_dom_object( + Box::new(FormDataEvent { + event: Event::new_inherited(), + form_data: Dom::from_ref(form_data), + }), + global, + ); + + { + let event = ev.upcast::<Event>(); + event.init_event(type_, bool::from(can_bubble), bool::from(cancelable)); + } + ev + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &FormDataEventBinding::FormDataEventInit, + ) -> Fallible<DomRoot<FormDataEvent>> { + let bubbles = EventBubbles::from(init.parent.bubbles); + let cancelable = EventCancelable::from(init.parent.cancelable); + + let event = FormDataEvent::new( + &window.global(), + Atom::from(type_), + bubbles, + cancelable, + &*init.formData.clone(), + ); + + Ok(event) + } +} + +impl FormDataEventMethods for FormDataEvent { + // https://html.spec.whatwg.org/multipage/#dom-formdataevent-formdata + fn FormData(&self) -> DomRoot<FormData> { + DomRoot::from_ref(&*self.form_data) + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/gainnode.rs b/components/script/dom/gainnode.rs new file mode 100644 index 00000000000..86a957734dd --- /dev/null +++ b/components/script/dom/gainnode.rs @@ -0,0 +1,96 @@ +/* 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 crate::dom::audionode::AudioNode; +use crate::dom::audioparam::AudioParam; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate; +use crate::dom::bindings::codegen::Bindings::GainNodeBinding::{GainNodeMethods, GainOptions}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::gain_node::GainNodeOptions; +use servo_media::audio::node::AudioNodeInit; +use servo_media::audio::param::ParamType; +use std::f32; + +#[dom_struct] +pub struct GainNode { + node: AudioNode, + gain: Dom<AudioParam>, +} + +impl GainNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + window: &Window, + context: &BaseAudioContext, + options: &GainOptions, + ) -> Fallible<GainNode> { + let node_options = + options + .parent + .unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers); + let node = AudioNode::new_inherited( + AudioNodeInit::GainNode(options.into()), + context, + node_options, + 1, // inputs + 1, // outputs + )?; + let gain = AudioParam::new( + window, + context, + node.node_id(), + ParamType::Gain, + AutomationRate::A_rate, + *options.gain, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + Ok(GainNode { + node, + gain: Dom::from_ref(&gain), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &GainOptions, + ) -> Fallible<DomRoot<GainNode>> { + let node = GainNode::new_inherited(window, context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &GainOptions, + ) -> Fallible<DomRoot<GainNode>> { + GainNode::new(window, context, options) + } +} + +impl GainNodeMethods for GainNode { + // https://webaudio.github.io/web-audio-api/#dom-gainnode-gain + fn Gain(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.gain) + } +} + +impl<'a> From<&'a GainOptions> for GainNodeOptions { + fn from(options: &'a GainOptions) -> Self { + Self { + gain: *options.gain, + } + } +} diff --git a/components/script/dom/gamepad.rs b/components/script/dom/gamepad.rs index f9f28fc8c51..b81d6a7b228 100644 --- a/components/script/dom/gamepad.rs +++ b/components/script/dom/gamepad.rs @@ -1,27 +1,24 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use core::nonzero::NonZero; -use dom::bindings::codegen::Bindings::GamepadBinding; -use dom::bindings::codegen::Bindings::GamepadBinding::GamepadMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::gamepadbuttonlist::GamepadButtonList; -use dom::gamepadevent::{GamepadEvent, GamepadEventType}; -use dom::globalscope::GlobalScope; -use dom::vrpose::VRPose; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::GamepadBinding::GamepadHand; +use crate::dom::bindings::codegen::Bindings::GamepadBinding::GamepadMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::gamepadbuttonlist::GamepadButtonList; +use crate::dom::gamepadevent::{GamepadEvent, GamepadEventType}; +use crate::dom::gamepadpose::GamepadPose; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{Heap, JSContext, JSObject}; -use js::typedarray::{Float64Array, CreateWith}; +use js::jsapi::{Heap, JSObject}; use std::cell::Cell; -use std::ptr; -use webvr_traits::{WebVRGamepadData, WebVRGamepadHand, WebVRGamepadState}; +use std::ptr::NonNull; #[dom_struct] pub struct Gamepad { @@ -32,26 +29,28 @@ pub struct Gamepad { connected: Cell<bool>, timestamp: Cell<f64>, mapping_type: String, + #[ignore_malloc_size_of = "mozjs"] axes: Heap<*mut JSObject>, - buttons: JS<GamepadButtonList>, - pose: Option<JS<VRPose>>, - #[ignore_heap_size_of = "Defined in rust-webvr"] - hand: WebVRGamepadHand, - display_id: u32 + buttons: Dom<GamepadButtonList>, + pose: Option<Dom<GamepadPose>>, + #[ignore_malloc_size_of = "Defined in rust-webvr"] + hand: GamepadHand, } +// TODO: support gamepad discovery +#[allow(dead_code)] impl Gamepad { - fn new_inherited(gamepad_id: u32, - id: String, - index: i32, - connected: bool, - timestamp: f64, - mapping_type: String, - axes: *mut JSObject, - buttons: &GamepadButtonList, - pose: Option<&VRPose>, - hand: WebVRGamepadHand, - display_id: u32) -> Gamepad { + fn new_inherited( + gamepad_id: u32, + id: String, + index: i32, + connected: bool, + timestamp: f64, + mapping_type: String, + buttons: &GamepadButtonList, + pose: Option<&GamepadPose>, + hand: GamepadHand, + ) -> Gamepad { Self { reflector_: Reflector::new(), gamepad_id: gamepad_id, @@ -60,44 +59,12 @@ impl Gamepad { connected: Cell::new(connected), timestamp: Cell::new(timestamp), mapping_type: mapping_type, - axes: Heap::new(axes), - buttons: JS::from_ref(buttons), - pose: pose.map(JS::from_ref), + axes: Heap::default(), + buttons: Dom::from_ref(buttons), + pose: pose.map(Dom::from_ref), hand: hand, - display_id: display_id } } - - #[allow(unsafe_code)] - pub fn new_from_vr(global: &GlobalScope, - index: i32, - data: &WebVRGamepadData, - state: &WebVRGamepadState) -> Root<Gamepad> { - let buttons = GamepadButtonList::new_from_vr(&global, &state.buttons); - let pose = VRPose::new(&global, &state.pose); - let cx = global.get_cx(); - rooted!(in (cx) let mut axes = ptr::null_mut()); - unsafe { - let _ = Float64Array::create(cx, - CreateWith::Slice(&state.axes), - axes.handle_mut()); - } - - reflect_dom_object(box Gamepad::new_inherited(state.gamepad_id, - data.name.clone(), - index, - state.connected, - state.timestamp, - "".into(), - axes.get(), - &buttons, - Some(&pose), - data.hand.clone(), - data.display_id), - global, - GamepadBinding::Wrap) - - } } impl GamepadMethods for Gamepad { @@ -128,54 +95,29 @@ impl GamepadMethods for Gamepad { #[allow(unsafe_code)] // https://w3c.github.io/gamepad/#dom-gamepad-axes - unsafe fn Axes(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> { - NonZero::new(self.axes.get()) + fn Axes(&self, _cx: JSContext) -> NonNull<JSObject> { + unsafe { NonNull::new_unchecked(self.axes.get()) } } // https://w3c.github.io/gamepad/#dom-gamepad-buttons - fn Buttons(&self) -> Root<GamepadButtonList> { - Root::from_ref(&*self.buttons) + fn Buttons(&self) -> DomRoot<GamepadButtonList> { + DomRoot::from_ref(&*self.buttons) } // https://w3c.github.io/gamepad/extensions.html#gamepadhand-enum - fn Hand(&self) -> DOMString { - let value = match self.hand { - WebVRGamepadHand::Unknown => "", - WebVRGamepadHand::Left => "left", - WebVRGamepadHand::Right => "right" - }; - value.into() + fn Hand(&self) -> GamepadHand { + self.hand } // https://w3c.github.io/gamepad/extensions.html#dom-gamepad-pose - fn GetPose(&self) -> Option<Root<VRPose>> { - self.pose.as_ref().map(|p| Root::from_ref(&**p)) - } - - // https://w3c.github.io/webvr/spec/1.1/#gamepad-getvrdisplays-attribute - fn DisplayId(&self) -> u32 { - self.display_id + fn GetPose(&self) -> Option<DomRoot<GamepadPose>> { + self.pose.as_ref().map(|p| DomRoot::from_ref(&**p)) } } +// TODO: support gamepad discovery +#[allow(dead_code)] impl Gamepad { - #[allow(unsafe_code)] - pub fn update_from_vr(&self, state: &WebVRGamepadState) { - self.timestamp.set(state.timestamp); - unsafe { - let cx = self.global().get_cx(); - typedarray!(in(cx) let axes: Float64Array = self.axes.get()); - if let Ok(mut array) = axes { - array.update(&state.axes); - } - } - self.buttons.sync_from_vr(&state.buttons); - if let Some(ref pose) = self.pose { - pose.update(&state.pose); - } - self.update_connected(state.connected); - } - pub fn gamepad_id(&self) -> u32 { self.gamepad_id } @@ -201,6 +143,8 @@ impl Gamepad { pub fn notify_event(&self, event_type: GamepadEventType) { let event = GamepadEvent::new_with_type(&self.global(), event_type, &self); - event.upcast::<Event>().fire(self.global().as_window().upcast::<EventTarget>()); + event + .upcast::<Event>() + .fire(self.global().as_window().upcast::<EventTarget>()); } } diff --git a/components/script/dom/gamepadbutton.rs b/components/script/dom/gamepadbutton.rs index ff0c7271e5b..e40e7fb4f05 100644 --- a/components/script/dom/gamepadbutton.rs +++ b/components/script/dom/gamepadbutton.rs @@ -1,13 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::GamepadButtonBinding; -use dom::bindings::codegen::Bindings::GamepadButtonBinding::GamepadButtonMethods; -use dom::bindings::js::Root; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::GamepadButtonBinding::GamepadButtonMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; use std::cell::Cell; @@ -19,6 +18,8 @@ pub struct GamepadButton { value: Cell<f64>, } +// TODO: support gamepad discovery +#[allow(dead_code)] impl GamepadButton { pub fn new_inherited(pressed: bool, touched: bool) -> GamepadButton { Self { @@ -29,10 +30,11 @@ impl GamepadButton { } } - pub fn new(global: &GlobalScope, pressed: bool, touched: bool) -> Root<GamepadButton> { - reflect_dom_object(box GamepadButton::new_inherited(pressed, touched), - global, - GamepadButtonBinding::Wrap) + pub fn new(global: &GlobalScope, pressed: bool, touched: bool) -> DomRoot<GamepadButton> { + reflect_dom_object( + Box::new(GamepadButton::new_inherited(pressed, touched)), + global, + ) } } @@ -53,6 +55,8 @@ impl GamepadButtonMethods for GamepadButton { } } +// TODO: support gamepad discovery +#[allow(dead_code)] impl GamepadButton { pub fn update(&self, pressed: bool, touched: bool) { self.pressed.set(pressed); diff --git a/components/script/dom/gamepadbuttonlist.rs b/components/script/dom/gamepadbuttonlist.rs index 5ac25504009..4d9e86c45ef 100644 --- a/components/script/dom/gamepadbuttonlist.rs +++ b/components/script/dom/gamepadbuttonlist.rs @@ -1,46 +1,28 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::GamepadButtonListBinding; -use dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods; -use dom::bindings::js::{JS, Root, RootedReference}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::gamepadbutton::GamepadButton; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::gamepadbutton::GamepadButton; use dom_struct::dom_struct; -use webvr_traits::WebVRGamepadButton; // https://w3c.github.io/gamepad/#gamepadbutton-interface #[dom_struct] pub struct GamepadButtonList { reflector_: Reflector, - list: Vec<JS<GamepadButton>> + list: Vec<Dom<GamepadButton>>, } +// TODO: support gamepad discovery +#[allow(dead_code)] impl GamepadButtonList { #[allow(unrooted_must_root)] fn new_inherited(list: &[&GamepadButton]) -> GamepadButtonList { GamepadButtonList { reflector_: Reflector::new(), - list: list.iter().map(|button| JS::from_ref(*button)).collect(), - } - } - - pub fn new_from_vr(global: &GlobalScope, buttons: &[WebVRGamepadButton]) -> Root<GamepadButtonList> { - rooted_vec!(let list <- buttons.iter() - .map(|btn| GamepadButton::new(&global, btn.pressed, btn.touched))); - - reflect_dom_object(box GamepadButtonList::new_inherited(list.r()), - global, - GamepadButtonListBinding::Wrap) - } - - pub fn sync_from_vr(&self, vr_buttons: &[WebVRGamepadButton]) { - let mut index = 0; - for btn in vr_buttons { - self.list.get(index).as_ref().unwrap().update(btn.pressed, btn.touched); - index += 1; + list: list.iter().map(|button| Dom::from_ref(*button)).collect(), } } } @@ -52,12 +34,14 @@ impl GamepadButtonListMethods for GamepadButtonList { } // https://w3c.github.io/gamepad/#dom-gamepad-buttons - fn Item(&self, index: u32) -> Option<Root<GamepadButton>> { - self.list.get(index as usize).map(|button| Root::from_ref(&**button)) + fn Item(&self, index: u32) -> Option<DomRoot<GamepadButton>> { + self.list + .get(index as usize) + .map(|button| DomRoot::from_ref(&**button)) } // https://w3c.github.io/gamepad/#dom-gamepad-buttons - fn IndexedGetter(&self, index: u32) -> Option<Root<GamepadButton>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<GamepadButton>> { self.Item(index) } } diff --git a/components/script/dom/gamepadevent.rs b/components/script/dom/gamepadevent.rs index f6690981a57..a6a3ad9e57c 100644 --- a/components/script/dom/gamepadevent.rs +++ b/components/script/dom/gamepadevent.rs @@ -1,50 +1,49 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::GamepadEventBinding; -use dom::bindings::codegen::Bindings::GamepadEventBinding::GamepadEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::gamepad::Gamepad; -use dom::globalscope::GlobalScope; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::GamepadEventBinding; +use crate::dom::bindings::codegen::Bindings::GamepadEventBinding::GamepadEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::gamepad::Gamepad; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; #[dom_struct] pub struct GamepadEvent { event: Event, - gamepad: JS<Gamepad>, + gamepad: Dom<Gamepad>, } pub enum GamepadEventType { Connected, - Disconnected + Disconnected, } impl GamepadEvent { fn new_inherited(gamepad: &Gamepad) -> GamepadEvent { GamepadEvent { event: Event::new_inherited(), - gamepad: JS::from_ref(gamepad), + gamepad: Dom::from_ref(gamepad), } } - pub fn new(global: &GlobalScope, - type_: Atom, - bubbles: bool, - cancelable: bool, - gamepad: &Gamepad) - -> Root<GamepadEvent> { - let ev = reflect_dom_object(box GamepadEvent::new_inherited(&gamepad), - global, - GamepadEventBinding::Wrap); + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + gamepad: &Gamepad, + ) -> DomRoot<GamepadEvent> { + let ev = reflect_dom_object(Box::new(GamepadEvent::new_inherited(&gamepad)), global); { let event = ev.upcast::<Event>(); event.init_event(type_, bubbles, cancelable); @@ -52,37 +51,40 @@ impl GamepadEvent { ev } - pub fn new_with_type(global: &GlobalScope, event_type: GamepadEventType, gamepad: &Gamepad) - -> Root<GamepadEvent> { + pub fn new_with_type( + global: &GlobalScope, + event_type: GamepadEventType, + gamepad: &Gamepad, + ) -> DomRoot<GamepadEvent> { let name = match event_type { GamepadEventType::Connected => "gamepadconnected", - GamepadEventType::Disconnected => "gamepaddisconnected" + GamepadEventType::Disconnected => "gamepaddisconnected", }; - GamepadEvent::new(&global, - name.into(), - false, - false, - &gamepad) + GamepadEvent::new(&global, name.into(), false, false, &gamepad) } // https://w3c.github.io/gamepad/#gamepadevent-interface - pub fn Constructor(window: &Window, - type_: DOMString, - init: &GamepadEventBinding::GamepadEventInit) - -> Fallible<Root<GamepadEvent>> { - Ok(GamepadEvent::new(&window.global(), - Atom::from(type_), - init.parent.bubbles, - init.parent.cancelable, - &init.gamepad)) + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &GamepadEventBinding::GamepadEventInit, + ) -> Fallible<DomRoot<GamepadEvent>> { + Ok(GamepadEvent::new( + &window.global(), + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + &init.gamepad, + )) } } impl GamepadEventMethods for GamepadEvent { // https://w3c.github.io/gamepad/#gamepadevent-interface - fn Gamepad(&self) -> Root<Gamepad> { - Root::from_ref(&*self.gamepad) + fn Gamepad(&self) -> DomRoot<Gamepad> { + DomRoot::from_ref(&*self.gamepad) } // https://dom.spec.whatwg.org/#dom-event-istrusted diff --git a/components/script/dom/gamepadlist.rs b/components/script/dom/gamepadlist.rs index dd5bdd757d9..ed551df4c35 100644 --- a/components/script/dom/gamepadlist.rs +++ b/components/script/dom/gamepadlist.rs @@ -1,41 +1,45 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::GamepadListBinding; -use dom::bindings::codegen::Bindings::GamepadListBinding::GamepadListMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::gamepad::Gamepad; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GamepadListBinding::GamepadListMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::gamepad::Gamepad; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; // https://www.w3.org/TR/gamepad/ #[dom_struct] pub struct GamepadList { reflector_: Reflector, - list: DOMRefCell<Vec<JS<Gamepad>>> + list: DomRefCell<Vec<Dom<Gamepad>>>, } +// TODO: support gamepad discovery +#[allow(dead_code)] impl GamepadList { fn new_inherited(list: &[&Gamepad]) -> GamepadList { GamepadList { reflector_: Reflector::new(), - list: DOMRefCell::new(list.iter().map(|g| JS::from_ref(&**g)).collect()) + list: DomRefCell::new(list.iter().map(|g| Dom::from_ref(&**g)).collect()), } } - pub fn new(global: &GlobalScope, list: &[&Gamepad]) -> Root<GamepadList> { - reflect_dom_object(box GamepadList::new_inherited(list), - global, - GamepadListBinding::Wrap) + pub fn new(global: &GlobalScope, list: &[&Gamepad]) -> DomRoot<GamepadList> { + reflect_dom_object(Box::new(GamepadList::new_inherited(list)), global) } - pub fn add_if_not_exists(&self, gamepads: &[Root<Gamepad>]) { + pub fn add_if_not_exists(&self, gamepads: &[DomRoot<Gamepad>]) { for gamepad in gamepads { - if !self.list.borrow().iter().any(|g| g.gamepad_id() == gamepad.gamepad_id()) { - self.list.borrow_mut().push(JS::from_ref(&*gamepad)); + if !self + .list + .borrow() + .iter() + .any(|g| g.gamepad_id() == gamepad.gamepad_id()) + { + self.list.borrow_mut().push(Dom::from_ref(&*gamepad)); // Ensure that the gamepad has the correct index gamepad.update_index(self.list.borrow().len() as i32 - 1); } @@ -50,12 +54,15 @@ impl GamepadListMethods for GamepadList { } // https://w3c.github.io/gamepad/#dom-navigator-getgamepads - fn Item(&self, index: u32) -> Option<Root<Gamepad>> { - self.list.borrow().get(index as usize).map(|gamepad| Root::from_ref(&**gamepad)) + fn Item(&self, index: u32) -> Option<DomRoot<Gamepad>> { + self.list + .borrow() + .get(index as usize) + .map(|gamepad| DomRoot::from_ref(&**gamepad)) } // https://w3c.github.io/gamepad/#dom-navigator-getgamepads - fn IndexedGetter(&self, index: u32) -> Option<Root<Gamepad>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Gamepad>> { self.Item(index) } } diff --git a/components/script/dom/gamepadpose.rs b/components/script/dom/gamepadpose.rs new file mode 100644 index 00000000000..10d770ea68a --- /dev/null +++ b/components/script/dom/gamepadpose.rs @@ -0,0 +1,131 @@ +/* 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 crate::dom::bindings::codegen::Bindings::GamepadPoseBinding::GamepadPoseMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use js::jsapi::{Heap, JSObject}; +use js::typedarray::{CreateWith, Float32Array}; +use std::ptr; +use std::ptr::NonNull; + +#[dom_struct] +pub struct GamepadPose { + reflector_: Reflector, + #[ignore_malloc_size_of = "mozjs"] + position: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "mozjs"] + orientation: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "mozjs"] + linear_vel: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "mozjs"] + angular_vel: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "mozjs"] + linear_acc: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "mozjs"] + angular_acc: Heap<*mut JSObject>, +} + +// TODO: support gamepad discovery +#[allow(dead_code)] +#[allow(unsafe_code)] +fn update_or_create_typed_array(cx: JSContext, src: Option<&[f32]>, dst: &Heap<*mut JSObject>) { + match src { + Some(data) => { + if dst.get().is_null() { + rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); + let _ = unsafe { + Float32Array::create(*cx, CreateWith::Slice(data), array.handle_mut()) + }; + (*dst).set(array.get()); + } else { + typedarray!(in(*cx) let array: Float32Array = dst.get()); + if let Ok(mut array) = array { + unsafe { array.update(data) }; + } + } + }, + None => { + if !dst.get().is_null() { + dst.set(ptr::null_mut()); + } + }, + } +} + +#[inline] +#[allow(unsafe_code)] +fn heap_to_option(heap: &Heap<*mut JSObject>) -> Option<NonNull<JSObject>> { + let js_object = heap.get(); + if js_object.is_null() { + None + } else { + unsafe { Some(NonNull::new_unchecked(js_object)) } + } +} + +// TODO: support gamepad discovery +#[allow(dead_code)] +impl GamepadPose { + fn new_inherited() -> GamepadPose { + GamepadPose { + reflector_: Reflector::new(), + position: Heap::default(), + orientation: Heap::default(), + linear_vel: Heap::default(), + angular_vel: Heap::default(), + linear_acc: Heap::default(), + angular_acc: Heap::default(), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<GamepadPose> { + reflect_dom_object(Box::new(GamepadPose::new_inherited()), global) + } +} + +impl GamepadPoseMethods for GamepadPose { + // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-position + fn GetPosition(&self, _cx: JSContext) -> Option<NonNull<JSObject>> { + heap_to_option(&self.position) + } + + // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-hasposition + fn HasPosition(&self) -> bool { + !self.position.get().is_null() + } + + // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-linearvelocity + fn GetLinearVelocity(&self, _cx: JSContext) -> Option<NonNull<JSObject>> { + heap_to_option(&self.linear_vel) + } + + // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-linearacceleration + fn GetLinearAcceleration(&self, _cx: JSContext) -> Option<NonNull<JSObject>> { + heap_to_option(&self.linear_acc) + } + + // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-orientation + fn GetOrientation(&self, _cx: JSContext) -> Option<NonNull<JSObject>> { + heap_to_option(&self.orientation) + } + + // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-orientation + fn HasOrientation(&self) -> bool { + !self.orientation.get().is_null() + } + + // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-angularvelocity + fn GetAngularVelocity(&self, _cx: JSContext) -> Option<NonNull<JSObject>> { + heap_to_option(&self.angular_vel) + } + + // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-angularacceleration + fn GetAngularAcceleration(&self, _cx: JSContext) -> Option<NonNull<JSObject>> { + heap_to_option(&self.angular_acc) + } +} diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 931d1f576aa..6e576c036f7 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -1,59 +1,196 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::conversions::root_from_object; -use dom::bindings::error::{ErrorInfo, report_pending_exception}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::DomObject; -use dom::bindings::settings_stack::{AutoEntryScript, entry_global, incumbent_global}; -use dom::bindings::str::DOMString; -use dom::crypto::Crypto; -use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; -use dom::errorevent::ErrorEvent; -use dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; -use dom::eventtarget::EventTarget; -use dom::window::Window; -use dom::workerglobalscope::WorkerGlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::{DomRefCell, RefMut}; +use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods; +use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSourceBinding::EventSourceMethods; +use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{ + ImageBitmapOptions, ImageBitmapSource, +}; +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionState; +use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods; +use crate::dom::bindings::conversions::{root_from_object, root_from_object_static}; +use crate::dom::bindings::error::{report_pending_exception, Error, ErrorInfo}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::settings_stack::{entry_global, incumbent_global, AutoEntryScript}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::utils::to_frozen_array; +use crate::dom::bindings::weakref::{DOMTracker, WeakRef}; +use crate::dom::blob::Blob; +use crate::dom::broadcastchannel::BroadcastChannel; +use crate::dom::crypto::Crypto; +use crate::dom::dedicatedworkerglobalscope::{ + DedicatedWorkerControlMsg, DedicatedWorkerGlobalScope, +}; +use crate::dom::errorevent::ErrorEvent; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; +use crate::dom::eventsource::EventSource; +use crate::dom::eventtarget::EventTarget; +use crate::dom::file::File; +use crate::dom::gpudevice::GPUDevice; +use crate::dom::htmlscriptelement::{ScriptId, SourceCode}; +use crate::dom::identityhub::Identities; +use crate::dom::imagebitmap::ImageBitmap; +use crate::dom::messageevent::MessageEvent; +use crate::dom::messageport::MessagePort; +use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; +use crate::dom::performance::Performance; +use crate::dom::performanceobserver::VALID_ENTRY_TYPES; +use crate::dom::promise::Promise; +use crate::dom::readablestream::{ExternalUnderlyingSource, ReadableStream}; +use crate::dom::serviceworker::ServiceWorker; +use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; +use crate::dom::window::Window; +use crate::dom::workerglobalscope::WorkerGlobalScope; +use crate::dom::workletglobalscope::WorkletGlobalScope; +use crate::microtask::{Microtask, MicrotaskQueue, UserMicrotask}; +use crate::realms::{enter_realm, AlreadyInRealm, InRealm}; +use crate::script_module::{DynamicModuleList, ModuleTree}; +use crate::script_module::{ModuleScript, ScriptFetchOptions}; +use crate::script_runtime::{ + CommonScriptMsg, ContextForRequestInterrupt, JSContext as SafeJSContext, ScriptChan, ScriptPort, +}; +use crate::script_thread::{MainThreadScriptChan, ScriptThread}; +use crate::task::TaskCanceller; +use crate::task_source::dom_manipulation::DOMManipulationTaskSource; +use crate::task_source::file_reading::FileReadingTaskSource; +use crate::task_source::networking::NetworkingTaskSource; +use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; +use crate::task_source::port_message::PortMessageQueue; +use crate::task_source::remote_event::RemoteEventTaskSource; +use crate::task_source::timer::TimerTaskSource; +use crate::task_source::websocket::WebsocketTaskSource; +use crate::task_source::TaskSource; +use crate::task_source::TaskSourceName; +use crate::timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle}; +use crate::timers::{OneshotTimers, TimerCallback}; +use content_security_policy::CspList; +use crossbeam_channel::Sender; +use devtools_traits::{PageError, ScriptToDevtoolsControlMsg}; use dom_struct::dom_struct; -use ipc_channel::ipc::IpcSender; -use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL}; -use js::glue::{IsWrapper, UnwrapObject}; -use js::jsapi::{CurrentGlobalOrNull, GetGlobalForObjectCrossCompartment}; -use js::jsapi::{HandleValue, Evaluate2, JSAutoCompartment, JSContext}; -use js::jsapi::{JSObject, JS_GetContext}; -use js::jsapi::{JS_GetObjectRuntime, MutableHandleValue}; +use embedder_traits::EmbedderMsg; +use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::router::ROUTER; +use js::glue::{IsWrapper, UnwrapObjectDynamic}; +use js::jsapi::Compile1; +use js::jsapi::SetScriptPrivate; +use js::jsapi::{CurrentGlobalOrNull, GetNonCCWObjectGlobal}; +use js::jsapi::{HandleObject, Heap}; +use js::jsapi::{JSContext, JSObject, JSScript}; +use js::jsval::PrivateValue; +use js::jsval::{JSVal, UndefinedValue}; use js::panic::maybe_resume_unwind; -use js::rust::{CompileOptionsWrapper, Runtime, get_object_class}; -use libc; -use microtask::Microtask; -use msg::constellation_msg::PipelineId; -use net_traits::{CoreResourceThread, ResourceThreads, IpcSend}; -use profile_traits::{mem, time}; -use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort}; -use script_thread::{MainThreadScriptChan, RunnableWrapper, ScriptThread}; -use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEvent}; +use js::rust::transform_str_to_source_text; +use js::rust::wrappers::{JS_ExecuteScript, JS_GetScriptPrivate}; +use js::rust::{get_object_class, CompileOptionsWrapper, ParentRuntime, Runtime}; +use js::rust::{HandleValue, MutableHandleValue}; +use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL}; +use msg::constellation_msg::{ + BlobId, BroadcastChannelRouterId, MessagePortId, MessagePortRouterId, PipelineId, + ServiceWorkerId, ServiceWorkerRegistrationId, +}; +use net_traits::blob_url_store::{get_blob_origin, BlobBuf}; +use net_traits::filemanager_thread::{ + FileManagerResult, FileManagerThreadMsg, ReadFileProgress, RelativePos, +}; +use net_traits::image_cache::ImageCache; +use net_traits::request::Referrer; +use net_traits::response::HttpsState; +use net_traits::{CoreResourceMsg, CoreResourceThread, IpcSend, ResourceThreads}; +use parking_lot::Mutex; +use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_time}; +use script_traits::serializable::{BlobData, BlobImpl, FileBlob}; +use script_traits::transferable::MessagePortImpl; +use script_traits::{ + BroadcastMsg, MessagePortMsg, MsDuration, PortMessageTask, ScriptMsg, + ScriptToConstellationChan, TimerEvent, +}; use script_traits::{TimerEventId, TimerSchedulerMsg, TimerSource}; -use servo_url::{MutableOrigin, ServoUrl}; +use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; +use std::borrow::Cow; use std::cell::Cell; -use std::collections::HashMap; use std::collections::hash_map::Entry; -use std::ffi::CString; -use task_source::file_reading::FileReadingTaskSource; -use task_source::networking::NetworkingTaskSource; -use time::{Timespec, get_time}; -use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle}; -use timers::{OneshotTimers, TimerCallback}; +use std::collections::{HashMap, VecDeque}; +use std::mem; +use std::ops::Index; +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread::JoinHandle; +use time::{get_time, Timespec}; +use uuid::Uuid; +use webgpu::{identity::WebGPUOpResult, ErrorScopeId, WebGPUDevice}; + +#[derive(JSTraceable)] +pub struct AutoCloseWorker { + /// https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-closing + closing: Arc<AtomicBool>, + /// A handle to join on the worker thread. + join_handle: Option<JoinHandle<()>>, + /// A sender of control messages, + /// currently only used to signal shutdown. + control_sender: Sender<DedicatedWorkerControlMsg>, + /// The context to request an interrupt on the worker thread. + context: ContextForRequestInterrupt, +} + +impl Drop for AutoCloseWorker { + /// <https://html.spec.whatwg.org/multipage/#terminate-a-worker> + fn drop(&mut self) { + // Step 1. + self.closing.store(true, Ordering::SeqCst); + + if self + .control_sender + .send(DedicatedWorkerControlMsg::Exit) + .is_err() + { + warn!("Couldn't send an exit message to a dedicated worker."); + } + + self.context.request_interrupt(); + + // TODO: step 2 and 3. + // Step 4 is unnecessary since we don't use actual ports for dedicated workers. + if self + .join_handle + .take() + .expect("No handle to join on worker.") + .join() + .is_err() + { + warn!("Failed to join on dedicated worker thread."); + } + } +} #[dom_struct] pub struct GlobalScope { eventtarget: EventTarget, - crypto: MutNullableJS<Crypto>, - next_worker_id: Cell<WorkerId>, + crypto: MutNullableDom<Crypto>, + + /// The message-port router id for this global, if it is managing ports. + message_port_state: DomRefCell<MessagePortState>, + + /// The broadcast channels state this global, if it is managing any. + broadcast_channel_state: DomRefCell<BroadcastChannelState>, + + /// The blobs managed by this global, if any. + blob_state: DomRefCell<BlobState>, + + /// <https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-registration-object-map> + registration_map: + DomRefCell<HashMap<ServiceWorkerRegistrationId, Dom<ServiceWorkerRegistration>>>, + + /// <https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-object-map> + worker_map: DomRefCell<HashMap<ServiceWorkerId, Dom<ServiceWorker>>>, /// Pipeline id associated with this global. pipeline_id: PipelineId, @@ -63,126 +200,2029 @@ pub struct GlobalScope { devtools_wants_updates: Cell<bool>, /// Timers used by the Console API. - console_timers: DOMRefCell<HashMap<DOMString, u64>>, + console_timers: DomRefCell<HashMap<DOMString, u64>>, + + /// module map is used when importing JavaScript modules + /// https://html.spec.whatwg.org/multipage/#concept-settings-object-module-map + #[ignore_malloc_size_of = "mozjs"] + module_map: DomRefCell<HashMap<ServoUrl, Rc<ModuleTree>>>, + + #[ignore_malloc_size_of = "mozjs"] + inline_module_map: DomRefCell<HashMap<ScriptId, Rc<ModuleTree>>>, /// For providing instructions to an optional devtools server. - #[ignore_heap_size_of = "channels are hard"] + #[ignore_malloc_size_of = "channels are hard"] devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, /// For sending messages to the memory profiler. - #[ignore_heap_size_of = "channels are hard"] - mem_profiler_chan: mem::ProfilerChan, + #[ignore_malloc_size_of = "channels are hard"] + mem_profiler_chan: profile_mem::ProfilerChan, /// For sending messages to the time profiler. - #[ignore_heap_size_of = "channels are hard"] - time_profiler_chan: time::ProfilerChan, + #[ignore_malloc_size_of = "channels are hard"] + time_profiler_chan: profile_time::ProfilerChan, /// A handle for communicating messages to the constellation thread. - #[ignore_heap_size_of = "channels are hard"] - constellation_chan: IpcSender<ConstellationMsg>, + #[ignore_malloc_size_of = "channels are hard"] + script_to_constellation_chan: ScriptToConstellationChan, - #[ignore_heap_size_of = "channels are hard"] + #[ignore_malloc_size_of = "channels are hard"] scheduler_chan: IpcSender<TimerSchedulerMsg>, - /// https://html.spec.whatwg.org/multipage/#in-error-reporting-mode + /// <https://html.spec.whatwg.org/multipage/#in-error-reporting-mode> in_error_reporting_mode: Cell<bool>, /// Associated resource threads for use by DOM objects like XMLHttpRequest, /// including resource_thread, filemanager_thread and storage_thread resource_threads: ResourceThreads, + /// The mechanism by which time-outs and intervals are scheduled. + /// <https://html.spec.whatwg.org/multipage/#timers> timers: OneshotTimers, + /// Have timers been initialized? + init_timers: Cell<bool>, + /// The origin of the globalscope origin: MutableOrigin, + + /// https://html.spec.whatwg.org/multipage/#concept-environment-creation-url + creation_url: Option<ServoUrl>, + + /// A map for storing the previous permission state read results. + permission_state_invocation_results: DomRefCell<HashMap<String, PermissionState>>, + + /// The microtask queue associated with this global. + /// + /// It is refcounted because windows in the same script thread share the + /// same microtask queue. + /// + /// <https://html.spec.whatwg.org/multipage/#microtask-queue> + #[ignore_malloc_size_of = "Rc<T> is hard"] + microtask_queue: Rc<MicrotaskQueue>, + + /// Vector storing closing references of all workers + #[ignore_malloc_size_of = "Arc"] + list_auto_close_worker: DomRefCell<Vec<AutoCloseWorker>>, + + /// Vector storing references of all eventsources. + event_source_tracker: DOMTracker<EventSource>, + + /// Storage for watching rejected promises waiting for some client to + /// consume their rejection. + /// Promises in this list have been rejected in the last turn of the + /// event loop without the rejection being handled. + /// Note that this can contain nullptrs in place of promises removed because + /// they're consumed before it'd be reported. + /// + /// <https://html.spec.whatwg.org/multipage/#about-to-be-notified-rejected-promises-list> + #[ignore_malloc_size_of = "mozjs"] + uncaught_rejections: DomRefCell<Vec<Box<Heap<*mut JSObject>>>>, + + /// Promises in this list have previously been reported as rejected + /// (because they were in the above list), but the rejection was handled + /// in the last turn of the event loop. + /// + /// <https://html.spec.whatwg.org/multipage/#outstanding-rejected-promises-weak-set> + #[ignore_malloc_size_of = "mozjs"] + consumed_rejections: DomRefCell<Vec<Box<Heap<*mut JSObject>>>>, + + /// True if headless mode. + is_headless: bool, + + /// An optional string allowing the user agent to be set for testing. + user_agent: Cow<'static, str>, + + /// Identity Manager for WebGPU resources + #[ignore_malloc_size_of = "defined in wgpu"] + gpu_id_hub: Arc<Mutex<Identities>>, + + /// WebGPU devices + gpu_devices: DomRefCell<HashMap<WebGPUDevice, Dom<GPUDevice>>>, + + // https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute + #[ignore_malloc_size_of = "mozjs"] + frozen_supported_performance_entry_types: DomRefCell<Option<Heap<JSVal>>>, + + /// currect https state (from previous request) + https_state: Cell<HttpsState>, + + /// The stack of active group labels for the Console APIs. + console_group_stack: DomRefCell<Vec<DOMString>>, + + /// List of ongoing dynamic module imports. + dynamic_modules: DomRefCell<DynamicModuleList>, + + /// Is considered in a secure context + inherited_secure_context: Option<bool>, +} + +/// A wrapper for glue-code between the ipc router and the event-loop. +struct MessageListener { + canceller: TaskCanceller, + task_source: PortMessageQueue, + context: Trusted<GlobalScope>, +} + +/// A wrapper for broadcasts coming in over IPC, and the event-loop. +struct BroadcastListener { + canceller: TaskCanceller, + task_source: DOMManipulationTaskSource, + context: Trusted<GlobalScope>, +} + +/// A wrapper between timer events coming in over IPC, and the event-loop. +struct TimerListener { + canceller: TaskCanceller, + task_source: TimerTaskSource, + context: Trusted<GlobalScope>, +} + +/// A wrapper for the handling of file data received by the ipc router +struct FileListener { + /// State should progress as either of: + /// - Some(Empty) => Some(Receiving) => None + /// - Some(Empty) => None + state: Option<FileListenerState>, + task_source: FileReadingTaskSource, + task_canceller: TaskCanceller, +} + +enum FileListenerCallback { + Promise(Box<dyn Fn(Rc<Promise>, Result<Vec<u8>, Error>) + Send>), + Stream, +} + +enum FileListenerTarget { + Promise(TrustedPromise), + Stream(Trusted<ReadableStream>), +} + +enum FileListenerState { + Empty(FileListenerCallback, FileListenerTarget), + Receiving(Vec<u8>, FileListenerCallback, FileListenerTarget), +} + +#[derive(JSTraceable, MallocSizeOf)] +/// A holder of a weak reference for a DOM blob or file. +pub enum BlobTracker { + /// A weak ref to a DOM file. + File(WeakRef<File>), + /// A weak ref to a DOM blob. + Blob(WeakRef<Blob>), +} + +#[derive(JSTraceable, MallocSizeOf)] +/// The info pertaining to a blob managed by this global. +pub struct BlobInfo { + /// The weak ref to the corresponding DOM object. + tracker: BlobTracker, + /// The data and logic backing the DOM object. + blob_impl: BlobImpl, + /// Whether this blob has an outstanding URL, + /// <https://w3c.github.io/FileAPI/#url>. + has_url: bool, +} + +/// State representing whether this global is currently managing blobs. +#[derive(JSTraceable, MallocSizeOf)] +pub enum BlobState { + /// A map of managed blobs. + Managed(HashMap<BlobId, BlobInfo>), + /// This global is not managing any blobs at this time. + UnManaged, +} + +/// The result of looking-up the data for a Blob, +/// containing either the in-memory bytes, +/// or the file-id. +enum BlobResult { + Bytes(Vec<u8>), + File(Uuid, usize), +} + +/// Data representing a message-port managed by this global. +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct ManagedMessagePort { + /// The DOM port. + dom_port: Dom<MessagePort>, + /// The logic and data backing the DOM port. + /// The option is needed to take out the port-impl + /// as part of its transferring steps, + /// without having to worry about rooting the dom-port. + port_impl: Option<MessagePortImpl>, + /// We keep ports pending when they are first transfer-received, + /// and only add them, and ask the constellation to complete the transfer, + /// in a subsequent task if the port hasn't been re-transfered. + pending: bool, + /// Has the port been closed? If closed, it can be dropped and later GC'ed. + closed: bool, +} + +/// State representing whether this global is currently managing broadcast channels. +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub enum BroadcastChannelState { + /// The broadcast-channel router id for this global, and a queue of managed channels. + /// Step 9, "sort destinations" + /// of https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage + /// requires keeping track of creation order, hence the queue. + Managed( + BroadcastChannelRouterId, + /// The map of channel-name to queue of channels, in order of creation. + HashMap<DOMString, VecDeque<Dom<BroadcastChannel>>>, + ), + /// This global is not managing any broadcast channels at this time. + UnManaged, +} + +/// State representing whether this global is currently managing messageports. +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub enum MessagePortState { + /// The message-port router id for this global, and a map of managed ports. + Managed( + MessagePortRouterId, + HashMap<MessagePortId, ManagedMessagePort>, + ), + /// This global is not managing any ports at this time. + UnManaged, +} + +impl BroadcastListener { + /// Handle a broadcast coming in over IPC, + /// by queueing the appropriate task on the relevant event-loop. + fn handle(&self, event: BroadcastMsg) { + let context = self.context.clone(); + + // Note: strictly speaking we should just queue the message event tasks, + // not queue a task that then queues more tasks. + // This however seems to be hard to avoid in the light of the IPC. + // One can imagine queueing tasks directly, + // for channels that would be in the same script-thread. + let _ = self.task_source.queue_with_canceller( + task!(broadcast_message_event: move || { + let global = context.root(); + // Step 10 of https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage, + // For each BroadcastChannel object destination in destinations, queue a task. + global.broadcast_message_event(event, None); + }), + &self.canceller, + ); + } +} + +impl TimerListener { + /// Handle a timer-event coming-in over IPC, + /// by queuing the appropriate task on the relevant event-loop. + fn handle(&self, event: TimerEvent) { + let context = self.context.clone(); + // Step 18, queue a task, + // https://html.spec.whatwg.org/multipage/#timer-initialisation-steps + let _ = self.task_source.queue_with_canceller( + task!(timer_event: move || { + let global = context.root(); + let TimerEvent(source, id) = event; + match source { + TimerSource::FromWorker => { + global.downcast::<WorkerGlobalScope>().expect("Window timer delivered to worker"); + }, + TimerSource::FromWindow(pipeline) => { + assert_eq!(pipeline, global.pipeline_id()); + global.downcast::<Window>().expect("Worker timer delivered to window"); + }, + }; + // Step 7, substeps run in a task. + global.fire_timer(id); + }), + &self.canceller, + ); + } +} + +impl MessageListener { + /// A new message came in, handle it via a task enqueued on the event-loop. + /// A task is required, since we are using a trusted globalscope, + /// and we can only access the root from the event-loop. + fn notify(&self, msg: MessagePortMsg) { + match msg { + MessagePortMsg::CompleteTransfer(ports) => { + let context = self.context.clone(); + let _ = self.task_source.queue_with_canceller( + task!(process_complete_transfer: move || { + let global = context.root(); + + let router_id = match global.port_router_id() { + Some(router_id) => router_id, + None => { + // If not managing any ports, no transfer can succeed, + // so just send back everything. + let _ = global.script_to_constellation_chan().send( + ScriptMsg::MessagePortTransferResult(None, vec![], ports), + ); + return; + } + }; + + let mut succeeded = vec![]; + let mut failed = HashMap::new(); + + for (id, buffer) in ports.into_iter() { + if global.is_managing_port(&id) { + succeeded.push(id.clone()); + global.complete_port_transfer(id, buffer); + } else { + failed.insert(id, buffer); + } + } + let _ = global.script_to_constellation_chan().send( + ScriptMsg::MessagePortTransferResult(Some(router_id), succeeded, failed), + ); + }), + &self.canceller, + ); + }, + MessagePortMsg::CompletePendingTransfer(port_id, buffer) => { + let context = self.context.clone(); + let _ = self.task_source.queue_with_canceller( + task!(complete_pending: move || { + let global = context.root(); + global.complete_port_transfer(port_id, buffer); + }), + &self.canceller, + ); + }, + MessagePortMsg::NewTask(port_id, task) => { + let context = self.context.clone(); + let _ = self.task_source.queue_with_canceller( + task!(process_new_task: move || { + let global = context.root(); + global.route_task_to_port(port_id, task); + }), + &self.canceller, + ); + }, + MessagePortMsg::RemoveMessagePort(port_id) => { + let context = self.context.clone(); + let _ = self.task_source.queue_with_canceller( + task!(process_remove_message_port: move || { + let global = context.root(); + global.note_entangled_port_removed(&port_id); + }), + &self.canceller, + ); + }, + } + } +} + +/// Callback used to enqueue file chunks to streams as part of FileListener. +fn stream_handle_incoming(stream: &ReadableStream, bytes: Result<Vec<u8>, Error>) { + match bytes { + Ok(b) => { + stream.enqueue_native(b); + }, + Err(e) => { + stream.error_native(e); + }, + } +} + +/// Callback used to close streams as part of FileListener. +fn stream_handle_eof(stream: &ReadableStream) { + stream.close_native(); +} + +impl FileListener { + fn handle(&mut self, msg: FileManagerResult<ReadFileProgress>) { + match msg { + Ok(ReadFileProgress::Meta(blob_buf)) => match self.state.take() { + Some(FileListenerState::Empty(callback, target)) => { + let bytes = if let FileListenerTarget::Stream(ref trusted_stream) = target { + let trusted = trusted_stream.clone(); + + let task = task!(enqueue_stream_chunk: move || { + let stream = trusted.root(); + stream_handle_incoming(&*stream, Ok(blob_buf.bytes)); + }); + + let _ = self + .task_source + .queue_with_canceller(task, &self.task_canceller); + Vec::with_capacity(0) + } else { + blob_buf.bytes + }; + + self.state = Some(FileListenerState::Receiving(bytes, callback, target)); + }, + _ => panic!( + "Unexpected FileListenerState when receiving ReadFileProgress::Meta msg." + ), + }, + Ok(ReadFileProgress::Partial(mut bytes_in)) => match self.state.take() { + Some(FileListenerState::Receiving(mut bytes, callback, target)) => { + if let FileListenerTarget::Stream(ref trusted_stream) = target { + let trusted = trusted_stream.clone(); + + let task = task!(enqueue_stream_chunk: move || { + let stream = trusted.root(); + stream_handle_incoming(&*stream, Ok(bytes_in)); + }); + + let _ = self + .task_source + .queue_with_canceller(task, &self.task_canceller); + } else { + bytes.append(&mut bytes_in); + }; + + self.state = Some(FileListenerState::Receiving(bytes, callback, target)); + }, + _ => panic!( + "Unexpected FileListenerState when receiving ReadFileProgress::Partial msg." + ), + }, + Ok(ReadFileProgress::EOF) => match self.state.take() { + Some(FileListenerState::Receiving(bytes, callback, target)) => match target { + FileListenerTarget::Promise(trusted_promise) => { + let callback = match callback { + FileListenerCallback::Promise(callback) => callback, + _ => panic!("Expected promise callback."), + }; + let task = task!(resolve_promise: move || { + let promise = trusted_promise.root(); + let _ac = enter_realm(&*promise.global()); + callback(promise, Ok(bytes)); + }); + + let _ = self + .task_source + .queue_with_canceller(task, &self.task_canceller); + }, + FileListenerTarget::Stream(trusted_stream) => { + let trusted = trusted_stream.clone(); + + let task = task!(enqueue_stream_chunk: move || { + let stream = trusted.root(); + stream_handle_eof(&*stream); + }); + + let _ = self + .task_source + .queue_with_canceller(task, &self.task_canceller); + }, + }, + _ => { + panic!("Unexpected FileListenerState when receiving ReadFileProgress::EOF msg.") + }, + }, + Err(_) => match self.state.take() { + Some(FileListenerState::Receiving(_, callback, target)) | + Some(FileListenerState::Empty(callback, target)) => { + let error = Err(Error::Network); + + match target { + FileListenerTarget::Promise(trusted_promise) => { + let callback = match callback { + FileListenerCallback::Promise(callback) => callback, + _ => panic!("Expected promise callback."), + }; + let _ = self.task_source.queue_with_canceller( + task!(reject_promise: move || { + let promise = trusted_promise.root(); + let _ac = enter_realm(&*promise.global()); + callback(promise, error); + }), + &self.task_canceller, + ); + }, + FileListenerTarget::Stream(trusted_stream) => { + let _ = self.task_source.queue_with_canceller( + task!(error_stream: move || { + let stream = trusted_stream.root(); + stream_handle_incoming(&*stream, error); + }), + &self.task_canceller, + ); + }, + } + }, + _ => panic!("Unexpected FileListenerState when receiving Err msg."), + }, + } + } } impl GlobalScope { pub fn new_inherited( - pipeline_id: PipelineId, - devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, - mem_profiler_chan: mem::ProfilerChan, - time_profiler_chan: time::ProfilerChan, - constellation_chan: IpcSender<ConstellationMsg>, - scheduler_chan: IpcSender<TimerSchedulerMsg>, - resource_threads: ResourceThreads, - timer_event_chan: IpcSender<TimerEvent>, - origin: MutableOrigin) - -> Self { - GlobalScope { + pipeline_id: PipelineId, + devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, + mem_profiler_chan: profile_mem::ProfilerChan, + time_profiler_chan: profile_time::ProfilerChan, + script_to_constellation_chan: ScriptToConstellationChan, + scheduler_chan: IpcSender<TimerSchedulerMsg>, + resource_threads: ResourceThreads, + origin: MutableOrigin, + creation_url: Option<ServoUrl>, + microtask_queue: Rc<MicrotaskQueue>, + is_headless: bool, + user_agent: Cow<'static, str>, + gpu_id_hub: Arc<Mutex<Identities>>, + inherited_secure_context: Option<bool>, + ) -> Self { + Self { + message_port_state: DomRefCell::new(MessagePortState::UnManaged), + broadcast_channel_state: DomRefCell::new(BroadcastChannelState::UnManaged), + blob_state: DomRefCell::new(BlobState::UnManaged), eventtarget: EventTarget::new_inherited(), crypto: Default::default(), - next_worker_id: Cell::new(WorkerId(0)), - pipeline_id: pipeline_id, + registration_map: DomRefCell::new(HashMap::new()), + worker_map: DomRefCell::new(HashMap::new()), + pipeline_id, devtools_wants_updates: Default::default(), - console_timers: DOMRefCell::new(Default::default()), - devtools_chan: devtools_chan, - mem_profiler_chan: mem_profiler_chan, - time_profiler_chan: time_profiler_chan, - constellation_chan: constellation_chan, + console_timers: DomRefCell::new(Default::default()), + module_map: DomRefCell::new(Default::default()), + inline_module_map: DomRefCell::new(Default::default()), + devtools_chan, + mem_profiler_chan, + time_profiler_chan, + script_to_constellation_chan, scheduler_chan: scheduler_chan.clone(), in_error_reporting_mode: Default::default(), - resource_threads: resource_threads, - timers: OneshotTimers::new(timer_event_chan, scheduler_chan), - origin: origin, + resource_threads, + timers: OneshotTimers::new(scheduler_chan), + init_timers: Default::default(), + origin, + creation_url, + permission_state_invocation_results: Default::default(), + microtask_queue, + list_auto_close_worker: Default::default(), + event_source_tracker: DOMTracker::new(), + uncaught_rejections: Default::default(), + consumed_rejections: Default::default(), + is_headless, + user_agent, + gpu_id_hub, + gpu_devices: DomRefCell::new(HashMap::new()), + frozen_supported_performance_entry_types: DomRefCell::new(Default::default()), + https_state: Cell::new(HttpsState::None), + console_group_stack: DomRefCell::new(Vec::new()), + dynamic_modules: DomRefCell::new(DynamicModuleList::new()), + inherited_secure_context, + } + } + + /// The message-port router Id of the global, if any + fn port_router_id(&self) -> Option<MessagePortRouterId> { + if let MessagePortState::Managed(id, _message_ports) = &*self.message_port_state.borrow() { + Some(id.clone()) + } else { + None + } + } + + /// Is this global managing a given port? + fn is_managing_port(&self, port_id: &MessagePortId) -> bool { + if let MessagePortState::Managed(_router_id, message_ports) = + &*self.message_port_state.borrow() + { + return message_ports.contains_key(port_id); + } + false + } + + /// Setup the IPC-to-event-loop glue for timers to schedule themselves. + fn setup_timers(&self) { + if self.init_timers.get() { + return; + } + self.init_timers.set(true); + + let (timer_ipc_chan, timer_ipc_port) = ipc::channel().unwrap(); + self.timers.setup_scheduling(timer_ipc_chan); + + // Setup route from IPC to task-queue for the timer-task-source. + let context = Trusted::new(&*self); + let (task_source, canceller) = ( + self.timer_task_source(), + self.task_canceller(TaskSourceName::Timer), + ); + let timer_listener = TimerListener { + context, + task_source, + canceller, + }; + ROUTER.add_route( + timer_ipc_port.to_opaque(), + Box::new(move |message| { + let event = message.to().unwrap(); + timer_listener.handle(event); + }), + ); + } + + /// <https://w3c.github.io/ServiceWorker/#get-the-service-worker-registration-object> + pub fn get_serviceworker_registration( + &self, + script_url: &ServoUrl, + scope: &ServoUrl, + registration_id: ServiceWorkerRegistrationId, + installing_worker: Option<ServiceWorkerId>, + _waiting_worker: Option<ServiceWorkerId>, + _active_worker: Option<ServiceWorkerId>, + ) -> DomRoot<ServiceWorkerRegistration> { + // Step 1 + let mut registrations = self.registration_map.borrow_mut(); + + if let Some(registration) = registrations.get(®istration_id) { + // Step 3 + return DomRoot::from_ref(&**registration); + } + + // Step 2.1 -> 2.5 + let new_registration = + ServiceWorkerRegistration::new(self, scope.clone(), registration_id.clone()); + + // Step 2.6 + if let Some(worker_id) = installing_worker { + let worker = self.get_serviceworker(script_url, scope, worker_id); + new_registration.set_installing(&*worker); + } + + // TODO: 2.7 (waiting worker) + + // TODO: 2.8 (active worker) + + // Step 2.9 + registrations.insert(registration_id, Dom::from_ref(&*new_registration)); + + // Step 3 + new_registration + } + + /// <https://w3c.github.io/ServiceWorker/#get-the-service-worker-object> + pub fn get_serviceworker( + &self, + script_url: &ServoUrl, + scope: &ServoUrl, + worker_id: ServiceWorkerId, + ) -> DomRoot<ServiceWorker> { + // Step 1 + let mut workers = self.worker_map.borrow_mut(); + + if let Some(worker) = workers.get(&worker_id) { + // Step 3 + DomRoot::from_ref(&**worker) + } else { + // Step 2.1 + // TODO: step 2.2, worker state. + let new_worker = + ServiceWorker::new(self, script_url.clone(), scope.clone(), worker_id.clone()); + + // Step 2.3 + workers.insert(worker_id, Dom::from_ref(&*new_worker)); + + // Step 3 + new_worker + } + } + + /// Complete the transfer of a message-port. + fn complete_port_transfer(&self, port_id: MessagePortId, tasks: VecDeque<PortMessageTask>) { + let should_start = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + match message_ports.get_mut(&port_id) { + None => { + panic!("complete_port_transfer called for an unknown port."); + }, + Some(managed_port) => { + if managed_port.pending { + panic!("CompleteTransfer msg received for a pending port."); + } + if let Some(port_impl) = managed_port.port_impl.as_mut() { + port_impl.complete_transfer(tasks); + port_impl.enabled() + } else { + panic!("managed-port has no port-impl."); + } + }, + } + } else { + panic!("complete_port_transfer called for an unknown port."); + }; + if should_start { + self.start_message_port(&port_id); + } + } + + /// Clean-up DOM related resources + pub fn perform_a_dom_garbage_collection_checkpoint(&self) { + self.perform_a_message_port_garbage_collection_checkpoint(); + self.perform_a_blob_garbage_collection_checkpoint(); + self.perform_a_broadcast_channel_garbage_collection_checkpoint(); + } + + /// Remove the routers for ports and broadcast-channels. + /// Drain the list of workers. + pub fn remove_web_messaging_and_dedicated_workers_infra(&self) { + self.remove_message_ports_router(); + self.remove_broadcast_channel_router(); + + // Drop each ref to a worker explicitly now, + // which will send a shutdown signal, + // and join on the worker thread. + self.list_auto_close_worker + .borrow_mut() + .drain(0..) + .for_each(|worker| drop(worker)); + } + + /// Update our state to un-managed, + /// and tell the constellation to drop the sender to our message-port router. + fn remove_message_ports_router(&self) { + if let MessagePortState::Managed(router_id, _message_ports) = + &*self.message_port_state.borrow() + { + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::RemoveMessagePortRouter(router_id.clone())); + } + *self.message_port_state.borrow_mut() = MessagePortState::UnManaged; + } + + /// Update our state to un-managed, + /// and tell the constellation to drop the sender to our broadcast router. + fn remove_broadcast_channel_router(&self) { + if let BroadcastChannelState::Managed(router_id, _channels) = + &*self.broadcast_channel_state.borrow() + { + let _ = + self.script_to_constellation_chan() + .send(ScriptMsg::RemoveBroadcastChannelRouter( + router_id.clone(), + self.origin().immutable().clone(), + )); + } + *self.broadcast_channel_state.borrow_mut() = BroadcastChannelState::UnManaged; + } + + /// <https://html.spec.whatwg.org/multipage/#entangle> + pub fn entangle_ports(&self, port1: MessagePortId, port2: MessagePortId) { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + for (port_id, entangled_id) in &[(port1, port2), (port2, port1)] { + match message_ports.get_mut(&port_id) { + None => { + return warn!("entangled_ports called on a global not managing the port."); + }, + Some(managed_port) => { + if let Some(port_impl) = managed_port.port_impl.as_mut() { + managed_port.dom_port.entangle(entangled_id.clone()); + port_impl.entangle(entangled_id.clone()); + } else { + panic!("managed-port has no port-impl."); + } + }, + } + } + } else { + panic!("entangled_ports called on a global not managing any ports."); + } + + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::EntanglePorts(port1, port2)); + } + + /// Note that the entangled port of `port_id` has been removed in another global. + pub fn note_entangled_port_removed(&self, port_id: &MessagePortId) { + // Note: currently this is a no-op, + // as we only use the `close` method to manage the local lifecyle of a port. + // This could be used as part of lifecyle management to determine a port can be GC'ed. + // See https://github.com/servo/servo/issues/25772 + warn!( + "Entangled port of {:?} has been removed in another global", + port_id + ); + } + + /// Handle the transfer of a port in the current task. + pub fn mark_port_as_transferred(&self, port_id: &MessagePortId) -> MessagePortImpl { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let mut port_impl = message_ports + .remove(&port_id) + .map(|ref mut managed_port| { + managed_port + .port_impl + .take() + .expect("Managed port doesn't have a port-impl.") + }) + .expect("mark_port_as_transferred called on a global not managing the port."); + port_impl.set_has_been_shipped(); + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::MessagePortShipped(port_id.clone())); + port_impl + } else { + panic!("mark_port_as_transferred called on a global not managing any ports."); + } + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start> + pub fn start_message_port(&self, port_id: &MessagePortId) { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let message_buffer = match message_ports.get_mut(&port_id) { + None => panic!("start_message_port called on a unknown port."), + Some(managed_port) => { + if let Some(port_impl) = managed_port.port_impl.as_mut() { + port_impl.start() + } else { + panic!("managed-port has no port-impl."); + } + }, + }; + if let Some(message_buffer) = message_buffer { + for task in message_buffer { + let port_id = port_id.clone(); + let this = Trusted::new(&*self); + let _ = self.port_message_queue().queue( + task!(process_pending_port_messages: move || { + let target_global = this.root(); + target_global.route_task_to_port(port_id, task); + }), + &self, + ); + } + } + } else { + return warn!("start_message_port called on a global not managing any ports."); + } + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-close> + pub fn close_message_port(&self, port_id: &MessagePortId) { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + match message_ports.get_mut(&port_id) { + None => panic!("close_message_port called on an unknown port."), + Some(managed_port) => { + if let Some(port_impl) = managed_port.port_impl.as_mut() { + port_impl.close(); + managed_port.closed = true; + } else { + panic!("managed-port has no port-impl."); + } + }, + }; + } else { + return warn!("close_message_port called on a global not managing any ports."); + } + } + + /// <https://html.spec.whatwg.org/multipage/#message-port-post-message-steps> + // Steps 6 and 7 + pub fn post_messageport_msg(&self, port_id: MessagePortId, task: PortMessageTask) { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let entangled_port = match message_ports.get_mut(&port_id) { + None => panic!("post_messageport_msg called on an unknown port."), + Some(managed_port) => { + if let Some(port_impl) = managed_port.port_impl.as_mut() { + port_impl.entangled_port_id() + } else { + panic!("managed-port has no port-impl."); + } + }, + }; + if let Some(entangled_id) = entangled_port { + // Step 7 + let this = Trusted::new(&*self); + let _ = self.port_message_queue().queue( + task!(post_message: move || { + let global = this.root(); + // Note: we do this in a task, as this will ensure the global and constellation + // are aware of any transfer that might still take place in the current task. + global.route_task_to_port(entangled_id, task); + }), + self, + ); + } + } else { + return warn!("post_messageport_msg called on a global not managing any ports."); + } + } + + /// If we don't know about the port, + /// send the message to the constellation for routing. + fn re_route_port_task(&self, port_id: MessagePortId, task: PortMessageTask) { + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::RerouteMessagePort(port_id, task)); + } + + /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage> + /// Step 7 and following steps. + pub fn schedule_broadcast(&self, msg: BroadcastMsg, channel_id: &Uuid) { + // First, broadcast locally. + self.broadcast_message_event(msg.clone(), Some(channel_id)); + + if let BroadcastChannelState::Managed(router_id, _) = + &*self.broadcast_channel_state.borrow() + { + // Second, broadcast to other globals via the constellation. + // + // Note: for globals in the same script-thread, + // we could skip the hop to the constellation. + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::ScheduleBroadcast(router_id.clone(), msg)); + } else { + panic!("Attemps to broadcast a message via global not managing any channels."); + } + } + + /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage> + /// Step 7 and following steps. + pub fn broadcast_message_event(&self, event: BroadcastMsg, channel_id: Option<&Uuid>) { + if let BroadcastChannelState::Managed(_, channels) = &*self.broadcast_channel_state.borrow() + { + let BroadcastMsg { + data, + origin, + channel_name, + } = event; + + // Step 7, a few preliminary steps. + + // - Check the worker is not closing. + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + if worker.is_closing() { + return; + } + } + + // - Check the associated document is fully-active. + if let Some(window) = self.downcast::<Window>() { + if !window.Document().is_fully_active() { + return; + } + } + + // - Check for a case-sensitive match for the name of the channel. + let channel_name = DOMString::from_string(channel_name); + + if let Some(channels) = channels.get(&channel_name) { + channels + .iter() + .filter(|ref channel| { + // Step 8. + // Filter out the sender. + if let Some(id) = channel_id { + channel.id() != id + } else { + true + } + }) + .map(|channel| DomRoot::from_ref(&**channel)) + // Step 9, sort by creation order, + // done by using a queue to store channels in creation order. + .for_each(|channel| { + let data = data.clone_for_broadcast(); + let origin = origin.clone(); + + // Step 10: Queue a task on the DOM manipulation task-source, + // to fire the message event + let channel = Trusted::new(&*channel); + let global = Trusted::new(&*self); + let _ = self.dom_manipulation_task_source().queue( + task!(process_pending_port_messages: move || { + let destination = channel.root(); + let global = global.root(); + + // 10.1 Check for closed flag. + if destination.closed() { + return; + } + + rooted!(in(*global.get_cx()) let mut message = UndefinedValue()); + + // Step 10.3 StructuredDeserialize(serialized, targetRealm). + if let Ok(ports) = structuredclone::read(&global, data, message.handle_mut()) { + // Step 10.4, Fire an event named message at destination. + MessageEvent::dispatch_jsval( + &*destination.upcast(), + &global, + message.handle(), + Some(&origin.ascii_serialization()), + None, + ports, + ); + } else { + // Step 10.3, fire an event named messageerror at destination. + MessageEvent::dispatch_error(&*destination.upcast(), &global); + } + }), + &self, + ); + }); + } + } + } + + /// Route the task to be handled by the relevant port. + pub fn route_task_to_port(&self, port_id: MessagePortId, task: PortMessageTask) { + let should_dispatch = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + if !message_ports.contains_key(&port_id) { + self.re_route_port_task(port_id, task); + return; + } + match message_ports.get_mut(&port_id) { + None => panic!("route_task_to_port called for an unknown port."), + Some(managed_port) => { + // If the port is not enabled yet, or if is awaiting the completion of it's transfer, + // the task will be buffered and dispatched upon enablement or completion of the transfer. + if let Some(port_impl) = managed_port.port_impl.as_mut() { + port_impl.handle_incoming(task).and_then(|to_dispatch| { + Some((DomRoot::from_ref(&*managed_port.dom_port), to_dispatch)) + }) + } else { + panic!("managed-port has no port-impl."); + } + }, + } + } else { + self.re_route_port_task(port_id, task); + return; + }; + if let Some((dom_port, PortMessageTask { origin, data })) = should_dispatch { + // Substep 3-4 + rooted!(in(*self.get_cx()) let mut message_clone = UndefinedValue()); + if let Ok(ports) = structuredclone::read(self, data, message_clone.handle_mut()) { + // Substep 6 + // Dispatch the event, using the dom message-port. + MessageEvent::dispatch_jsval( + &dom_port.upcast(), + self, + message_clone.handle(), + Some(&origin.ascii_serialization()), + None, + ports, + ); + } else { + // Step 4, fire messageerror event. + MessageEvent::dispatch_error(&dom_port.upcast(), self); + } + } + } + + /// Check all ports that have been transfer-received in the previous task, + /// and complete their transfer if they haven't been re-transferred. + pub fn maybe_add_pending_ports(&self) { + if let MessagePortState::Managed(router_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let to_be_added: Vec<MessagePortId> = message_ports + .iter() + .filter_map(|(id, managed_port)| { + if managed_port.pending { + Some(id.clone()) + } else { + None + } + }) + .collect(); + for id in to_be_added.iter() { + let managed_port = message_ports + .get_mut(&id) + .expect("Collected port-id to match an entry"); + if !managed_port.pending { + panic!("Only pending ports should be found in to_be_added") + } + managed_port.pending = false; + } + let _ = + self.script_to_constellation_chan() + .send(ScriptMsg::CompleteMessagePortTransfer( + router_id.clone(), + to_be_added, + )); + } else { + warn!("maybe_add_pending_ports called on a global not managing any ports."); + } + } + + /// https://html.spec.whatwg.org/multipage/#ports-and-garbage-collection + pub fn perform_a_message_port_garbage_collection_checkpoint(&self) { + let is_empty = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let to_be_removed: Vec<MessagePortId> = message_ports + .iter() + .filter_map(|(id, ref managed_port)| { + if managed_port.closed { + // Let the constellation know to drop this port and the one it is entangled with, + // and to forward this message to the script-process where the entangled is found. + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::RemoveMessagePort(id.clone())); + Some(id.clone()) + } else { + None + } + }) + .collect(); + for id in to_be_removed { + message_ports.remove(&id); + } + message_ports.is_empty() + } else { + false + }; + if is_empty { + self.remove_message_ports_router(); + } + } + + /// Remove broadcast-channels that are closed. + /// TODO: Also remove them if they do not have an event-listener. + /// see https://github.com/servo/servo/issues/25772 + pub fn perform_a_broadcast_channel_garbage_collection_checkpoint(&self) { + let is_empty = if let BroadcastChannelState::Managed(router_id, ref mut channels) = + &mut *self.broadcast_channel_state.borrow_mut() + { + channels.retain(|name, ref mut channels| { + channels.retain(|ref chan| !chan.closed()); + if channels.is_empty() { + let _ = self.script_to_constellation_chan().send( + ScriptMsg::RemoveBroadcastChannelNameInRouter( + router_id.clone(), + name.to_string(), + self.origin().immutable().clone(), + ), + ); + false + } else { + true + } + }); + channels.is_empty() + } else { + false + }; + if is_empty { + self.remove_broadcast_channel_router(); + } + } + + /// Start tracking a broadcast-channel. + pub fn track_broadcast_channel(&self, dom_channel: &BroadcastChannel) { + let mut current_state = self.broadcast_channel_state.borrow_mut(); + + if let BroadcastChannelState::UnManaged = &*current_state { + // Setup a route for IPC, for broadcasts from the constellation to our channels. + let (broadcast_control_sender, broadcast_control_receiver) = + ipc::channel().expect("ipc channel failure"); + let context = Trusted::new(self); + let (task_source, canceller) = ( + self.dom_manipulation_task_source(), + self.task_canceller(TaskSourceName::DOMManipulation), + ); + let listener = BroadcastListener { + canceller, + task_source, + context, + }; + ROUTER.add_route( + broadcast_control_receiver.to_opaque(), + Box::new(move |message| { + let msg = message.to(); + match msg { + Ok(msg) => listener.handle(msg), + Err(err) => warn!("Error receiving a BroadcastMsg: {:?}", err), + } + }), + ); + let router_id = BroadcastChannelRouterId::new(); + *current_state = BroadcastChannelState::Managed(router_id.clone(), HashMap::new()); + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::NewBroadcastChannelRouter( + router_id, + broadcast_control_sender, + self.origin().immutable().clone(), + )); + } + + if let BroadcastChannelState::Managed(router_id, channels) = &mut *current_state { + let entry = channels.entry(dom_channel.Name()).or_insert_with(|| { + let _ = self.script_to_constellation_chan().send( + ScriptMsg::NewBroadcastChannelNameInRouter( + router_id.clone(), + dom_channel.Name().to_string(), + self.origin().immutable().clone(), + ), + ); + VecDeque::new() + }); + entry.push_back(Dom::from_ref(dom_channel)); + } else { + panic!("track_broadcast_channel should have first switched the state to managed."); + } + } + + /// Start tracking a message-port + pub fn track_message_port(&self, dom_port: &MessagePort, port_impl: Option<MessagePortImpl>) { + let mut current_state = self.message_port_state.borrow_mut(); + + if let MessagePortState::UnManaged = &*current_state { + // Setup a route for IPC, for messages from the constellation to our ports. + let (port_control_sender, port_control_receiver) = + ipc::channel().expect("ipc channel failure"); + let context = Trusted::new(self); + let (task_source, canceller) = ( + self.port_message_queue(), + self.task_canceller(TaskSourceName::PortMessage), + ); + let listener = MessageListener { + canceller, + task_source, + context, + }; + ROUTER.add_route( + port_control_receiver.to_opaque(), + Box::new(move |message| { + let msg = message.to(); + match msg { + Ok(msg) => listener.notify(msg), + Err(err) => warn!("Error receiving a MessagePortMsg: {:?}", err), + } + }), + ); + let router_id = MessagePortRouterId::new(); + *current_state = MessagePortState::Managed(router_id.clone(), HashMap::new()); + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::NewMessagePortRouter( + router_id, + port_control_sender, + )); + } + + if let MessagePortState::Managed(router_id, message_ports) = &mut *current_state { + if let Some(port_impl) = port_impl { + // We keep transfer-received ports as "pending", + // and only ask the constellation to complete the transfer + // if they're not re-shipped in the current task. + message_ports.insert( + dom_port.message_port_id().clone(), + ManagedMessagePort { + port_impl: Some(port_impl), + dom_port: Dom::from_ref(dom_port), + pending: true, + closed: false, + }, + ); + + // Queue a task to complete the transfer, + // unless the port is re-transferred in the current task. + let this = Trusted::new(&*self); + let _ = self.port_message_queue().queue( + task!(process_pending_port_messages: move || { + let target_global = this.root(); + target_global.maybe_add_pending_ports(); + }), + &self, + ); + } else { + // If this is a newly-created port, let the constellation immediately know. + let port_impl = MessagePortImpl::new(dom_port.message_port_id().clone()); + message_ports.insert( + dom_port.message_port_id().clone(), + ManagedMessagePort { + port_impl: Some(port_impl), + dom_port: Dom::from_ref(dom_port), + pending: false, + closed: false, + }, + ); + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::NewMessagePort( + router_id.clone(), + dom_port.message_port_id().clone(), + )); + }; + } else { + panic!("track_message_port should have first switched the state to managed."); + } + } + + /// <https://html.spec.whatwg.org/multipage/#serialization-steps> + /// defined at <https://w3c.github.io/FileAPI/#blob-section>. + /// Get the snapshot state and underlying bytes of the blob. + pub fn serialize_blob(&self, blob_id: &BlobId) -> BlobImpl { + // Note: we combine the snapshot state and underlying bytes into one call, + // which seems spec compliant. + // See https://w3c.github.io/FileAPI/#snapshot-state + let bytes = self + .get_blob_bytes(blob_id) + .expect("Could not read bytes from blob as part of serialization steps."); + let type_string = self.get_blob_type_string(blob_id); + + // Note: the new BlobImpl is a clone, but with it's own BlobId. + BlobImpl::new_from_bytes(bytes, type_string) + } + + fn track_blob_info(&self, blob_info: BlobInfo, blob_id: BlobId) { + let mut blob_state = self.blob_state.borrow_mut(); + + match &mut *blob_state { + BlobState::UnManaged => { + let mut blobs_map = HashMap::new(); + blobs_map.insert(blob_id, blob_info); + *blob_state = BlobState::Managed(blobs_map); + }, + BlobState::Managed(blobs_map) => { + blobs_map.insert(blob_id, blob_info); + }, } } + /// Start tracking a blob + pub fn track_blob(&self, dom_blob: &Blob, blob_impl: BlobImpl) { + let blob_id = blob_impl.blob_id(); + + let blob_info = BlobInfo { + blob_impl, + tracker: BlobTracker::Blob(WeakRef::new(dom_blob)), + has_url: false, + }; + + self.track_blob_info(blob_info, blob_id); + } + + /// Start tracking a file + pub fn track_file(&self, file: &File, blob_impl: BlobImpl) { + let blob_id = blob_impl.blob_id(); + + let blob_info = BlobInfo { + blob_impl, + tracker: BlobTracker::File(WeakRef::new(file)), + has_url: false, + }; + + self.track_blob_info(blob_info, blob_id); + } + + /// Clean-up any file or blob that is unreachable from script, + /// unless it has an oustanding blob url. + /// <https://w3c.github.io/FileAPI/#lifeTime> + fn perform_a_blob_garbage_collection_checkpoint(&self) { + let mut blob_state = self.blob_state.borrow_mut(); + if let BlobState::Managed(blobs_map) = &mut *blob_state { + blobs_map.retain(|_id, blob_info| { + let garbage_collected = match &blob_info.tracker { + BlobTracker::File(weak) => weak.root().is_none(), + BlobTracker::Blob(weak) => weak.root().is_none(), + }; + if garbage_collected && !blob_info.has_url { + if let BlobData::File(ref f) = blob_info.blob_impl.blob_data() { + self.decrement_file_ref(f.get_id()); + } + false + } else { + true + } + }); + if blobs_map.is_empty() { + *blob_state = BlobState::UnManaged; + } + } + } + + /// Clean-up all file related resources on document unload. + /// <https://w3c.github.io/FileAPI/#lifeTime> + pub fn clean_up_all_file_resources(&self) { + let mut blob_state = self.blob_state.borrow_mut(); + if let BlobState::Managed(blobs_map) = &mut *blob_state { + blobs_map.drain().for_each(|(_id, blob_info)| { + if let BlobData::File(ref f) = blob_info.blob_impl.blob_data() { + self.decrement_file_ref(f.get_id()); + } + }); + } + *blob_state = BlobState::UnManaged; + } + + fn decrement_file_ref(&self, id: Uuid) { + let origin = get_blob_origin(&self.get_url()); + + let (tx, rx) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap(); + + let msg = FileManagerThreadMsg::DecRef(id, origin, tx); + self.send_to_file_manager(msg); + let _ = rx.recv(); + } + + /// Get a slice to the inner data of a Blob, + /// In the case of a File-backed blob, this might incur synchronous read and caching. + pub fn get_blob_bytes(&self, blob_id: &BlobId) -> Result<Vec<u8>, ()> { + let parent = { + let blob_state = self.blob_state.borrow(); + if let BlobState::Managed(blobs_map) = &*blob_state { + let blob_info = blobs_map + .get(blob_id) + .expect("get_blob_bytes for an unknown blob."); + match blob_info.blob_impl.blob_data() { + BlobData::Sliced(ref parent, ref rel_pos) => { + Some((parent.clone(), rel_pos.clone())) + }, + _ => None, + } + } else { + panic!("get_blob_bytes called on a global not managing any blobs."); + } + }; + + match parent { + Some((parent_id, rel_pos)) => self.get_blob_bytes_non_sliced(&parent_id).map(|v| { + let range = rel_pos.to_abs_range(v.len()); + v.index(range).to_vec() + }), + None => self.get_blob_bytes_non_sliced(blob_id), + } + } + + /// Get bytes from a non-sliced blob + fn get_blob_bytes_non_sliced(&self, blob_id: &BlobId) -> Result<Vec<u8>, ()> { + let blob_state = self.blob_state.borrow(); + if let BlobState::Managed(blobs_map) = &*blob_state { + let blob_info = blobs_map + .get(blob_id) + .expect("get_blob_bytes_non_sliced called for a unknown blob."); + match blob_info.blob_impl.blob_data() { + BlobData::File(ref f) => { + let (buffer, is_new_buffer) = match f.get_cache() { + Some(bytes) => (bytes, false), + None => { + let bytes = self.read_file(f.get_id())?; + (bytes, true) + }, + }; + + // Cache + if is_new_buffer { + f.cache_bytes(buffer.clone()); + } + + Ok(buffer) + }, + BlobData::Memory(ref s) => Ok(s.clone()), + BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."), + } + } else { + panic!("get_blob_bytes_non_sliced called on a global not managing any blobs."); + } + } + + /// Get a slice to the inner data of a Blob, + /// if it's a memory blob, or it's file-id and file-size otherwise. + /// + /// Note: this is almost a duplicate of `get_blob_bytes`, + /// tweaked for integration with streams. + /// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams. + fn get_blob_bytes_or_file_id(&self, blob_id: &BlobId) -> BlobResult { + let parent = { + let blob_state = self.blob_state.borrow(); + if let BlobState::Managed(blobs_map) = &*blob_state { + let blob_info = blobs_map + .get(blob_id) + .expect("get_blob_bytes_or_file_id for an unknown blob."); + match blob_info.blob_impl.blob_data() { + BlobData::Sliced(ref parent, ref rel_pos) => { + Some((parent.clone(), rel_pos.clone())) + }, + _ => None, + } + } else { + panic!("get_blob_bytes_or_file_id called on a global not managing any blobs."); + } + }; + + match parent { + Some((parent_id, rel_pos)) => { + match self.get_blob_bytes_non_sliced_or_file_id(&parent_id) { + BlobResult::Bytes(bytes) => { + let range = rel_pos.to_abs_range(bytes.len()); + BlobResult::Bytes(bytes.index(range).to_vec()) + }, + res => res, + } + }, + None => self.get_blob_bytes_non_sliced_or_file_id(blob_id), + } + } + + /// Get bytes from a non-sliced blob if in memory, or it's file-id and file-size. + /// + /// Note: this is almost a duplicate of `get_blob_bytes_non_sliced`, + /// tweaked for integration with streams. + /// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams. + fn get_blob_bytes_non_sliced_or_file_id(&self, blob_id: &BlobId) -> BlobResult { + let blob_state = self.blob_state.borrow(); + if let BlobState::Managed(blobs_map) = &*blob_state { + let blob_info = blobs_map + .get(blob_id) + .expect("get_blob_bytes_non_sliced_or_file_id called for a unknown blob."); + match blob_info.blob_impl.blob_data() { + BlobData::File(ref f) => match f.get_cache() { + Some(bytes) => BlobResult::Bytes(bytes.clone()), + None => BlobResult::File(f.get_id(), f.get_size() as usize), + }, + BlobData::Memory(ref s) => BlobResult::Bytes(s.clone()), + BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."), + } + } else { + panic!( + "get_blob_bytes_non_sliced_or_file_id called on a global not managing any blobs." + ); + } + } + + /// Get a copy of the type_string of a blob. + pub fn get_blob_type_string(&self, blob_id: &BlobId) -> String { + let blob_state = self.blob_state.borrow(); + if let BlobState::Managed(blobs_map) = &*blob_state { + let blob_info = blobs_map + .get(blob_id) + .expect("get_blob_type_string called for a unknown blob."); + blob_info.blob_impl.type_string() + } else { + panic!("get_blob_type_string called on a global not managing any blobs."); + } + } + + /// https://w3c.github.io/FileAPI/#dfn-size + pub fn get_blob_size(&self, blob_id: &BlobId) -> u64 { + let blob_state = self.blob_state.borrow(); + if let BlobState::Managed(blobs_map) = &*blob_state { + let parent = { + let blob_info = blobs_map + .get(blob_id) + .expect("get_blob_size called for a unknown blob."); + match blob_info.blob_impl.blob_data() { + BlobData::Sliced(ref parent, ref rel_pos) => { + Some((parent.clone(), rel_pos.clone())) + }, + _ => None, + } + }; + match parent { + Some((parent_id, rel_pos)) => { + let parent_info = blobs_map + .get(&parent_id) + .expect("Parent of blob whose size is unknown."); + let parent_size = match parent_info.blob_impl.blob_data() { + BlobData::File(ref f) => f.get_size(), + BlobData::Memory(ref v) => v.len() as u64, + BlobData::Sliced(_, _) => panic!("Blob ancestry should be only one level."), + }; + rel_pos.to_abs_range(parent_size as usize).len() as u64 + }, + None => { + let blob_info = blobs_map.get(blob_id).expect("Blob whose size is unknown."); + match blob_info.blob_impl.blob_data() { + BlobData::File(ref f) => f.get_size(), + BlobData::Memory(ref v) => v.len() as u64, + BlobData::Sliced(_, _) => panic!( + "It was previously checked that this blob does not have a parent." + ), + } + }, + } + } else { + panic!("get_blob_size called on a global not managing any blobs."); + } + } + + pub fn get_blob_url_id(&self, blob_id: &BlobId) -> Uuid { + let mut blob_state = self.blob_state.borrow_mut(); + if let BlobState::Managed(blobs_map) = &mut *blob_state { + let parent = { + let blob_info = blobs_map + .get_mut(blob_id) + .expect("get_blob_url_id called for a unknown blob."); + + // Keep track of blobs with outstanding URLs. + blob_info.has_url = true; + + match blob_info.blob_impl.blob_data() { + BlobData::Sliced(ref parent, ref rel_pos) => { + Some((parent.clone(), rel_pos.clone())) + }, + _ => None, + } + }; + match parent { + Some((parent_id, rel_pos)) => { + let parent_file_id = { + let parent_info = blobs_map + .get_mut(&parent_id) + .expect("Parent of blob whose url is requested is unknown."); + self.promote(parent_info, /* set_valid is */ false) + }; + let parent_size = self.get_blob_size(&parent_id); + let blob_info = blobs_map + .get_mut(blob_id) + .expect("Blob whose url is requested is unknown."); + self.create_sliced_url_id(blob_info, &parent_file_id, &rel_pos, parent_size) + }, + None => { + let blob_info = blobs_map + .get_mut(blob_id) + .expect("Blob whose url is requested is unknown."); + self.promote(blob_info, /* set_valid is */ true) + }, + } + } else { + panic!("get_blob_url_id called on a global not managing any blobs."); + } + } + + /// Get a FileID representing sliced parent-blob content + fn create_sliced_url_id( + &self, + blob_info: &mut BlobInfo, + parent_file_id: &Uuid, + rel_pos: &RelativePos, + parent_len: u64, + ) -> Uuid { + let origin = get_blob_origin(&self.get_url()); + + let (tx, rx) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap(); + let msg = FileManagerThreadMsg::AddSlicedURLEntry( + parent_file_id.clone(), + rel_pos.clone(), + tx, + origin.clone(), + ); + self.send_to_file_manager(msg); + match rx.recv().expect("File manager thread is down.") { + Ok(new_id) => { + *blob_info.blob_impl.blob_data_mut() = BlobData::File(FileBlob::new( + new_id.clone(), + None, + None, + rel_pos.to_abs_range(parent_len as usize).len() as u64, + )); + + // Return the indirect id reference + new_id + }, + Err(_) => { + // Return dummy id + Uuid::new_v4() + }, + } + } + + /// Promote non-Slice blob: + /// 1. Memory-based: The bytes in data slice will be transferred to file manager thread. + /// 2. File-based: If set_valid, then activate the FileID so it can serve as URL + /// Depending on set_valid, the returned FileID can be part of + /// valid or invalid Blob URL. + pub fn promote(&self, blob_info: &mut BlobInfo, set_valid: bool) -> Uuid { + let mut bytes = vec![]; + let global_url = self.get_url(); + + match blob_info.blob_impl.blob_data_mut() { + BlobData::Sliced(_, _) => { + panic!("Sliced blobs should use create_sliced_url_id instead of promote."); + }, + BlobData::File(ref f) => { + if set_valid { + let origin = get_blob_origin(&global_url); + let (tx, rx) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap(); + + let msg = FileManagerThreadMsg::ActivateBlobURL(f.get_id(), tx, origin.clone()); + self.send_to_file_manager(msg); + + match rx.recv().unwrap() { + Ok(_) => return f.get_id(), + // Return a dummy id on error + Err(_) => return Uuid::new_v4(), + } + } else { + // no need to activate + return f.get_id(); + } + }, + BlobData::Memory(ref mut bytes_in) => mem::swap(bytes_in, &mut bytes), + }; + + let origin = get_blob_origin(&global_url); + + let blob_buf = BlobBuf { + filename: None, + type_string: blob_info.blob_impl.type_string(), + size: bytes.len() as u64, + bytes: bytes.to_vec(), + }; + + let id = Uuid::new_v4(); + let msg = FileManagerThreadMsg::PromoteMemory(id, blob_buf, set_valid, origin.clone()); + self.send_to_file_manager(msg); + + *blob_info.blob_impl.blob_data_mut() = BlobData::File(FileBlob::new( + id.clone(), + None, + Some(bytes.to_vec()), + bytes.len() as u64, + )); + + id + } + + fn send_to_file_manager(&self, msg: FileManagerThreadMsg) { + let resource_threads = self.resource_threads(); + let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg)); + } + + fn read_file(&self, id: Uuid) -> Result<Vec<u8>, ()> { + let recv = self.send_msg(id); + GlobalScope::read_msg(recv) + } + + /// <https://w3c.github.io/FileAPI/#blob-get-stream> + pub fn get_blob_stream(&self, blob_id: &BlobId) -> DomRoot<ReadableStream> { + let (file_id, size) = match self.get_blob_bytes_or_file_id(blob_id) { + BlobResult::Bytes(bytes) => { + // If we have all the bytes in memory, queue them and close the stream. + let stream = ReadableStream::new_from_bytes(self, bytes); + return stream; + }, + BlobResult::File(id, size) => (id, size), + }; + + let stream = ReadableStream::new_with_external_underlying_source( + self, + ExternalUnderlyingSource::Blob(size as usize), + ); + + let recv = self.send_msg(file_id); + + let trusted_stream = Trusted::new(&*stream.clone()); + let task_canceller = self.task_canceller(TaskSourceName::FileReading); + let task_source = self.file_reading_task_source(); + + let mut file_listener = FileListener { + state: Some(FileListenerState::Empty( + FileListenerCallback::Stream, + FileListenerTarget::Stream(trusted_stream), + )), + task_source, + task_canceller, + }; + + ROUTER.add_route( + recv.to_opaque(), + Box::new(move |msg| { + file_listener.handle( + msg.to() + .expect("Deserialization of file listener msg failed."), + ); + }), + ); + + stream + } + + pub fn read_file_async( + &self, + id: Uuid, + promise: Rc<Promise>, + callback: Box<dyn Fn(Rc<Promise>, Result<Vec<u8>, Error>) + Send>, + ) { + let recv = self.send_msg(id); + + let trusted_promise = TrustedPromise::new(promise); + let task_canceller = self.task_canceller(TaskSourceName::FileReading); + let task_source = self.file_reading_task_source(); + + let mut file_listener = FileListener { + state: Some(FileListenerState::Empty( + FileListenerCallback::Promise(callback), + FileListenerTarget::Promise(trusted_promise), + )), + task_source, + task_canceller, + }; + + ROUTER.add_route( + recv.to_opaque(), + Box::new(move |msg| { + file_listener.handle( + msg.to() + .expect("Deserialization of file listener msg failed."), + ); + }), + ); + } + + fn send_msg(&self, id: Uuid) -> profile_ipc::IpcReceiver<FileManagerResult<ReadFileProgress>> { + let resource_threads = self.resource_threads(); + let (chan, recv) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap(); + let origin = get_blob_origin(&self.get_url()); + let msg = FileManagerThreadMsg::ReadFile(chan, id, origin); + let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg)); + recv + } + + fn read_msg( + receiver: profile_ipc::IpcReceiver<FileManagerResult<ReadFileProgress>>, + ) -> Result<Vec<u8>, ()> { + let mut bytes = vec![]; + + loop { + match receiver.recv().unwrap() { + Ok(ReadFileProgress::Meta(mut blob_buf)) => { + bytes.append(&mut blob_buf.bytes); + }, + Ok(ReadFileProgress::Partial(mut bytes_in)) => { + bytes.append(&mut bytes_in); + }, + Ok(ReadFileProgress::EOF) => { + return Ok(bytes); + }, + Err(_) => return Err(()), + } + } + } + + pub fn permission_state_invocation_results( + &self, + ) -> &DomRefCell<HashMap<String, PermissionState>> { + &self.permission_state_invocation_results + } + + pub fn track_worker( + &self, + closing: Arc<AtomicBool>, + join_handle: JoinHandle<()>, + control_sender: Sender<DedicatedWorkerControlMsg>, + context: ContextForRequestInterrupt, + ) { + self.list_auto_close_worker + .borrow_mut() + .push(AutoCloseWorker { + closing, + join_handle: Some(join_handle), + control_sender: control_sender, + context, + }); + } + + pub fn track_event_source(&self, event_source: &EventSource) { + self.event_source_tracker.track(event_source); + } + + pub fn close_event_sources(&self) -> bool { + let mut canceled_any_fetch = false; + self.event_source_tracker + .for_each( + |event_source: DomRoot<EventSource>| match event_source.ReadyState() { + 2 => {}, + _ => { + event_source.cancel(); + canceled_any_fetch = true; + }, + }, + ); + canceled_any_fetch + } + /// Returns the global scope of the realm that the given DOM object's reflector /// was created in. #[allow(unsafe_code)] - pub fn from_reflector<T: DomObject>(reflector: &T) -> Root<Self> { + pub fn from_reflector<T: DomObject>(reflector: &T) -> DomRoot<Self> { unsafe { GlobalScope::from_object(*reflector.reflector().get_jsobject()) } } /// Returns the global scope of the realm that the given JS object was created in. #[allow(unsafe_code)] - pub unsafe fn from_object(obj: *mut JSObject) -> Root<Self> { + pub unsafe fn from_object(obj: *mut JSObject) -> DomRoot<Self> { assert!(!obj.is_null()); - let global = GetGlobalForObjectCrossCompartment(obj); - global_scope_from_global(global) + let global = GetNonCCWObjectGlobal(obj); + global_scope_from_global_static(global) } /// Returns the global scope for the given JSContext #[allow(unsafe_code)] - pub unsafe fn from_context(cx: *mut JSContext) -> Root<Self> { + pub unsafe fn from_context(cx: *mut JSContext, _realm: InRealm) -> DomRoot<Self> { let global = CurrentGlobalOrNull(cx); - global_scope_from_global(global) + assert!(!global.is_null()); + global_scope_from_global(global, cx) + } + + /// Returns the global scope for the given SafeJSContext + #[allow(unsafe_code)] + pub fn from_safe_context(cx: SafeJSContext, realm: InRealm) -> DomRoot<Self> { + unsafe { Self::from_context(*cx, realm) } } /// Returns the global object of the realm that the given JS object /// was created in, after unwrapping any wrappers. #[allow(unsafe_code)] - pub unsafe fn from_object_maybe_wrapped(mut obj: *mut JSObject) -> Root<Self> { + pub unsafe fn from_object_maybe_wrapped( + mut obj: *mut JSObject, + cx: *mut JSContext, + ) -> DomRoot<Self> { if IsWrapper(obj) { - obj = UnwrapObject(obj, /* stopAtWindowProxy = */ 0); + obj = UnwrapObjectDynamic(obj, cx, /* stopAtWindowProxy = */ 0); assert!(!obj.is_null()); } GlobalScope::from_object(obj) } - #[allow(unsafe_code)] - pub fn get_cx(&self) -> *mut JSContext { - unsafe { - let runtime = JS_GetObjectRuntime( - self.reflector().get_jsobject().get()); - assert!(!runtime.is_null()); - let context = JS_GetContext(runtime); - assert!(!context.is_null()); - context + pub fn add_uncaught_rejection(&self, rejection: HandleObject) { + self.uncaught_rejections + .borrow_mut() + .push(Heap::boxed(rejection.get())); + } + + pub fn remove_uncaught_rejection(&self, rejection: HandleObject) { + let mut uncaught_rejections = self.uncaught_rejections.borrow_mut(); + + if let Some(index) = uncaught_rejections + .iter() + .position(|promise| *promise == Heap::boxed(rejection.get())) + { + uncaught_rejections.remove(index); } } - pub fn crypto(&self) -> Root<Crypto> { - self.crypto.or_init(|| Crypto::new(self)) + pub fn get_uncaught_rejections(&self) -> &DomRefCell<Vec<Box<Heap<*mut JSObject>>>> { + &self.uncaught_rejections + } + + pub fn add_consumed_rejection(&self, rejection: HandleObject) { + self.consumed_rejections + .borrow_mut() + .push(Heap::boxed(rejection.get())); + } + + pub fn remove_consumed_rejection(&self, rejection: HandleObject) { + let mut consumed_rejections = self.consumed_rejections.borrow_mut(); + + if let Some(index) = consumed_rejections + .iter() + .position(|promise| *promise == Heap::boxed(rejection.get())) + { + consumed_rejections.remove(index); + } } - /// Get next worker id. - pub fn get_next_worker_id(&self) -> WorkerId { - let worker_id = self.next_worker_id.get(); - let WorkerId(id_num) = worker_id; - self.next_worker_id.set(WorkerId(id_num + 1)); - worker_id + pub fn get_consumed_rejections(&self) -> &DomRefCell<Vec<Box<Heap<*mut JSObject>>>> { + &self.consumed_rejections + } + + pub fn set_module_map(&self, url: ServoUrl, module: ModuleTree) { + self.module_map.borrow_mut().insert(url, Rc::new(module)); + } + + pub fn get_module_map(&self) -> &DomRefCell<HashMap<ServoUrl, Rc<ModuleTree>>> { + &self.module_map + } + + pub fn set_inline_module_map(&self, script_id: ScriptId, module: ModuleTree) { + self.inline_module_map + .borrow_mut() + .insert(script_id, Rc::new(module)); + } + + pub fn get_inline_module_map(&self) -> &DomRefCell<HashMap<ScriptId, Rc<ModuleTree>>> { + &self.inline_module_map + } + + #[allow(unsafe_code)] + pub fn get_cx(&self) -> SafeJSContext { + unsafe { SafeJSContext::from_ptr(Runtime::get()) } + } + + pub fn crypto(&self) -> DomRoot<Crypto> { + self.crypto.or_init(|| Crypto::new(self)) } pub fn live_devtools_updates(&self) -> bool { @@ -208,9 +2248,11 @@ impl GlobalScope { } pub fn time_end(&self, label: &str) -> Result<u64, ()> { - self.console_timers.borrow_mut().remove(label).ok_or(()).map(|start| { - timestamp_in_ms(get_time()) - start - }) + self.console_timers + .borrow_mut() + .remove(label) + .ok_or(()) + .map(|start| timestamp_in_ms(get_time()) - start) } /// Get an `&IpcSender<ScriptToDevtoolsControlMsg>` to send messages @@ -219,19 +2261,50 @@ impl GlobalScope { self.devtools_chan.as_ref() } + pub fn issue_page_warning(&self, warning: &str) { + if let Some(ref chan) = self.devtools_chan { + let _ = chan.send(ScriptToDevtoolsControlMsg::ReportPageError( + self.pipeline_id.clone(), + PageError { + type_: "PageError".to_string(), + errorMessage: warning.to_string(), + sourceName: self.get_url().to_string(), + lineText: "".to_string(), + lineNumber: 0, + columnNumber: 0, + category: "script".to_string(), + timeStamp: 0, //TODO + error: false, + warning: true, + exception: true, + strict: false, + private: false, + }, + )); + } + } + /// Get a sender to the memory profiler thread. - pub fn mem_profiler_chan(&self) -> &mem::ProfilerChan { + pub fn mem_profiler_chan(&self) -> &profile_mem::ProfilerChan { &self.mem_profiler_chan } /// Get a sender to the time profiler thread. - pub fn time_profiler_chan(&self) -> &time::ProfilerChan { + pub fn time_profiler_chan(&self) -> &profile_time::ProfilerChan { &self.time_profiler_chan } /// Get a sender to the constellation thread. - pub fn constellation_chan(&self) -> &IpcSender<ConstellationMsg> { - &self.constellation_chan + pub fn script_to_constellation_chan(&self) -> &ScriptToConstellationChan { + &self.script_to_constellation_chan + } + + pub fn send_to_embedder(&self, msg: EmbedderMsg) { + self.send_to_constellation(ScriptMsg::ForwardToEmbedder(msg)); + } + + pub fn send_to_constellation(&self, msg: ScriptMsg) { + self.script_to_constellation_chan().send(msg).unwrap(); } pub fn scheduler_chan(&self) -> &IpcSender<TimerSchedulerMsg> { @@ -248,6 +2321,24 @@ impl GlobalScope { &self.origin } + /// Get the creation_url for this global scope + pub fn creation_url(&self) -> &Option<ServoUrl> { + &self.creation_url + } + + pub fn image_cache(&self) -> Arc<dyn ImageCache> { + if let Some(window) = self.downcast::<Window>() { + return window.image_cache(); + } + if let Some(worker) = self.downcast::<DedicatedWorkerGlobalScope>() { + return worker.image_cache(); + } + if let Some(worker) = self.downcast::<PaintWorkletGlobalScope>() { + return worker.image_cache(); + } + unreachable!(); + } + /// Get the [base url](https://html.spec.whatwg.org/multipage/#api-base-url) /// for this global scope. pub fn api_base_url(&self) -> ServoUrl { @@ -259,6 +2350,10 @@ impl GlobalScope { // https://html.spec.whatwg.org/multipage/#script-settings-for-workers:api-base-url return worker.get_url().clone(); } + if let Some(worklet) = self.downcast::<WorkletGlobalScope>() { + // https://drafts.css-houdini.org/worklets/#script-settings-for-worklets + return worklet.base_url(); + } unreachable!(); } @@ -270,15 +2365,56 @@ impl GlobalScope { if let Some(worker) = self.downcast::<WorkerGlobalScope>() { return worker.get_url().clone(); } + if let Some(worklet) = self.downcast::<WorkletGlobalScope>() { + // TODO: is this the right URL to return? + return worklet.base_url(); + } unreachable!(); } + /// Determine the Referrer for a request whose Referrer is "client" + pub fn get_referrer(&self) -> Referrer { + // Step 3 of https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer + if let Some(window) = self.downcast::<Window>() { + // Substep 3.1 + + // Substep 3.1.1 + let mut document = window.Document(); + + // Substep 3.1.2 + if let ImmutableOrigin::Opaque(_) = document.origin().immutable() { + return Referrer::NoReferrer; + } + + let mut url = document.url(); + + // Substep 3.1.3 + while url.as_str() == "about:srcdoc" { + document = document + .browsing_context() + .expect("iframe should have browsing context") + .parent() + .expect("iframes browsing_context should have parent") + .document() + .expect("iframes parent should have document"); + + url = document.url(); + } + + // Substep 3.1.4 + Referrer::Client(url) + } else { + // Substep 3.2 + Referrer::Client(self.get_url()) + } + } + /// Extract a `Window`, panic if the global object is not a `Window`. pub fn as_window(&self) -> &Window { self.downcast::<Window>().expect("expected a Window scope") } - /// https://html.spec.whatwg.org/multipage/#report-the-error + /// <https://html.spec.whatwg.org/multipage/#report-the-error> pub fn report_an_error(&self, error_info: ErrorInfo, value: HandleValue) { // Step 1. if self.in_error_reporting_mode.get() { @@ -288,30 +2424,54 @@ impl GlobalScope { // Step 2. self.in_error_reporting_mode.set(true); - // Steps 3-12. + // Steps 3-6. // FIXME(#13195): muted errors. - let event = ErrorEvent::new(self, - atom!("error"), - EventBubbles::DoesNotBubble, - EventCancelable::Cancelable, - error_info.message.as_str().into(), - error_info.filename.as_str().into(), - error_info.lineno, - error_info.column, - value); - - // Step 13. + let event = ErrorEvent::new( + self, + atom!("error"), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable, + error_info.message.as_str().into(), + error_info.filename.as_str().into(), + error_info.lineno, + error_info.column, + value, + ); + + // Step 7. let event_status = event.upcast::<Event>().fire(self.upcast::<EventTarget>()); - // Step 15 + // Step 8. + self.in_error_reporting_mode.set(false); + + // Step 9. if event_status == EventStatus::NotCanceled { + // https://html.spec.whatwg.org/multipage/#runtime-script-errors-2 if let Some(dedicated) = self.downcast::<DedicatedWorkerGlobalScope>() { dedicated.forward_error_to_worker_object(error_info); + } else if self.is::<Window>() { + if let Some(ref chan) = self.devtools_chan { + let _ = chan.send(ScriptToDevtoolsControlMsg::ReportPageError( + self.pipeline_id.clone(), + PageError { + type_: "PageError".to_string(), + errorMessage: error_info.message.clone(), + sourceName: error_info.filename.clone(), + lineText: "".to_string(), //TODO + lineNumber: error_info.lineno, + columnNumber: error_info.column, + category: "script".to_string(), + timeStamp: 0, //TODO + error: true, + warning: false, + exception: true, + strict: false, + private: false, + }, + )); + } } } - - // Step 14 - self.in_error_reporting_mode.set(false); } /// Get the `&ResourceThreads` for this global scope. @@ -325,7 +2485,7 @@ impl GlobalScope { } /// `ScriptChan` to send messages to the event loop of this global scope. - pub fn script_chan(&self) -> Box<ScriptChan + Send> { + pub fn script_chan(&self) -> Box<dyn ScriptChan + Send> { if let Some(window) = self.downcast::<Window>() { return MainThreadScriptChan(window.main_thread_script_chan().clone()).clone(); } @@ -335,11 +2495,11 @@ impl GlobalScope { unreachable!(); } - /// `ScriptChan` to send messages to the networking task source of - /// this of this global scope. + /// `TaskSource` to send messages to the networking task source of + /// this global scope. pub fn networking_task_source(&self) -> NetworkingTaskSource { if let Some(window) = self.downcast::<Window>() { - return window.networking_task_source(); + return window.task_manager().networking_task_source(); } if let Some(worker) = self.downcast::<WorkerGlobalScope>() { return worker.networking_task_source(); @@ -347,95 +2507,295 @@ impl GlobalScope { unreachable!(); } + /// `TaskSource` to send messages to the port message queue of + /// this global scope. + pub fn port_message_queue(&self) -> PortMessageQueue { + if let Some(window) = self.downcast::<Window>() { + return window.task_manager().port_message_queue(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return worker.port_message_queue(); + } + unreachable!(); + } + + /// `TaskSource` to send messages to the timer queue of + /// this global scope. + pub fn timer_task_source(&self) -> TimerTaskSource { + if let Some(window) = self.downcast::<Window>() { + return window.task_manager().timer_task_source(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return worker.timer_task_source(); + } + unreachable!(); + } + + /// `TaskSource` to send messages to the remote-event task source of + /// this global scope. + pub fn remote_event_task_source(&self) -> RemoteEventTaskSource { + if let Some(window) = self.downcast::<Window>() { + return window.task_manager().remote_event_task_source(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return worker.remote_event_task_source(); + } + unreachable!(); + } + + /// `TaskSource` to send messages to the websocket task source of + /// this global scope. + pub fn websocket_task_source(&self) -> WebsocketTaskSource { + if let Some(window) = self.downcast::<Window>() { + return window.task_manager().websocket_task_source(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return worker.websocket_task_source(); + } + unreachable!(); + } + /// Evaluate JS code on this global scope. pub fn evaluate_js_on_global_with_result( - &self, code: &str, rval: MutableHandleValue) { - self.evaluate_script_on_global_with_result(code, "", rval, 1) + &self, + code: &str, + rval: MutableHandleValue, + fetch_options: ScriptFetchOptions, + script_base_url: ServoUrl, + ) -> bool { + let source_code = SourceCode::Text(Rc::new(DOMString::from_string((*code).to_string()))); + self.evaluate_script_on_global_with_result( + &source_code, + "", + rval, + 1, + fetch_options, + script_base_url, + ) } /// Evaluate a JS script on this global scope. #[allow(unsafe_code)] pub fn evaluate_script_on_global_with_result( - &self, code: &str, filename: &str, rval: MutableHandleValue, line_number: u32) { - let metadata = time::TimerMetadata { + &self, + code: &SourceCode, + filename: &str, + rval: MutableHandleValue, + line_number: u32, + fetch_options: ScriptFetchOptions, + script_base_url: ServoUrl, + ) -> bool { + let metadata = profile_time::TimerMetadata { url: if filename.is_empty() { self.get_url().as_str().into() } else { filename.into() }, - iframe: time::TimerMetadataFrameType::RootWindow, - incremental: time::TimerMetadataReflowType::FirstReflow, + iframe: profile_time::TimerMetadataFrameType::RootWindow, + incremental: profile_time::TimerMetadataReflowType::FirstReflow, }; - time::profile( - time::ProfilerCategory::ScriptEvaluate, + profile_time::profile( + profile_time::ProfilerCategory::ScriptEvaluate, Some(metadata), self.time_profiler_chan().clone(), || { let cx = self.get_cx(); - let globalhandle = self.reflector().get_jsobject(); - let code: Vec<u16> = code.encode_utf16().collect(); - let filename = CString::new(filename).unwrap(); - let _ac = JSAutoCompartment::new(cx, globalhandle.get()); + let ar = enter_realm(&*self); + let _aes = AutoEntryScript::new(self); - let options = CompileOptionsWrapper::new(cx, filename.as_ptr(), line_number); + unsafe { - if !Evaluate2(cx, options.ptr, code.as_ptr(), - code.len() as libc::size_t, - rval) { - debug!("error evaluating JS string"); - report_pending_exception(cx, true); + rooted!(in(*cx) let mut compiled_script = std::ptr::null_mut::<JSScript>()); + match code { + SourceCode::Text(text_code) => { + let options = CompileOptionsWrapper::new(*cx, filename, line_number); + + debug!("compiling dom string"); + compiled_script.set(Compile1( + *cx, + options.ptr, + &mut transform_str_to_source_text(text_code), + )); + + if compiled_script.is_null() { + debug!("error compiling Dom string"); + report_pending_exception(*cx, true, InRealm::Entered(&ar)); + return false; + } + }, + SourceCode::Compiled(pre_compiled_script) => { + compiled_script.set(pre_compiled_script.source_code.get()); + }, + }; + + assert!(!compiled_script.is_null()); + + rooted!(in(*cx) let mut script_private = UndefinedValue()); + JS_GetScriptPrivate(*compiled_script, script_private.handle_mut()); + + // When `ScriptPrivate` for the compiled script is undefined, + // we need to set it so that it can be used in dynamic import context. + if script_private.is_undefined() { + debug!("Set script private for {}", script_base_url); + + let module_script_data = Rc::new(ModuleScript::new( + script_base_url, + fetch_options, + // We can't initialize an module owner here because + // the executing context of script might be different + // from the dynamic import script's executing context. + None, + )); + + SetScriptPrivate( + *compiled_script, + &PrivateValue(Rc::into_raw(module_script_data) as *const _), + ); } - } - maybe_resume_unwind(); - } + let result = JS_ExecuteScript(*cx, compiled_script.handle(), rval); + + if !result { + debug!("error evaluating Dom string"); + report_pending_exception(*cx, true, InRealm::Entered(&ar)); + } + + maybe_resume_unwind(); + result + } + }, ) } + /// <https://html.spec.whatwg.org/multipage/#timer-initialisation-steps> pub fn schedule_callback( - &self, callback: OneshotTimerCallback, duration: MsDuration) - -> OneshotTimerHandle { - self.timers.schedule_callback(callback, duration, self.timer_source()) + &self, + callback: OneshotTimerCallback, + duration: MsDuration, + ) -> OneshotTimerHandle { + self.setup_timers(); + self.timers + .schedule_callback(callback, duration, self.timer_source()) } pub fn unschedule_callback(&self, handle: OneshotTimerHandle) { self.timers.unschedule_callback(handle); } + /// <https://html.spec.whatwg.org/multipage/#timer-initialisation-steps> pub fn set_timeout_or_interval( - &self, - callback: TimerCallback, - arguments: Vec<HandleValue>, - timeout: i32, - is_interval: IsInterval) - -> i32 { + &self, + callback: TimerCallback, + arguments: Vec<HandleValue>, + timeout: i32, + is_interval: IsInterval, + ) -> i32 { + self.setup_timers(); self.timers.set_timeout_or_interval( - self, callback, arguments, timeout, is_interval, self.timer_source()) + self, + callback, + arguments, + timeout, + is_interval, + self.timer_source(), + ) } pub fn clear_timeout_or_interval(&self, handle: i32) { - self.timers.clear_timeout_or_interval(self, handle) + self.timers.clear_timeout_or_interval(self, handle); + } + + pub fn queue_function_as_microtask(&self, callback: Rc<VoidFunction>) { + self.enqueue_microtask(Microtask::User(UserMicrotask { + callback: callback, + pipeline: self.pipeline_id(), + })) + } + + pub fn create_image_bitmap( + &self, + image: ImageBitmapSource, + options: &ImageBitmapOptions, + ) -> Rc<Promise> { + let in_realm_proof = AlreadyInRealm::assert(&self); + let p = Promise::new_in_current_realm(&self, InRealm::Already(&in_realm_proof)); + if options.resizeWidth.map_or(false, |w| w == 0) { + p.reject_error(Error::InvalidState); + return p; + } + + if options.resizeHeight.map_or(false, |w| w == 0) { + p.reject_error(Error::InvalidState); + return p; + } + + let promise = match image { + ImageBitmapSource::HTMLCanvasElement(ref canvas) => { + // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument + if !canvas.is_valid() { + p.reject_error(Error::InvalidState); + return p; + } + + if let Some((data, size)) = canvas.fetch_all_data() { + let data = data + .map(|data| data.to_vec()) + .unwrap_or_else(|| vec![0; size.area() as usize * 4]); + + let image_bitmap = ImageBitmap::new(&self, size.width, size.height).unwrap(); + + image_bitmap.set_bitmap_data(data); + image_bitmap.set_origin_clean(canvas.origin_is_clean()); + p.resolve_native(&(image_bitmap)); + } + p + }, + ImageBitmapSource::OffscreenCanvas(ref canvas) => { + // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument + if !canvas.is_valid() { + p.reject_error(Error::InvalidState); + return p; + } + + if let Some((data, size)) = canvas.fetch_all_data() { + let data = data + .map(|data| data.to_vec()) + .unwrap_or_else(|| vec![0; size.area() as usize * 4]); + + let image_bitmap = ImageBitmap::new(&self, size.width, size.height).unwrap(); + image_bitmap.set_bitmap_data(data); + image_bitmap.set_origin_clean(canvas.origin_is_clean()); + p.resolve_native(&(image_bitmap)); + } + p + }, + _ => { + p.reject_error(Error::NotSupported); + return p; + }, + }; + promise } pub fn fire_timer(&self, handle: TimerEventId) { - self.timers.fire_timer(handle, self) + self.timers.fire_timer(handle, self); } pub fn resume(&self) { - self.timers.resume() + self.timers.resume(); } pub fn suspend(&self) { - self.timers.suspend() + self.timers.suspend(); } pub fn slow_down_timers(&self) { - self.timers.slow_down() + self.timers.slow_down(); } pub fn speed_up_timers(&self) { - self.timers.speed_up() + self.timers.speed_up(); } fn timer_source(&self) -> TimerSource { @@ -448,44 +2808,56 @@ impl GlobalScope { unreachable!(); } - /// Returns a wrapper for runnables to ensure they are cancelled if - /// the global scope is being destroyed. - pub fn get_runnable_wrapper(&self) -> RunnableWrapper { + /// Returns a boolean indicating whether the event-loop + /// where this global is running on can continue running JS. + pub fn can_continue_running(&self) -> bool { + if self.downcast::<Window>().is_some() { + return ScriptThread::can_continue_running(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return !worker.is_closing(); + } + + // TODO: plug worklets into this. + true + } + + /// Returns the task canceller of this global to ensure that everything is + /// properly cancelled when the global scope is destroyed. + pub fn task_canceller(&self, name: TaskSourceName) -> TaskCanceller { if let Some(window) = self.downcast::<Window>() { - return window.get_runnable_wrapper(); + return window.task_manager().task_canceller(name); } if let Some(worker) = self.downcast::<WorkerGlobalScope>() { - return worker.get_runnable_wrapper(); + // Note: the "name" is not passed to the worker, + // because 'closing' it only requires one task canceller for all task sources. + // https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-closing + return worker.task_canceller(); } unreachable!(); } /// Perform a microtask checkpoint. pub fn perform_a_microtask_checkpoint(&self) { - if self.is::<Window>() { - return ScriptThread::invoke_perform_a_microtask_checkpoint(); + // Only perform the checkpoint if we're not shutting down. + if self.can_continue_running() { + self.microtask_queue.checkpoint( + self.get_cx(), + |_| Some(DomRoot::from_ref(self)), + vec![DomRoot::from_ref(self)], + ); } - if let Some(worker) = self.downcast::<WorkerGlobalScope>() { - return worker.perform_a_microtask_checkpoint(); - } - unreachable!(); } /// Enqueue a microtask for subsequent execution. pub fn enqueue_microtask(&self, job: Microtask) { - if self.is::<Window>() { - return ScriptThread::enqueue_microtask(job); - } - if let Some(worker) = self.downcast::<WorkerGlobalScope>() { - return worker.enqueue_microtask(job); - } - unreachable!(); + self.microtask_queue.enqueue(job, self.get_cx()); } /// Create a new sender/receiver pair that can be used to implement an on-demand /// event loop. Used for implementing web APIs that require blocking semantics /// without resorting to nested event loops. - pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) { + pub fn new_script_pair(&self) -> (Box<dyn ScriptChan + Send>, Box<dyn ScriptPort + Send>) { if let Some(window) = self.downcast::<Window>() { return window.new_script_pair(); } @@ -495,9 +2867,15 @@ impl GlobalScope { unreachable!(); } + /// Returns the microtask queue of this global. + pub fn microtask_queue(&self) -> &Rc<MicrotaskQueue> { + &self.microtask_queue + } + /// Process a single event as if it were the next event - /// in the thread queue for this global scope. - pub fn process_event(&self, msg: CommonScriptMsg) { + /// in the queue for the event-loop where this global scope is running on. + /// Returns a boolean indicating whether further events should be processed. + pub fn process_event(&self, msg: CommonScriptMsg) -> bool { if self.is::<Window>() { return ScriptThread::process_event(msg); } @@ -507,11 +2885,21 @@ impl GlobalScope { unreachable!(); } + pub fn dom_manipulation_task_source(&self) -> DOMManipulationTaskSource { + if let Some(window) = self.downcast::<Window>() { + return window.task_manager().dom_manipulation_task_source(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return worker.dom_manipulation_task_source(); + } + unreachable!(); + } + /// Channel to send messages to the file reading task source of /// this of this global scope. pub fn file_reading_task_source(&self) -> FileReadingTaskSource { if let Some(window) = self.downcast::<Window>() { - return window.file_reading_task_source(); + return window.task_manager().file_reading_task_source(); } if let Some(worker) = self.downcast::<WorkerGlobalScope>() { return worker.file_reading_task_source(); @@ -519,32 +2907,175 @@ impl GlobalScope { unreachable!(); } + pub fn runtime_handle(&self) -> ParentRuntime { + if self.is::<Window>() { + ScriptThread::runtime_handle() + } else if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + worker.runtime_handle() + } else { + unreachable!() + } + } + /// Returns the ["current"] global object. /// /// ["current"]: https://html.spec.whatwg.org/multipage/#current #[allow(unsafe_code)] - pub fn current() -> Root<Self> { + pub fn current() -> Option<DomRoot<Self>> { unsafe { let cx = Runtime::get(); assert!(!cx.is_null()); let global = CurrentGlobalOrNull(cx); - global_scope_from_global(global) + if global.is_null() { + None + } else { + Some(global_scope_from_global(global, cx)) + } } } /// Returns the ["entry"] global object. /// /// ["entry"]: https://html.spec.whatwg.org/multipage/#entry - pub fn entry() -> Root<Self> { + pub fn entry() -> DomRoot<Self> { entry_global() } /// Returns the ["incumbent"] global object. /// /// ["incumbent"]: https://html.spec.whatwg.org/multipage/#incumbent - pub fn incumbent() -> Option<Root<Self>> { + pub fn incumbent() -> Option<DomRoot<Self>> { incumbent_global() } + + pub fn performance(&self) -> DomRoot<Performance> { + if let Some(window) = self.downcast::<Window>() { + return window.Performance(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return worker.Performance(); + } + unreachable!(); + } + + /// Channel to send messages to the performance timeline task source + /// of this global scope. + pub fn performance_timeline_task_source(&self) -> PerformanceTimelineTaskSource { + if let Some(window) = self.downcast::<Window>() { + return window.task_manager().performance_timeline_task_source(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return worker.performance_timeline_task_source(); + } + unreachable!(); + } + + // https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute + pub fn supported_performance_entry_types(&self, cx: SafeJSContext) -> JSVal { + if let Some(types) = &*self.frozen_supported_performance_entry_types.borrow() { + return types.get(); + } + + let types: Vec<DOMString> = VALID_ENTRY_TYPES + .iter() + .map(|t| DOMString::from(t.to_string())) + .collect(); + let frozen_types = to_frozen_array(types.as_slice(), cx); + + // Safety: need to create the Heap value in its final memory location before setting it. + *self.frozen_supported_performance_entry_types.borrow_mut() = Some(Heap::default()); + self.frozen_supported_performance_entry_types + .borrow() + .as_ref() + .unwrap() + .set(frozen_types); + + frozen_types + } + + pub fn is_headless(&self) -> bool { + self.is_headless + } + + pub fn get_user_agent(&self) -> Cow<'static, str> { + self.user_agent.clone() + } + + pub fn get_https_state(&self) -> HttpsState { + self.https_state.get() + } + + pub fn set_https_state(&self, https_state: HttpsState) { + self.https_state.set(https_state); + } + + pub fn is_secure_context(&self) -> bool { + if Some(false) == self.inherited_secure_context { + return false; + } + if let Some(creation_url) = self.creation_url() { + if creation_url.scheme() == "blob" && Some(true) == self.inherited_secure_context { + return true; + } + return creation_url.is_potentially_trustworthy(); + } + false + } + + /// https://www.w3.org/TR/CSP/#get-csp-of-object + pub fn get_csp_list(&self) -> Option<CspList> { + if let Some(window) = self.downcast::<Window>() { + return window.Document().get_csp_list().map(|c| c.clone()); + } + // TODO: Worker and Worklet global scopes. + None + } + + pub fn wgpu_id_hub(&self) -> Arc<Mutex<Identities>> { + self.gpu_id_hub.clone() + } + + pub fn add_gpu_device(&self, device: &GPUDevice) { + self.gpu_devices + .borrow_mut() + .insert(device.id(), Dom::from_ref(device)); + } + + pub fn remove_gpu_device(&self, device: WebGPUDevice) { + let _ = self.gpu_devices.borrow_mut().remove(&device); + } + + pub fn handle_wgpu_msg( + &self, + device: WebGPUDevice, + scope: Option<ErrorScopeId>, + result: WebGPUOpResult, + ) { + self.gpu_devices + .borrow() + .get(&device) + .expect("GPUDevice not found") + .handle_server_msg(scope, result); + } + + pub(crate) fn current_group_label(&self) -> Option<DOMString> { + self.console_group_stack + .borrow() + .last() + .map(|label| DOMString::from(format!("[{}]", label))) + } + + pub(crate) fn push_console_group(&self, group: DOMString) { + self.console_group_stack.borrow_mut().push(group); + } + + pub(crate) fn pop_console_group(&self) { + let _ = self.console_group_stack.borrow_mut().pop(); + } + + pub(crate) fn dynamic_module_list(&self) -> RefMut<DynamicModuleList> { + self.dynamic_modules.borrow_mut() + } } fn timestamp_in_ms(time: Timespec) -> u64 { @@ -553,9 +3084,27 @@ fn timestamp_in_ms(time: Timespec) -> u64 { /// Returns the Rust global scope from a JS global object. #[allow(unsafe_code)] -unsafe fn global_scope_from_global(global: *mut JSObject) -> Root<GlobalScope> { +unsafe fn global_scope_from_global( + global: *mut JSObject, + cx: *mut JSContext, +) -> DomRoot<GlobalScope> { + assert!(!global.is_null()); + let clasp = get_object_class(global); + assert_ne!( + ((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)), + 0 + ); + root_from_object(global, cx).unwrap() +} + +/// Returns the Rust global scope from a JS global object. +#[allow(unsafe_code)] +unsafe fn global_scope_from_global_static(global: *mut JSObject) -> DomRoot<GlobalScope> { assert!(!global.is_null()); let clasp = get_object_class(global); - assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0); - root_from_object(global).unwrap() + assert_ne!( + ((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)), + 0 + ); + root_from_object_static(global).unwrap() } diff --git a/components/script/dom/gpu.rs b/components/script/dom/gpu.rs new file mode 100644 index 00000000000..eea21c1b988 --- /dev/null +++ b/components/script/dom/gpu.rs @@ -0,0 +1,157 @@ +/* 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 crate::dom::bindings::codegen::Bindings::GPUBinding::GPURequestAdapterOptions; +use crate::dom::bindings::codegen::Bindings::GPUBinding::{GPUMethods, GPUPowerPreference}; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpuadapter::GPUAdapter; +use crate::dom::promise::Promise; +use crate::realms::InRealm; +use crate::task_source::{TaskSource, TaskSourceName}; +use dom_struct::dom_struct; +use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::router::ROUTER; +use js::jsapi::Heap; +use script_traits::ScriptMsg; +use std::rc::Rc; +use webgpu::wgt::PowerPreference; +use webgpu::{wgpu, WebGPUResponse, WebGPUResponseResult}; + +#[dom_struct] +pub struct GPU { + reflector_: Reflector, +} + +impl GPU { + pub fn new_inherited() -> GPU { + GPU { + reflector_: Reflector::new(), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<GPU> { + reflect_dom_object(Box::new(GPU::new_inherited()), global) + } +} + +pub trait AsyncWGPUListener { + fn handle_response(&self, response: WebGPUResponseResult, promise: &Rc<Promise>); +} + +struct WGPUResponse<T: AsyncWGPUListener + DomObject> { + trusted: TrustedPromise, + receiver: Trusted<T>, +} + +impl<T: AsyncWGPUListener + DomObject> WGPUResponse<T> { + #[allow(unrooted_must_root)] + fn response(self, response: WebGPUResponseResult) { + let promise = self.trusted.root(); + self.receiver.root().handle_response(response, &promise); + } +} + +pub fn response_async<T: AsyncWGPUListener + DomObject + 'static>( + promise: &Rc<Promise>, + receiver: &T, +) -> IpcSender<WebGPUResponseResult> { + let (action_sender, action_receiver) = ipc::channel().unwrap(); + let task_source = receiver.global().dom_manipulation_task_source(); + let canceller = receiver + .global() + .task_canceller(TaskSourceName::DOMManipulation); + let mut trusted = Some(TrustedPromise::new(promise.clone())); + let trusted_receiver = Trusted::new(receiver); + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + let trusted = if let Some(trusted) = trusted.take() { + trusted + } else { + error!("WebGPU callback called twice!"); + return; + }; + + let context = WGPUResponse { + trusted, + receiver: trusted_receiver.clone(), + }; + let result = task_source.queue_with_canceller( + task!(process_webgpu_task: move|| { + context.response(message.to().unwrap()); + }), + &canceller, + ); + if let Err(err) = result { + error!("Failed to queue GPU listener-task: {:?}", err); + } + }), + ); + action_sender +} + +impl GPUMethods for GPU { + // https://gpuweb.github.io/gpuweb/#dom-gpu-requestadapter + fn RequestAdapter(&self, options: &GPURequestAdapterOptions, comp: InRealm) -> Rc<Promise> { + let global = &self.global(); + let promise = Promise::new_in_current_realm(global, comp); + let sender = response_async(&promise, self); + let power_preference = match options.powerPreference { + Some(GPUPowerPreference::Low_power) => PowerPreference::LowPower, + Some(GPUPowerPreference::High_performance) => PowerPreference::HighPerformance, + None => PowerPreference::Default, + }; + let ids = global.wgpu_id_hub().lock().create_adapter_ids(); + + let script_to_constellation_chan = global.script_to_constellation_chan(); + if script_to_constellation_chan + .send(ScriptMsg::RequestAdapter( + sender, + wgpu::instance::RequestAdapterOptions { + power_preference, + compatible_surface: None, + }, + ids, + )) + .is_err() + { + promise.reject_error(Error::Operation); + } + promise + } +} + +impl AsyncWGPUListener for GPU { + fn handle_response(&self, response: WebGPUResponseResult, promise: &Rc<Promise>) { + match response { + Ok(WebGPUResponse::RequestAdapter { + adapter_name, + adapter_id, + channel, + }) => { + let adapter = GPUAdapter::new( + &self.global(), + channel, + DOMString::from(format!("{} ({:?})", adapter_name, adapter_id.0.backend())), + Heap::default(), + adapter_id, + ); + promise.resolve_native(&adapter); + }, + Err(e) => { + warn!("Could not get GPUAdapter ({:?})", e); + promise.resolve_native(&None::<GPUAdapter>); + }, + _ => { + warn!("GPU received wrong WebGPUResponse"); + promise.reject_error(Error::Operation); + }, + } + } +} diff --git a/components/script/dom/gpuadapter.rs b/components/script/dom/gpuadapter.rs new file mode 100644 index 00000000000..e4876416952 --- /dev/null +++ b/components/script/dom/gpuadapter.rs @@ -0,0 +1,203 @@ +/* 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 crate::dom::bindings::codegen::Bindings::GPUAdapterBinding::{ + GPUAdapterMethods, GPUDeviceDescriptor, GPUExtensionName, GPULimits, +}; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpu::response_async; +use crate::dom::gpu::AsyncWGPUListener; +use crate::dom::gpudevice::GPUDevice; +use crate::dom::promise::Promise; +use crate::realms::InRealm; +use crate::script_runtime::JSContext as SafeJSContext; +use dom_struct::dom_struct; +use js::jsapi::{Heap, JSObject}; +use std::ptr::NonNull; +use std::rc::Rc; +use webgpu::{wgt, WebGPU, WebGPUAdapter, WebGPURequest, WebGPUResponse, WebGPUResponseResult}; + +#[dom_struct] +pub struct GPUAdapter { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + channel: WebGPU, + name: DOMString, + #[ignore_malloc_size_of = "mozjs"] + extensions: Heap<*mut JSObject>, + adapter: WebGPUAdapter, +} + +impl GPUAdapter { + fn new_inherited( + channel: WebGPU, + name: DOMString, + extensions: Heap<*mut JSObject>, + adapter: WebGPUAdapter, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + name, + extensions, + adapter, + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + name: DOMString, + extensions: Heap<*mut JSObject>, + adapter: WebGPUAdapter, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUAdapter::new_inherited( + channel, name, extensions, adapter, + )), + global, + ) + } +} + +impl GPUAdapterMethods for GPUAdapter { + // https://gpuweb.github.io/gpuweb/#dom-gpuadapter-name + fn Name(&self) -> DOMString { + self.name.clone() + } + + // https://gpuweb.github.io/gpuweb/#dom-gpuadapter-extensions + fn Extensions(&self, _cx: SafeJSContext) -> NonNull<JSObject> { + NonNull::new(self.extensions.get()).unwrap() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestdevice + fn RequestDevice(&self, descriptor: &GPUDeviceDescriptor, comp: InRealm) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&self.global(), comp); + let sender = response_async(&promise, self); + let mut features = wgt::Features::empty(); + for &ext in descriptor.extensions.iter() { + if ext == GPUExtensionName::Depth_clamping { + features.insert(wgt::Features::DEPTH_CLAMPING); + } else if ext == GPUExtensionName::Texture_compression_bc { + features.insert(wgt::Features::TEXTURE_COMPRESSION_BC) + } + } + + let desc = wgt::DeviceDescriptor { + features, + limits: wgt::Limits { + max_bind_groups: descriptor.limits.maxBindGroups, + max_dynamic_uniform_buffers_per_pipeline_layout: descriptor + .limits + .maxDynamicUniformBuffersPerPipelineLayout, + max_dynamic_storage_buffers_per_pipeline_layout: descriptor + .limits + .maxDynamicStorageBuffersPerPipelineLayout, + max_sampled_textures_per_shader_stage: descriptor + .limits + .maxSampledTexturesPerShaderStage, + max_samplers_per_shader_stage: descriptor.limits.maxSamplersPerShaderStage, + max_storage_buffers_per_shader_stage: descriptor + .limits + .maxStorageBuffersPerShaderStage, + max_storage_textures_per_shader_stage: descriptor + .limits + .maxStorageTexturesPerShaderStage, + max_uniform_buffers_per_shader_stage: descriptor + .limits + .maxUniformBuffersPerShaderStage, + max_uniform_buffer_binding_size: descriptor.limits.maxUniformBufferBindingSize, + ..Default::default() + }, + shader_validation: true, + }; + let id = self + .global() + .wgpu_id_hub() + .lock() + .create_device_id(self.adapter.0.backend()); + let pipeline_id = self.global().pipeline_id(); + if self + .channel + .0 + .send(( + None, + WebGPURequest::RequestDevice { + sender, + adapter_id: self.adapter, + descriptor: desc, + device_id: id, + pipeline_id, + label: descriptor.parent.label.as_ref().map(|s| s.to_string()), + }, + )) + .is_err() + { + promise.reject_error(Error::Operation); + } + promise + } +} + +impl AsyncWGPUListener for GPUAdapter { + fn handle_response(&self, response: WebGPUResponseResult, promise: &Rc<Promise>) { + match response { + Ok(WebGPUResponse::RequestDevice { + device_id, + queue_id, + descriptor, + label, + }) => { + let limits = GPULimits { + maxBindGroups: descriptor.limits.max_bind_groups, + maxDynamicStorageBuffersPerPipelineLayout: descriptor + .limits + .max_dynamic_storage_buffers_per_pipeline_layout, + maxDynamicUniformBuffersPerPipelineLayout: descriptor + .limits + .max_dynamic_uniform_buffers_per_pipeline_layout, + maxSampledTexturesPerShaderStage: descriptor + .limits + .max_sampled_textures_per_shader_stage, + maxSamplersPerShaderStage: descriptor.limits.max_samplers_per_shader_stage, + maxStorageBuffersPerShaderStage: descriptor + .limits + .max_storage_buffers_per_shader_stage, + maxStorageTexturesPerShaderStage: descriptor + .limits + .max_storage_textures_per_shader_stage, + maxUniformBufferBindingSize: descriptor.limits.max_uniform_buffer_binding_size, + maxUniformBuffersPerShaderStage: descriptor + .limits + .max_uniform_buffers_per_shader_stage, + }; + let device = GPUDevice::new( + &self.global(), + self.channel.clone(), + &self, + Heap::default(), + limits, + device_id, + queue_id, + label, + ); + self.global().add_gpu_device(&device); + promise.resolve_native(&device); + }, + Err(e) => { + warn!("Could not get GPUDevice({:?})", e); + promise.reject_error(Error::Operation); + }, + _ => { + warn!("GPUAdapter received wrong WebGPUResponse"); + promise.reject_error(Error::Operation); + }, + } + } +} diff --git a/components/script/dom/gpubindgroup.rs b/components/script/dom/gpubindgroup.rs new file mode 100644 index 00000000000..394cf41e915 --- /dev/null +++ b/components/script/dom/gpubindgroup.rs @@ -0,0 +1,72 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUBindGroupBinding::GPUBindGroupMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; +use dom_struct::dom_struct; +use webgpu::{WebGPUBindGroup, WebGPUDevice}; + +#[dom_struct] +pub struct GPUBindGroup { + reflector_: Reflector, + label: DomRefCell<Option<USVString>>, + bind_group: WebGPUBindGroup, + device: WebGPUDevice, + layout: Dom<GPUBindGroupLayout>, +} + +impl GPUBindGroup { + fn new_inherited( + bind_group: WebGPUBindGroup, + device: WebGPUDevice, + layout: &GPUBindGroupLayout, + label: Option<USVString>, + ) -> Self { + Self { + reflector_: Reflector::new(), + label: DomRefCell::new(label), + bind_group, + device, + layout: Dom::from_ref(layout), + } + } + + pub fn new( + global: &GlobalScope, + bind_group: WebGPUBindGroup, + device: WebGPUDevice, + layout: &GPUBindGroupLayout, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUBindGroup::new_inherited( + bind_group, device, layout, label, + )), + global, + ) + } +} + +impl GPUBindGroup { + pub fn id(&self) -> &WebGPUBindGroup { + &self.bind_group + } +} + +impl GPUBindGroupMethods for GPUBindGroup { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/gpubindgrouplayout.rs b/components/script/dom/gpubindgrouplayout.rs new file mode 100644 index 00000000000..ce794269359 --- /dev/null +++ b/components/script/dom/gpubindgrouplayout.rs @@ -0,0 +1,58 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUBindGroupLayoutBinding::GPUBindGroupLayoutMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use webgpu::WebGPUBindGroupLayout; + +#[dom_struct] +pub struct GPUBindGroupLayout { + reflector_: Reflector, + label: DomRefCell<Option<USVString>>, + bind_group_layout: WebGPUBindGroupLayout, +} + +impl GPUBindGroupLayout { + fn new_inherited(bind_group_layout: WebGPUBindGroupLayout, label: Option<USVString>) -> Self { + Self { + reflector_: Reflector::new(), + label: DomRefCell::new(label), + bind_group_layout, + } + } + + pub fn new( + global: &GlobalScope, + bind_group_layout: WebGPUBindGroupLayout, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUBindGroupLayout::new_inherited(bind_group_layout, label)), + global, + ) + } +} + +impl GPUBindGroupLayout { + pub fn id(&self) -> WebGPUBindGroupLayout { + self.bind_group_layout + } +} + +impl GPUBindGroupLayoutMethods for GPUBindGroupLayout { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/gpubuffer.rs b/components/script/dom/gpubuffer.rs new file mode 100644 index 00000000000..60a6075cb7f --- /dev/null +++ b/components/script/dom/gpubuffer.rs @@ -0,0 +1,378 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUBufferBinding::{GPUBufferMethods, GPUSize64}; +use crate::dom::bindings::codegen::Bindings::GPUMapModeBinding::GPUMapModeConstants; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpu::{response_async, AsyncWGPUListener}; +use crate::dom::gpudevice::GPUDevice; +use crate::dom::promise::Promise; +use crate::realms::InRealm; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSharedMemory; +use js::jsapi::DetachArrayBuffer; +use js::jsapi::NewExternalArrayBuffer; +use js::jsapi::{Heap, JSObject}; +use std::cell::{Cell, RefCell}; +use std::ffi::c_void; +use std::ops::Range; +use std::ptr::NonNull; +use std::rc::Rc; +use std::string::String; +use webgpu::{ + identity::WebGPUOpResult, wgpu::device::HostMap, WebGPU, WebGPUBuffer, WebGPURequest, + WebGPUResponse, WebGPUResponseResult, +}; + +const RANGE_OFFSET_ALIGN_MASK: u64 = 8; +const RANGE_SIZE_ALIGN_MASK: u64 = 4; + +// https://gpuweb.github.io/gpuweb/#buffer-state +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] +pub enum GPUBufferState { + Mapped, + MappedAtCreation, + MappingPending, + Unmapped, + Destroyed, +} + +#[derive(JSTraceable, MallocSizeOf)] +pub struct GPUBufferMapInfo { + #[ignore_malloc_size_of = "Rc"] + pub mapping: Rc<RefCell<Vec<u8>>>, + pub mapping_range: Range<u64>, + pub mapped_ranges: Vec<Range<u64>>, + #[ignore_malloc_size_of = "defined in mozjs"] + pub js_buffers: Vec<Box<Heap<*mut JSObject>>>, + pub map_mode: Option<u32>, +} + +#[dom_struct] +pub struct GPUBuffer { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + channel: WebGPU, + label: DomRefCell<Option<USVString>>, + state: Cell<GPUBufferState>, + buffer: WebGPUBuffer, + device: Dom<GPUDevice>, + size: GPUSize64, + #[ignore_malloc_size_of = "promises are hard"] + map_promise: DomRefCell<Option<Rc<Promise>>>, + map_info: DomRefCell<Option<GPUBufferMapInfo>>, +} + +impl GPUBuffer { + fn new_inherited( + channel: WebGPU, + buffer: WebGPUBuffer, + device: &GPUDevice, + state: GPUBufferState, + size: GPUSize64, + map_info: DomRefCell<Option<GPUBufferMapInfo>>, + label: Option<USVString>, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + label: DomRefCell::new(label), + state: Cell::new(state), + device: Dom::from_ref(device), + buffer, + map_promise: DomRefCell::new(None), + size, + map_info, + } + } + + #[allow(unsafe_code)] + pub fn new( + global: &GlobalScope, + channel: WebGPU, + buffer: WebGPUBuffer, + device: &GPUDevice, + state: GPUBufferState, + size: GPUSize64, + map_info: DomRefCell<Option<GPUBufferMapInfo>>, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUBuffer::new_inherited( + channel, buffer, device, state, size, map_info, label, + )), + global, + ) + } +} + +impl GPUBuffer { + pub fn id(&self) -> WebGPUBuffer { + self.buffer + } + + pub fn state(&self) -> GPUBufferState { + self.state.get() + } +} + +impl Drop for GPUBuffer { + fn drop(&mut self) { + self.Destroy() + } +} + +impl GPUBufferMethods for GPUBuffer { + #[allow(unsafe_code)] + /// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap + fn Unmap(&self) { + let cx = self.global().get_cx(); + // Step 1 + match self.state.get() { + GPUBufferState::Unmapped | GPUBufferState::Destroyed => { + // TODO: Record validation error on the current scope + return; + }, + // Step 3 + GPUBufferState::Mapped | GPUBufferState::MappedAtCreation => { + let mut info = self.map_info.borrow_mut(); + let m_info = info.as_mut().unwrap(); + let m_range = m_info.mapping_range.clone(); + if let Err(e) = self.channel.0.send(( + self.device.use_current_scope(), + WebGPURequest::UnmapBuffer { + buffer_id: self.id().0, + device_id: self.device.id().0, + array_buffer: IpcSharedMemory::from_bytes( + m_info.mapping.borrow().as_slice(), + ), + is_map_read: m_info.map_mode == Some(GPUMapModeConstants::READ), + offset: m_range.start, + size: m_range.end - m_range.start, + }, + )) { + warn!("Failed to send Buffer unmap ({:?}) ({})", self.buffer.0, e); + } + // Step 3.3 + m_info.js_buffers.drain(..).for_each(|obj| unsafe { + DetachArrayBuffer(*cx, obj.handle()); + }); + }, + // Step 2 + GPUBufferState::MappingPending => { + let promise = self.map_promise.borrow_mut().take().unwrap(); + promise.reject_error(Error::Operation); + }, + }; + // Step 4 + self.state.set(GPUBufferState::Unmapped); + *self.map_info.borrow_mut() = None; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy + fn Destroy(&self) { + let state = self.state.get(); + match state { + GPUBufferState::Mapped | GPUBufferState::MappedAtCreation => { + self.Unmap(); + }, + GPUBufferState::Destroyed => return, + _ => {}, + }; + if let Err(e) = self + .channel + .0 + .send((None, WebGPURequest::DestroyBuffer(self.buffer.0))) + { + warn!( + "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})", + self.buffer.0, e + ); + }; + self.state.set(GPUBufferState::Destroyed); + } + + #[allow(unsafe_code)] + /// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapasync-offset-size + fn MapAsync( + &self, + mode: u32, + offset: GPUSize64, + size: Option<GPUSize64>, + comp: InRealm, + ) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&self.global(), comp); + let range_size = if let Some(s) = size { + s + } else if offset >= self.size { + promise.reject_error(Error::Operation); + return promise; + } else { + self.size - offset + }; + let scope_id = self.device.use_current_scope(); + if self.state.get() != GPUBufferState::Unmapped { + self.device.handle_server_msg( + scope_id, + WebGPUOpResult::ValidationError(String::from("Buffer is not Unmapped")), + ); + promise.reject_error(Error::Abort); + return promise; + } + let host_map = match mode { + GPUMapModeConstants::READ => HostMap::Read, + GPUMapModeConstants::WRITE => HostMap::Write, + _ => { + self.device.handle_server_msg( + scope_id, + WebGPUOpResult::ValidationError(String::from("Invalid MapModeFlags")), + ); + promise.reject_error(Error::Abort); + return promise; + }, + }; + + let map_range = offset..offset + range_size; + + let sender = response_async(&promise, self); + if let Err(e) = self.channel.0.send(( + scope_id, + WebGPURequest::BufferMapAsync { + sender, + buffer_id: self.buffer.0, + device_id: self.device.id().0, + host_map, + map_range: map_range.clone(), + }, + )) { + warn!( + "Failed to send BufferMapAsync ({:?}) ({})", + self.buffer.0, e + ); + promise.reject_error(Error::Operation); + return promise; + } + + self.state.set(GPUBufferState::MappingPending); + *self.map_info.borrow_mut() = Some(GPUBufferMapInfo { + mapping: Rc::new(RefCell::new(Vec::with_capacity(0))), + mapping_range: map_range, + mapped_ranges: Vec::new(), + js_buffers: Vec::new(), + map_mode: Some(mode), + }); + *self.map_promise.borrow_mut() = Some(promise.clone()); + promise + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange + #[allow(unsafe_code)] + fn GetMappedRange( + &self, + cx: JSContext, + offset: GPUSize64, + size: Option<GPUSize64>, + ) -> Fallible<NonNull<JSObject>> { + let range_size = if let Some(s) = size { + s + } else if offset >= self.size { + return Err(Error::Operation); + } else { + self.size - offset + }; + let m_end = offset + range_size; + let mut info = self.map_info.borrow_mut(); + let m_info = info.as_mut().unwrap(); + + let mut valid = match self.state.get() { + GPUBufferState::Mapped | GPUBufferState::MappedAtCreation => true, + _ => false, + }; + valid &= offset % RANGE_OFFSET_ALIGN_MASK == 0 && + range_size % RANGE_SIZE_ALIGN_MASK == 0 && + offset >= m_info.mapping_range.start && + m_end <= m_info.mapping_range.end; + valid &= m_info + .mapped_ranges + .iter() + .all(|range| range.start >= m_end || range.end <= offset); + if !valid { + return Err(Error::Operation); + } + + unsafe extern "C" fn free_func(_contents: *mut c_void, free_user_data: *mut c_void) { + let _ = Rc::from_raw(free_user_data as _); + } + + let array_buffer = unsafe { + NewExternalArrayBuffer( + *cx, + range_size as usize, + m_info.mapping.borrow_mut()[offset as usize..m_end as usize].as_mut_ptr() as _, + Some(free_func), + Rc::into_raw(m_info.mapping.clone()) as _, + ) + }; + + m_info.mapped_ranges.push(offset..m_end); + m_info.js_buffers.push(Heap::boxed(array_buffer)); + + Ok(NonNull::new(array_buffer).unwrap()) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } +} + +impl AsyncWGPUListener for GPUBuffer { + #[allow(unsafe_code)] + fn handle_response(&self, response: WebGPUResponseResult, promise: &Rc<Promise>) { + match response { + Ok(WebGPUResponse::BufferMapAsync(bytes)) => { + *self + .map_info + .borrow_mut() + .as_mut() + .unwrap() + .mapping + .borrow_mut() = bytes.to_vec(); + promise.resolve_native(&()); + self.state.set(GPUBufferState::Mapped); + }, + Err(e) => { + warn!("Could not map buffer({:?})", e); + promise.reject_error(Error::Abort); + }, + _ => { + warn!("GPUBuffer received wrong WebGPUResponse"); + promise.reject_error(Error::Operation); + }, + } + *self.map_promise.borrow_mut() = None; + if let Err(e) = self + .channel + .0 + .send((None, WebGPURequest::BufferMapComplete(self.buffer.0))) + { + warn!( + "Failed to send BufferMapComplete({:?}) ({})", + self.buffer.0, e + ); + } + } +} diff --git a/components/script/dom/gpubufferusage.rs b/components/script/dom/gpubufferusage.rs new file mode 100644 index 00000000000..9b3a97d26fd --- /dev/null +++ b/components/script/dom/gpubufferusage.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::bindings::reflector::Reflector; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct GPUBufferUsage { + reflector_: Reflector, +} diff --git a/components/script/dom/gpucanvascontext.rs b/components/script/dom/gpucanvascontext.rs new file mode 100644 index 00000000000..c0391d7ca61 --- /dev/null +++ b/components/script/dom/gpucanvascontext.rs @@ -0,0 +1,222 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::{ + GPUCanvasContextMethods, GPUSwapChainDescriptor, +}; +use crate::dom::bindings::codegen::Bindings::GPUDeviceBinding::GPUDeviceBinding::GPUDeviceMethods; +use crate::dom::bindings::codegen::Bindings::GPUObjectBaseBinding::GPUObjectDescriptorBase; +use crate::dom::bindings::codegen::Bindings::GPUTextureBinding::{ + GPUExtent3D, GPUExtent3DDict, GPUTextureDescriptor, GPUTextureDimension, GPUTextureFormat, +}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpuswapchain::GPUSwapChain; +use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutCanvasRenderingContextHelpers}; +use crate::dom::node::{document_from_node, Node, NodeDamage}; +use arrayvec::ArrayVec; +use dom_struct::dom_struct; +use euclid::default::Size2D; +use ipc_channel::ipc; +use script_layout_interface::HTMLCanvasDataSource; +use std::cell::Cell; +use webgpu::{wgpu::id, wgt, WebGPU, WebGPURequest, PRESENTATION_BUFFER_COUNT}; + +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)] +pub struct WebGPUContextId(pub u64); + +#[dom_struct] +pub struct GPUCanvasContext { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + channel: WebGPU, + canvas: Dom<HTMLCanvasElement>, + size: Cell<Size2D<u32>>, + swap_chain: DomRefCell<Option<Dom<GPUSwapChain>>>, + #[ignore_malloc_size_of = "Defined in webrender"] + webrender_image: Cell<Option<webrender_api::ImageKey>>, + context_id: WebGPUContextId, +} + +impl GPUCanvasContext { + fn new_inherited(canvas: &HTMLCanvasElement, size: Size2D<u32>, channel: WebGPU) -> Self { + let (sender, receiver) = ipc::channel().unwrap(); + if let Err(e) = channel.0.send((None, WebGPURequest::CreateContext(sender))) { + warn!("Failed to send CreateContext ({:?})", e); + } + let external_id = receiver.recv().unwrap(); + Self { + reflector_: Reflector::new(), + channel, + canvas: Dom::from_ref(canvas), + size: Cell::new(size), + swap_chain: DomRefCell::new(None), + webrender_image: Cell::new(None), + context_id: WebGPUContextId(external_id.0), + } + } + + pub fn new( + global: &GlobalScope, + canvas: &HTMLCanvasElement, + size: Size2D<u32>, + channel: WebGPU, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUCanvasContext::new_inherited(canvas, size, channel)), + global, + ) + } +} + +impl GPUCanvasContext { + fn layout_handle(&self) -> HTMLCanvasDataSource { + let image_key = if self.webrender_image.get().is_some() { + self.webrender_image.get().unwrap() + } else { + webrender_api::ImageKey::DUMMY + }; + HTMLCanvasDataSource::WebGPU(image_key) + } + + pub fn send_swap_chain_present(&self) { + let texture_id = self.swap_chain.borrow().as_ref().unwrap().texture_id().0; + let encoder_id = self + .global() + .wgpu_id_hub() + .lock() + .create_command_encoder_id(texture_id.backend()); + if let Err(e) = self.channel.0.send(( + None, + WebGPURequest::SwapChainPresent { + external_id: self.context_id.0, + texture_id, + encoder_id, + }, + )) { + warn!( + "Failed to send UpdateWebrenderData({:?}) ({})", + self.context_id, e + ); + } + } + + pub fn context_id(&self) -> WebGPUContextId { + self.context_id + } + + pub fn mark_as_dirty(&self) { + self.canvas + .upcast::<Node>() + .dirty(NodeDamage::OtherNodeDamage); + + let document = document_from_node(&*self.canvas); + document.add_dirty_webgpu_canvas(self); + } +} + +impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, GPUCanvasContext> { + #[allow(unsafe_code)] + unsafe fn canvas_data_source(self) -> HTMLCanvasDataSource { + (*self.unsafe_get()).layout_handle() + } +} + +impl GPUCanvasContextMethods for GPUCanvasContext { + /// https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-configureswapchain + fn ConfigureSwapChain(&self, descriptor: &GPUSwapChainDescriptor) -> DomRoot<GPUSwapChain> { + if let Some(chain) = &*self.swap_chain.borrow() { + chain.destroy(self.context_id.0, self.webrender_image.get().unwrap()); + self.webrender_image.set(None); + } + *self.swap_chain.borrow_mut() = None; + + let mut buffer_ids = ArrayVec::<[id::BufferId; PRESENTATION_BUFFER_COUNT]>::new(); + for _ in 0..PRESENTATION_BUFFER_COUNT { + buffer_ids.push( + self.global() + .wgpu_id_hub() + .lock() + .create_buffer_id(descriptor.device.id().0.backend()), + ); + } + + let image_desc = webrender_api::ImageDescriptor { + format: match descriptor.format { + GPUTextureFormat::Rgba8unorm => webrender_api::ImageFormat::RGBA8, + GPUTextureFormat::Bgra8unorm => webrender_api::ImageFormat::BGRA8, + _ => panic!("SwapChain format({:?}) not supported", descriptor.format), + }, + size: webrender_api::units::DeviceIntSize::new( + self.size.get().width as i32, + self.size.get().height as i32, + ), + stride: Some( + (((self.size.get().width * 4) | (wgt::COPY_BYTES_PER_ROW_ALIGNMENT - 1)) + 1) + as i32, + ), + offset: 0, + flags: webrender_api::ImageDescriptorFlags::from_bits(1).unwrap(), + }; + + let image_data = webrender_api::ImageData::External(webrender_api::ExternalImageData { + id: webrender_api::ExternalImageId(self.context_id.0), + channel_index: 0, + image_type: webrender_api::ExternalImageType::Buffer, + }); + + let (sender, receiver) = ipc::channel().unwrap(); + + self.channel + .0 + .send(( + None, + WebGPURequest::CreateSwapChain { + device_id: descriptor.device.id().0, + buffer_ids, + external_id: self.context_id.0, + sender, + image_desc, + image_data, + }, + )) + .expect("Failed to create WebGPU SwapChain"); + + let usage = if descriptor.usage % 2 == 0 { + descriptor.usage + 1 + } else { + descriptor.usage + }; + let text_desc = GPUTextureDescriptor { + parent: GPUObjectDescriptorBase { label: None }, + dimension: GPUTextureDimension::_2d, + format: descriptor.format, + mipLevelCount: 1, + sampleCount: 1, + usage, + size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict { + width: self.size.get().width, + height: self.size.get().height, + depth: 1, + }), + }; + + let texture = descriptor.device.CreateTexture(&text_desc); + + self.webrender_image.set(Some(receiver.recv().unwrap())); + + let swap_chain = GPUSwapChain::new( + &self.global(), + self.channel.clone(), + &self, + &*texture, + descriptor.parent.label.as_ref().cloned(), + ); + *self.swap_chain.borrow_mut() = Some(Dom::from_ref(&*swap_chain)); + swap_chain + } +} diff --git a/components/script/dom/gpucolorwrite.rs b/components/script/dom/gpucolorwrite.rs new file mode 100644 index 00000000000..c34799d9982 --- /dev/null +++ b/components/script/dom/gpucolorwrite.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::bindings::reflector::Reflector; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct GPUColorWrite { + reflector_: Reflector, +} diff --git a/components/script/dom/gpucommandbuffer.rs b/components/script/dom/gpucommandbuffer.rs new file mode 100644 index 00000000000..eb6ede84c26 --- /dev/null +++ b/components/script/dom/gpucommandbuffer.rs @@ -0,0 +1,104 @@ +/* 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 crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::GPUCommandBufferBinding::GPUCommandBufferMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubuffer::GPUBuffer; +use dom_struct::dom_struct; +use std::collections::HashSet; +use std::hash::{Hash, Hasher}; +use webgpu::{WebGPU, WebGPUCommandBuffer, WebGPURequest}; + +impl Eq for DomRoot<GPUBuffer> {} +impl Hash for DomRoot<GPUBuffer> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.id().hash(state); + } +} + +#[dom_struct] +pub struct GPUCommandBuffer { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + channel: WebGPU, + label: DomRefCell<Option<USVString>>, + command_buffer: WebGPUCommandBuffer, + buffers: DomRefCell<HashSet<Dom<GPUBuffer>>>, +} + +impl GPUCommandBuffer { + fn new_inherited( + channel: WebGPU, + command_buffer: WebGPUCommandBuffer, + buffers: HashSet<DomRoot<GPUBuffer>>, + label: Option<USVString>, + ) -> Self { + Self { + channel, + reflector_: Reflector::new(), + label: DomRefCell::new(label), + command_buffer, + buffers: DomRefCell::new(buffers.into_iter().map(|b| Dom::from_ref(&*b)).collect()), + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + command_buffer: WebGPUCommandBuffer, + buffers: HashSet<DomRoot<GPUBuffer>>, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUCommandBuffer::new_inherited( + channel, + command_buffer, + buffers, + label, + )), + global, + ) + } +} + +impl Drop for GPUCommandBuffer { + fn drop(&mut self) { + if let Err(e) = self.channel.0.send(( + None, + WebGPURequest::FreeCommandBuffer(self.command_buffer.0), + )) { + warn!( + "Failed to send FreeCommandBuffer({:?}) ({})", + self.command_buffer.0, e + ); + } + } +} + +impl GPUCommandBuffer { + pub fn id(&self) -> WebGPUCommandBuffer { + self.command_buffer + } + + pub fn buffers(&self) -> Ref<HashSet<Dom<GPUBuffer>>> { + self.buffers.borrow() + } +} + +impl GPUCommandBufferMethods for GPUCommandBuffer { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/gpucommandencoder.rs b/components/script/dom/gpucommandencoder.rs new file mode 100644 index 00000000000..60eabbf1973 --- /dev/null +++ b/components/script/dom/gpucommandencoder.rs @@ -0,0 +1,448 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUBufferBinding::GPUSize64; +use crate::dom::bindings::codegen::Bindings::GPUCommandEncoderBinding::{ + GPUBufferCopyView, GPUCommandBufferDescriptor, GPUCommandEncoderMethods, + GPUComputePassDescriptor, GPUOrigin3D, GPURenderPassDescriptor, GPUStencilLoadValue, + GPUStoreOp, GPUTextureCopyView, GPUTextureDataLayout, +}; +use crate::dom::bindings::codegen::Bindings::GPUTextureBinding::GPUExtent3D; +use crate::dom::bindings::codegen::UnionTypes::{ + GPULoadOpOrDoubleSequenceOrGPUColorDict as GPUColorLoad, GPULoadOpOrFloat, +}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpucommandbuffer::GPUCommandBuffer; +use crate::dom::gpucomputepassencoder::GPUComputePassEncoder; +use crate::dom::gpudevice::{convert_texture_size_to_dict, convert_texture_size_to_wgt, GPUDevice}; +use crate::dom::gpurenderpassencoder::GPURenderPassEncoder; +use dom_struct::dom_struct; +use std::borrow::Cow; +use std::cell::Cell; +use std::collections::HashSet; +use webgpu::wgpu::command as wgpu_com; +use webgpu::{self, wgt, WebGPU, WebGPURequest}; + +// https://gpuweb.github.io/gpuweb/#enumdef-encoder-state +#[derive(MallocSizeOf, PartialEq)] +pub enum GPUCommandEncoderState { + Open, + EncodingRenderPass, + EncodingComputePass, + Closed, +} + +#[dom_struct] +pub struct GPUCommandEncoder { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + channel: WebGPU, + label: DomRefCell<Option<USVString>>, + encoder: webgpu::WebGPUCommandEncoder, + buffers: DomRefCell<HashSet<DomRoot<GPUBuffer>>>, + state: DomRefCell<GPUCommandEncoderState>, + device: Dom<GPUDevice>, + valid: Cell<bool>, +} + +impl GPUCommandEncoder { + pub fn new_inherited( + channel: WebGPU, + device: &GPUDevice, + encoder: webgpu::WebGPUCommandEncoder, + label: Option<USVString>, + ) -> Self { + Self { + channel, + reflector_: Reflector::new(), + label: DomRefCell::new(label), + device: Dom::from_ref(device), + encoder, + buffers: DomRefCell::new(HashSet::new()), + state: DomRefCell::new(GPUCommandEncoderState::Open), + valid: Cell::new(true), + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + device: &GPUDevice, + encoder: webgpu::WebGPUCommandEncoder, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUCommandEncoder::new_inherited( + channel, device, encoder, label, + )), + global, + ) + } +} + +impl GPUCommandEncoder { + pub fn id(&self) -> webgpu::WebGPUCommandEncoder { + self.encoder + } + + pub fn set_state(&self, set: GPUCommandEncoderState, expect: GPUCommandEncoderState) { + if *self.state.borrow() == expect { + *self.state.borrow_mut() = set; + } else { + self.valid.set(false); + *self.state.borrow_mut() = GPUCommandEncoderState::Closed; + } + } +} + +impl GPUCommandEncoderMethods for GPUCommandEncoder { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-begincomputepass + fn BeginComputePass( + &self, + descriptor: &GPUComputePassDescriptor, + ) -> DomRoot<GPUComputePassEncoder> { + self.set_state( + GPUCommandEncoderState::EncodingComputePass, + GPUCommandEncoderState::Open, + ); + + let compute_pass = if !self.valid.get() { + None + } else { + Some(wgpu_com::ComputePass::new(self.encoder.0)) + }; + + GPUComputePassEncoder::new( + &self.global(), + self.channel.clone(), + &self, + compute_pass, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-beginrenderpass + fn BeginRenderPass( + &self, + descriptor: &GPURenderPassDescriptor, + ) -> DomRoot<GPURenderPassEncoder> { + self.set_state( + GPUCommandEncoderState::EncodingRenderPass, + GPUCommandEncoderState::Open, + ); + + let render_pass = if !self.valid.get() { + None + } else { + let depth_stencil = descriptor.depthStencilAttachment.as_ref().map(|depth| { + let (depth_load_op, clear_depth) = match depth.depthLoadValue { + GPULoadOpOrFloat::GPULoadOp(_) => (wgpu_com::LoadOp::Load, 0.0f32), + GPULoadOpOrFloat::Float(f) => (wgpu_com::LoadOp::Clear, *f), + }; + let (stencil_load_op, clear_stencil) = match depth.stencilLoadValue { + GPUStencilLoadValue::GPULoadOp(_) => (wgpu_com::LoadOp::Load, 0u32), + GPUStencilLoadValue::RangeEnforcedUnsignedLong(l) => { + (wgpu_com::LoadOp::Clear, l) + }, + }; + let depth_channel = wgpu_com::PassChannel { + load_op: depth_load_op, + store_op: match depth.depthStoreOp { + GPUStoreOp::Store => wgpu_com::StoreOp::Store, + GPUStoreOp::Clear => wgpu_com::StoreOp::Clear, + }, + clear_value: clear_depth, + read_only: depth.depthReadOnly, + }; + let stencil_channel = wgpu_com::PassChannel { + load_op: stencil_load_op, + store_op: match depth.stencilStoreOp { + GPUStoreOp::Store => wgpu_com::StoreOp::Store, + GPUStoreOp::Clear => wgpu_com::StoreOp::Clear, + }, + clear_value: clear_stencil, + read_only: depth.stencilReadOnly, + }; + wgpu_com::DepthStencilAttachmentDescriptor { + attachment: depth.attachment.id().0, + depth: depth_channel, + stencil: stencil_channel, + } + }); + + let desc = wgpu_com::RenderPassDescriptor { + color_attachments: Cow::Owned( + descriptor + .colorAttachments + .iter() + .map(|color| { + let (load_op, clear_value) = match color.loadValue { + GPUColorLoad::GPULoadOp(_) => { + (wgpu_com::LoadOp::Load, wgt::Color::TRANSPARENT) + }, + GPUColorLoad::DoubleSequence(ref s) => { + let mut w = s.clone(); + if w.len() < 3 { + w.resize(3, Finite::wrap(0.0f64)); + } + w.resize(4, Finite::wrap(1.0f64)); + ( + wgpu_com::LoadOp::Clear, + wgt::Color { + r: *w[0], + g: *w[1], + b: *w[2], + a: *w[3], + }, + ) + }, + GPUColorLoad::GPUColorDict(ref d) => ( + wgpu_com::LoadOp::Clear, + wgt::Color { + r: *d.r, + g: *d.g, + b: *d.b, + a: *d.a, + }, + ), + }; + let channel = wgpu_com::PassChannel { + load_op, + store_op: match color.storeOp { + GPUStoreOp::Store => wgpu_com::StoreOp::Store, + GPUStoreOp::Clear => wgpu_com::StoreOp::Clear, + }, + clear_value, + read_only: false, + }; + wgpu_com::ColorAttachmentDescriptor { + attachment: color.attachment.id().0, + resolve_target: color.resolveTarget.as_ref().map(|t| t.id().0), + channel, + } + }) + .collect::<Vec<_>>(), + ), + depth_stencil_attachment: depth_stencil.as_ref(), + }; + Some(wgpu_com::RenderPass::new(self.encoder.0, desc)) + }; + + GPURenderPassEncoder::new( + &self.global(), + self.channel.clone(), + render_pass, + &self, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertobuffer + fn CopyBufferToBuffer( + &self, + source: &GPUBuffer, + source_offset: GPUSize64, + destination: &GPUBuffer, + destination_offset: GPUSize64, + size: GPUSize64, + ) { + if !(*self.state.borrow() == GPUCommandEncoderState::Open) { + self.valid.set(false); + return; + } + + self.buffers.borrow_mut().insert(DomRoot::from_ref(source)); + self.buffers + .borrow_mut() + .insert(DomRoot::from_ref(destination)); + self.channel + .0 + .send(( + None, + WebGPURequest::CopyBufferToBuffer { + command_encoder_id: self.encoder.0, + source_id: source.id().0, + source_offset, + destination_id: destination.id().0, + destination_offset, + size, + }, + )) + .expect("Failed to send CopyBufferToBuffer"); + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertotexture + fn CopyBufferToTexture( + &self, + source: &GPUBufferCopyView, + destination: &GPUTextureCopyView, + copy_size: GPUExtent3D, + ) { + if !(*self.state.borrow() == GPUCommandEncoderState::Open) { + self.valid.set(false); + return; + } + + self.buffers + .borrow_mut() + .insert(DomRoot::from_ref(&*source.buffer)); + + self.channel + .0 + .send(( + None, + WebGPURequest::CopyBufferToTexture { + command_encoder_id: self.encoder.0, + source: convert_buffer_cv(source), + destination: convert_texture_cv(destination), + copy_size: convert_texture_size_to_wgt(&convert_texture_size_to_dict( + ©_size, + )), + }, + )) + .expect("Failed to send CopyBufferToTexture"); + } + + /// https://gpuweb.github.io/gpuweb/#GPUCommandEncoder-copyTextureToBuffer + fn CopyTextureToBuffer( + &self, + source: &GPUTextureCopyView, + destination: &GPUBufferCopyView, + copy_size: GPUExtent3D, + ) { + if !(*self.state.borrow() == GPUCommandEncoderState::Open) { + self.valid.set(false); + return; + } + + self.buffers + .borrow_mut() + .insert(DomRoot::from_ref(&*destination.buffer)); + + self.channel + .0 + .send(( + None, + WebGPURequest::CopyTextureToBuffer { + command_encoder_id: self.encoder.0, + source: convert_texture_cv(source), + destination: convert_buffer_cv(destination), + copy_size: convert_texture_size_to_wgt(&convert_texture_size_to_dict( + ©_size, + )), + }, + )) + .expect("Failed to send CopyTextureToBuffer"); + } + + /// https://gpuweb.github.io/gpuweb/#GPUCommandEncoder-copyTextureToTexture + fn CopyTextureToTexture( + &self, + source: &GPUTextureCopyView, + destination: &GPUTextureCopyView, + copy_size: GPUExtent3D, + ) { + if !(*self.state.borrow() == GPUCommandEncoderState::Open) { + self.valid.set(false); + return; + } + + self.channel + .0 + .send(( + None, + WebGPURequest::CopyTextureToTexture { + command_encoder_id: self.encoder.0, + source: convert_texture_cv(source), + destination: convert_texture_cv(destination), + copy_size: convert_texture_size_to_wgt(&convert_texture_size_to_dict( + ©_size, + )), + }, + )) + .expect("Failed to send CopyTextureToTexture"); + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-finish + fn Finish(&self, descriptor: &GPUCommandBufferDescriptor) -> DomRoot<GPUCommandBuffer> { + self.channel + .0 + .send(( + self.device.use_current_scope(), + WebGPURequest::CommandEncoderFinish { + command_encoder_id: self.encoder.0, + device_id: self.device.id().0, + is_error: !self.valid.get(), + // TODO(zakorgy): We should use `_descriptor` here after it's not empty + // and the underlying wgpu-core struct is serializable + }, + )) + .expect("Failed to send Finish"); + + *self.state.borrow_mut() = GPUCommandEncoderState::Closed; + let buffer = webgpu::WebGPUCommandBuffer(self.encoder.0); + GPUCommandBuffer::new( + &self.global(), + self.channel.clone(), + buffer, + self.buffers.borrow_mut().drain().collect(), + descriptor.parent.label.as_ref().cloned(), + ) + } +} + +fn convert_buffer_cv(buffer_cv: &GPUBufferCopyView) -> wgpu_com::BufferCopyView { + wgpu_com::BufferCopyView { + buffer: buffer_cv.buffer.id().0, + layout: convert_texture_data_layout(&buffer_cv.parent), + } +} + +pub fn convert_texture_cv(texture_cv: &GPUTextureCopyView) -> wgpu_com::TextureCopyView { + wgpu_com::TextureCopyView { + texture: texture_cv.texture.id().0, + mip_level: texture_cv.mipLevel, + origin: match texture_cv.origin { + GPUOrigin3D::RangeEnforcedUnsignedLongSequence(ref v) => { + let mut w = v.clone(); + w.resize(3, 0); + wgt::Origin3d { + x: w[0], + y: w[1], + z: w[2], + } + }, + GPUOrigin3D::GPUOrigin3DDict(ref d) => wgt::Origin3d { + x: d.x, + y: d.y, + z: d.z, + }, + }, + } +} + +pub fn convert_texture_data_layout(data_layout: &GPUTextureDataLayout) -> wgt::TextureDataLayout { + wgt::TextureDataLayout { + offset: data_layout.offset as wgt::BufferAddress, + bytes_per_row: data_layout.bytesPerRow, + rows_per_image: data_layout.rowsPerImage, + } +} diff --git a/components/script/dom/gpucomputepassencoder.rs b/components/script/dom/gpucomputepassencoder.rs new file mode 100644 index 00000000000..f0f4325ae6d --- /dev/null +++ b/components/script/dom/gpucomputepassencoder.rs @@ -0,0 +1,138 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUComputePassEncoderBinding::GPUComputePassEncoderMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgroup::GPUBindGroup; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpucommandencoder::{GPUCommandEncoder, GPUCommandEncoderState}; +use crate::dom::gpucomputepipeline::GPUComputePipeline; +use dom_struct::dom_struct; +use webgpu::{ + wgpu::command::{compute_ffi as wgpu_comp, ComputePass}, + WebGPU, WebGPURequest, +}; + +#[dom_struct] +pub struct GPUComputePassEncoder { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + channel: WebGPU, + label: DomRefCell<Option<USVString>>, + #[ignore_malloc_size_of = "defined in wgpu-core"] + compute_pass: DomRefCell<Option<ComputePass>>, + command_encoder: Dom<GPUCommandEncoder>, +} + +impl GPUComputePassEncoder { + fn new_inherited( + channel: WebGPU, + parent: &GPUCommandEncoder, + compute_pass: Option<ComputePass>, + label: Option<USVString>, + ) -> Self { + Self { + channel, + reflector_: Reflector::new(), + label: DomRefCell::new(label), + compute_pass: DomRefCell::new(compute_pass), + command_encoder: Dom::from_ref(parent), + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + parent: &GPUCommandEncoder, + compute_pass: Option<ComputePass>, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUComputePassEncoder::new_inherited( + channel, + parent, + compute_pass, + label, + )), + global, + ) + } +} + +impl GPUComputePassEncoderMethods for GPUComputePassEncoder { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpucomputepassencoder-dispatch + fn Dispatch(&self, x: u32, y: u32, z: u32) { + if let Some(compute_pass) = self.compute_pass.borrow_mut().as_mut() { + wgpu_comp::wgpu_compute_pass_dispatch(compute_pass, x, y, z); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpucomputepassencoder-dispatchindirect + fn DispatchIndirect(&self, indirect_buffer: &GPUBuffer, indirect_offset: u64) { + if let Some(compute_pass) = self.compute_pass.borrow_mut().as_mut() { + wgpu_comp::wgpu_compute_pass_dispatch_indirect( + compute_pass, + indirect_buffer.id().0, + indirect_offset, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-endpass + fn EndPass(&self) { + let compute_pass = self.compute_pass.borrow_mut().take(); + self.channel + .0 + .send(( + None, + WebGPURequest::RunComputePass { + command_encoder_id: self.command_encoder.id().0, + compute_pass, + }, + )) + .expect("Failed to send RunComputePass"); + + self.command_encoder.set_state( + GPUCommandEncoderState::Open, + GPUCommandEncoderState::EncodingComputePass, + ); + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuprogrammablepassencoder-setbindgroup + #[allow(unsafe_code)] + fn SetBindGroup(&self, index: u32, bind_group: &GPUBindGroup, dynamic_offsets: Vec<u32>) { + if let Some(compute_pass) = self.compute_pass.borrow_mut().as_mut() { + unsafe { + wgpu_comp::wgpu_compute_pass_set_bind_group( + compute_pass, + index, + bind_group.id().0, + dynamic_offsets.as_ptr(), + dynamic_offsets.len(), + ) + }; + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpucomputepassencoder-setpipeline + fn SetPipeline(&self, pipeline: &GPUComputePipeline) { + if let Some(compute_pass) = self.compute_pass.borrow_mut().as_mut() { + wgpu_comp::wgpu_compute_pass_set_pipeline(compute_pass, pipeline.id().0); + } + } +} diff --git a/components/script/dom/gpucomputepipeline.rs b/components/script/dom/gpucomputepipeline.rs new file mode 100644 index 00000000000..604af2373e0 --- /dev/null +++ b/components/script/dom/gpucomputepipeline.rs @@ -0,0 +1,90 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUComputePipelineBinding::GPUComputePipelineMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; +use crate::dom::gpudevice::GPUDevice; +use dom_struct::dom_struct; +use std::string::String; +use webgpu::{WebGPUBindGroupLayout, WebGPUComputePipeline}; + +#[dom_struct] +pub struct GPUComputePipeline { + reflector_: Reflector, + label: DomRefCell<Option<USVString>>, + compute_pipeline: WebGPUComputePipeline, + bind_group_layouts: Vec<WebGPUBindGroupLayout>, + device: Dom<GPUDevice>, +} + +impl GPUComputePipeline { + fn new_inherited( + compute_pipeline: WebGPUComputePipeline, + label: Option<USVString>, + bgls: Vec<WebGPUBindGroupLayout>, + device: &GPUDevice, + ) -> Self { + Self { + reflector_: Reflector::new(), + label: DomRefCell::new(label), + compute_pipeline, + bind_group_layouts: bgls, + device: Dom::from_ref(device), + } + } + + pub fn new( + global: &GlobalScope, + compute_pipeline: WebGPUComputePipeline, + label: Option<USVString>, + bgls: Vec<WebGPUBindGroupLayout>, + device: &GPUDevice, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUComputePipeline::new_inherited( + compute_pipeline, + label, + bgls, + device, + )), + global, + ) + } +} + +impl GPUComputePipeline { + pub fn id(&self) -> &WebGPUComputePipeline { + &self.compute_pipeline + } +} + +impl GPUComputePipelineMethods for GPUComputePipeline { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpupipelinebase-getbindgrouplayout + fn GetBindGroupLayout(&self, index: u32) -> Fallible<DomRoot<GPUBindGroupLayout>> { + if index > self.bind_group_layouts.len() as u32 { + return Err(Error::Range(String::from("Index out of bounds"))); + } + Ok(GPUBindGroupLayout::new( + &self.global(), + self.bind_group_layouts[index as usize], + None, + )) + } +} diff --git a/components/script/dom/gpudevice.rs b/components/script/dom/gpudevice.rs new file mode 100644 index 00000000000..fb6fad7efb4 --- /dev/null +++ b/components/script/dom/gpudevice.rs @@ -0,0 +1,1372 @@ +/* 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/. */ + +#![allow(unsafe_code)] + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit; +use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; +use crate::dom::bindings::codegen::Bindings::GPUAdapterBinding::GPULimits; +use crate::dom::bindings::codegen::Bindings::GPUBindGroupBinding::{ + GPUBindGroupDescriptor, GPUBindingResource, +}; +use crate::dom::bindings::codegen::Bindings::GPUBindGroupLayoutBinding::{ + GPUBindGroupLayoutDescriptor, GPUBindingType, +}; +use crate::dom::bindings::codegen::Bindings::GPUBufferBinding::GPUBufferDescriptor; +use crate::dom::bindings::codegen::Bindings::GPUComputePipelineBinding::GPUComputePipelineDescriptor; +use crate::dom::bindings::codegen::Bindings::GPUDeviceBinding::{ + GPUCommandEncoderDescriptor, GPUDeviceMethods, +}; +use crate::dom::bindings::codegen::Bindings::GPUObjectBaseBinding::GPUObjectDescriptorBase; +use crate::dom::bindings::codegen::Bindings::GPUPipelineLayoutBinding::GPUPipelineLayoutDescriptor; +use crate::dom::bindings::codegen::Bindings::GPURenderBundleEncoderBinding::GPURenderBundleEncoderDescriptor; +use crate::dom::bindings::codegen::Bindings::GPURenderPipelineBinding::{ + GPUBlendDescriptor, GPUBlendFactor, GPUBlendOperation, GPUCullMode, GPUFrontFace, + GPUIndexFormat, GPUInputStepMode, GPUPrimitiveTopology, GPURenderPipelineDescriptor, + GPUStencilOperation, GPUVertexFormat, +}; +use crate::dom::bindings::codegen::Bindings::GPUSamplerBinding::{ + GPUAddressMode, GPUCompareFunction, GPUFilterMode, GPUSamplerDescriptor, +}; +use crate::dom::bindings::codegen::Bindings::GPUShaderModuleBinding::GPUShaderModuleDescriptor; +use crate::dom::bindings::codegen::Bindings::GPUTextureBinding::{ + GPUExtent3D, GPUExtent3DDict, GPUTextureComponentType, GPUTextureDescriptor, + GPUTextureDimension, GPUTextureFormat, +}; +use crate::dom::bindings::codegen::Bindings::GPUTextureViewBinding::GPUTextureViewDimension; +use crate::dom::bindings::codegen::Bindings::GPUUncapturedErrorEventBinding::GPUUncapturedErrorEventInit; +use crate::dom::bindings::codegen::Bindings::GPUValidationErrorBinding::{ + GPUError, GPUErrorFilter, +}; +use crate::dom::bindings::codegen::UnionTypes::Uint32ArrayOrString; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpuadapter::GPUAdapter; +use crate::dom::gpubindgroup::GPUBindGroup; +use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; +use crate::dom::gpubuffer::{GPUBuffer, GPUBufferMapInfo, GPUBufferState}; +use crate::dom::gpucommandencoder::GPUCommandEncoder; +use crate::dom::gpucomputepipeline::GPUComputePipeline; +use crate::dom::gpuoutofmemoryerror::GPUOutOfMemoryError; +use crate::dom::gpupipelinelayout::GPUPipelineLayout; +use crate::dom::gpuqueue::GPUQueue; +use crate::dom::gpurenderbundleencoder::GPURenderBundleEncoder; +use crate::dom::gpurenderpipeline::GPURenderPipeline; +use crate::dom::gpusampler::GPUSampler; +use crate::dom::gpushadermodule::GPUShaderModule; +use crate::dom::gputexture::GPUTexture; +use crate::dom::gpuuncapturederrorevent::GPUUncapturedErrorEvent; +use crate::dom::gpuvalidationerror::GPUValidationError; +use crate::dom::promise::Promise; +use crate::realms::InRealm; +use crate::script_runtime::JSContext as SafeJSContext; +use dom_struct::dom_struct; +use js::jsapi::{Heap, JSObject}; +use std::borrow::Cow; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::ptr::{self, NonNull}; +use std::rc::Rc; +use webgpu::wgpu::{ + binding_model as wgpu_bind, command as wgpu_com, + id::{BindGroupLayoutId, PipelineLayoutId}, + pipeline as wgpu_pipe, resource as wgpu_res, +}; +use webgpu::{self, identity::WebGPUOpResult, wgt, ErrorScopeId, WebGPU, WebGPURequest}; + +#[derive(JSTraceable, MallocSizeOf)] +struct ErrorScopeInfo { + op_count: u64, + #[ignore_malloc_size_of = "Because it is non-owning"] + error: Option<GPUError>, + #[ignore_malloc_size_of = "promises are hard"] + promise: Option<Rc<Promise>>, +} + +#[derive(JSTraceable, MallocSizeOf)] +struct ErrorScopeMetadata { + id: ErrorScopeId, + filter: GPUErrorFilter, + popped: Cell<bool>, +} + +#[derive(JSTraceable, MallocSizeOf)] +struct ScopeContext { + error_scopes: HashMap<ErrorScopeId, ErrorScopeInfo>, + scope_stack: Vec<ErrorScopeMetadata>, + next_scope_id: ErrorScopeId, +} + +#[dom_struct] +pub struct GPUDevice { + eventtarget: EventTarget, + #[ignore_malloc_size_of = "channels are hard"] + channel: WebGPU, + adapter: Dom<GPUAdapter>, + #[ignore_malloc_size_of = "mozjs"] + extensions: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "Because it is non-owning"] + limits: GPULimits, + label: DomRefCell<Option<USVString>>, + device: webgpu::WebGPUDevice, + default_queue: Dom<GPUQueue>, + scope_context: DomRefCell<ScopeContext>, + #[ignore_malloc_size_of = "promises are hard"] + lost_promise: DomRefCell<Option<Rc<Promise>>>, +} + +impl GPUDevice { + fn new_inherited( + channel: WebGPU, + adapter: &GPUAdapter, + extensions: Heap<*mut JSObject>, + limits: GPULimits, + device: webgpu::WebGPUDevice, + queue: &GPUQueue, + label: Option<String>, + ) -> Self { + Self { + eventtarget: EventTarget::new_inherited(), + channel, + adapter: Dom::from_ref(adapter), + extensions, + limits, + label: DomRefCell::new(label.map(|l| USVString::from(l))), + device, + default_queue: Dom::from_ref(queue), + scope_context: DomRefCell::new(ScopeContext { + error_scopes: HashMap::new(), + scope_stack: Vec::new(), + next_scope_id: ErrorScopeId::new(1).unwrap(), + }), + lost_promise: DomRefCell::new(None), + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + adapter: &GPUAdapter, + extensions: Heap<*mut JSObject>, + limits: GPULimits, + device: webgpu::WebGPUDevice, + queue: webgpu::WebGPUQueue, + label: Option<String>, + ) -> DomRoot<Self> { + let queue = GPUQueue::new(global, channel.clone(), queue); + let device = reflect_dom_object( + Box::new(GPUDevice::new_inherited( + channel, adapter, extensions, limits, device, &queue, label, + )), + global, + ); + queue.set_device(&*device); + device + } +} + +impl GPUDevice { + pub fn id(&self) -> webgpu::WebGPUDevice { + self.device + } + + pub fn limits(&self) -> &GPULimits { + &self.limits + } + + pub fn handle_server_msg(&self, scope: Option<ErrorScopeId>, result: WebGPUOpResult) { + let result = match result { + WebGPUOpResult::Success => Ok(()), + WebGPUOpResult::ValidationError(m) => { + let val_err = GPUValidationError::new(&self.global(), DOMString::from_string(m)); + Err(( + GPUError::GPUValidationError(val_err), + GPUErrorFilter::Validation, + )) + }, + WebGPUOpResult::OutOfMemoryError => { + let oom_err = GPUOutOfMemoryError::new(&self.global()); + Err(( + GPUError::GPUOutOfMemoryError(oom_err), + GPUErrorFilter::Out_of_memory, + )) + }, + }; + + if let Some(s_id) = scope { + if let Err((err, filter)) = result { + let scop = self + .scope_context + .borrow() + .scope_stack + .iter() + .rev() + .find(|meta| meta.id <= s_id && meta.filter == filter) + .map(|meta| meta.id); + if let Some(s) = scop { + self.handle_error(s, err); + } else { + self.fire_uncaptured_error(err); + } + } + self.try_remove_scope(s_id); + } else { + if let Err((err, _)) = result { + self.fire_uncaptured_error(err); + } + } + } + + fn handle_error(&self, scope: ErrorScopeId, error: GPUError) { + let mut context = self.scope_context.borrow_mut(); + if let Some(mut err_scope) = context.error_scopes.get_mut(&scope) { + if err_scope.error.is_none() { + err_scope.error = Some(error); + } + } else { + warn!("Could not find ErrorScope with Id({})", scope); + } + } + + fn try_remove_scope(&self, scope: ErrorScopeId) { + let mut context = self.scope_context.borrow_mut(); + let remove = if let Some(mut err_scope) = context.error_scopes.get_mut(&scope) { + err_scope.op_count -= 1; + if let Some(ref promise) = err_scope.promise { + if !promise.is_fulfilled() { + if let Some(ref e) = err_scope.error { + promise.resolve_native(e); + } else if err_scope.op_count == 0 { + promise.resolve_native(&None::<GPUError>); + } + } + } + err_scope.op_count == 0 && err_scope.promise.is_some() + } else { + warn!("Could not find ErrorScope with Id({})", scope); + false + }; + if remove { + let _ = context.error_scopes.remove(&scope); + context.scope_stack.retain(|meta| meta.id != scope); + } + } + + fn fire_uncaptured_error(&self, err: GPUError) { + let ev = GPUUncapturedErrorEvent::new( + &self.global(), + DOMString::from("uncapturederror"), + &GPUUncapturedErrorEventInit { + error: err, + parent: EventInit::empty(), + }, + ); + let _ = self.eventtarget.DispatchEvent(ev.event()); + } + + pub fn use_current_scope(&self) -> Option<ErrorScopeId> { + let mut context = self.scope_context.borrow_mut(); + let scope_id = context + .scope_stack + .iter() + .rev() + .find(|meta| !meta.popped.get()) + .map(|meta| meta.id); + scope_id.and_then(|s_id| { + context.error_scopes.get_mut(&s_id).map(|mut scope| { + scope.op_count += 1; + s_id + }) + }) + } + + fn get_pipeline_layout_data( + &self, + layout: &Option<DomRoot<GPUPipelineLayout>>, + ) -> ( + Option<PipelineLayoutId>, + Option<(PipelineLayoutId, Vec<BindGroupLayoutId>)>, + Vec<webgpu::WebGPUBindGroupLayout>, + ) { + if let Some(ref layout) = layout { + (Some(layout.id().0), None, layout.bind_group_layouts()) + } else { + let layout_id = self + .global() + .wgpu_id_hub() + .lock() + .create_pipeline_layout_id(self.device.0.backend()); + let max_bind_grps = self.limits.maxBindGroups; + let mut bgls = Vec::with_capacity(max_bind_grps as usize); + let mut bgl_ids = Vec::with_capacity(max_bind_grps as usize); + for _ in 0..max_bind_grps { + let bgl = self + .global() + .wgpu_id_hub() + .lock() + .create_bind_group_layout_id(self.device.0.backend()); + bgls.push(webgpu::WebGPUBindGroupLayout(bgl)); + bgl_ids.push(bgl); + } + (None, Some((layout_id, bgl_ids)), bgls) + } + } +} + +impl GPUDeviceMethods for GPUDevice { + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-adapter + fn Adapter(&self) -> DomRoot<GPUAdapter> { + DomRoot::from_ref(&self.adapter) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-extensions + fn Extensions(&self, _cx: SafeJSContext) -> NonNull<JSObject> { + NonNull::new(self.extensions.get()).unwrap() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-limits + fn Limits(&self, cx: SafeJSContext) -> NonNull<JSObject> { + rooted!(in (*cx) let mut limits = ptr::null_mut::<JSObject>()); + unsafe { + self.limits.to_jsobject(*cx, limits.handle_mut()); + } + NonNull::new(limits.get()).unwrap() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-defaultqueue + fn DefaultQueue(&self) -> DomRoot<GPUQueue> { + DomRoot::from_ref(&self.default_queue) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-lost + fn Lost(&self, comp: InRealm) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&self.global(), comp); + *self.lost_promise.borrow_mut() = Some(promise.clone()); + promise + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbuffer + fn CreateBuffer(&self, descriptor: &GPUBufferDescriptor) -> DomRoot<GPUBuffer> { + let desc = + wgt::BufferUsage::from_bits(descriptor.usage).map(|usg| wgpu_res::BufferDescriptor { + label: convert_label(&descriptor.parent), + size: descriptor.size as wgt::BufferAddress, + usage: usg, + mapped_at_creation: descriptor.mappedAtCreation, + }); + let id = self + .global() + .wgpu_id_hub() + .lock() + .create_buffer_id(self.device.0.backend()); + + let scope_id = self.use_current_scope(); + if desc.is_none() { + self.handle_server_msg( + scope_id, + WebGPUOpResult::ValidationError(String::from("Invalid GPUBufferUsage")), + ); + } + + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateBuffer { + device_id: self.device.0, + buffer_id: id, + descriptor: desc, + }, + )) + .expect("Failed to create WebGPU buffer"); + + let buffer = webgpu::WebGPUBuffer(id); + let map_info; + let state; + if descriptor.mappedAtCreation { + let buf_data = vec![0u8; descriptor.size as usize]; + map_info = DomRefCell::new(Some(GPUBufferMapInfo { + mapping: Rc::new(RefCell::new(buf_data)), + mapping_range: 0..descriptor.size, + mapped_ranges: Vec::new(), + js_buffers: Vec::new(), + map_mode: None, + })); + state = GPUBufferState::MappedAtCreation; + } else { + map_info = DomRefCell::new(None); + state = GPUBufferState::Unmapped; + } + + GPUBuffer::new( + &self.global(), + self.channel.clone(), + buffer, + &self, + state, + descriptor.size, + map_info, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#GPUDevice-createBindGroupLayout + #[allow(non_snake_case)] + fn CreateBindGroupLayout( + &self, + descriptor: &GPUBindGroupLayoutDescriptor, + ) -> DomRoot<GPUBindGroupLayout> { + let mut valid = true; + let entries = descriptor + .entries + .iter() + .map(|bind| { + let visibility = match wgt::ShaderStage::from_bits(bind.visibility) { + Some(visibility) => visibility, + None => { + valid = false; + wgt::ShaderStage::empty() + }, + }; + let ty = match bind.type_ { + GPUBindingType::Uniform_buffer => wgt::BindingType::UniformBuffer { + dynamic: bind.hasDynamicOffset.unwrap_or(false), + min_binding_size: bind.minBufferBindingSize.and_then(wgt::BufferSize::new), + }, + GPUBindingType::Storage_buffer => wgt::BindingType::StorageBuffer { + dynamic: bind.hasDynamicOffset.unwrap_or(false), + min_binding_size: bind.minBufferBindingSize.and_then(wgt::BufferSize::new), + readonly: false, + }, + GPUBindingType::Readonly_storage_buffer => wgt::BindingType::StorageBuffer { + dynamic: bind.hasDynamicOffset.unwrap_or(false), + min_binding_size: bind.minBufferBindingSize.and_then(wgt::BufferSize::new), + readonly: true, + }, + GPUBindingType::Sampled_texture => wgt::BindingType::SampledTexture { + dimension: bind + .viewDimension + .map_or(wgt::TextureViewDimension::D2, |v| { + convert_texture_view_dimension(v) + }), + component_type: convert_texture_component_type(bind.textureComponentType), + multisampled: false, + }, + GPUBindingType::Multisampled_texture => wgt::BindingType::SampledTexture { + dimension: bind + .viewDimension + .map_or(wgt::TextureViewDimension::D2, |v| { + convert_texture_view_dimension(v) + }), + component_type: convert_texture_component_type(bind.textureComponentType), + multisampled: true, + }, + GPUBindingType::Readonly_storage_texture => wgt::BindingType::StorageTexture { + dimension: bind + .viewDimension + .map_or(wgt::TextureViewDimension::D2, |v| { + convert_texture_view_dimension(v) + }), + format: bind + .storageTextureFormat + .map_or(wgt::TextureFormat::Bgra8UnormSrgb, |f| { + convert_texture_format(f) + }), + readonly: true, + }, + GPUBindingType::Writeonly_storage_texture => wgt::BindingType::StorageTexture { + dimension: bind + .viewDimension + .map_or(wgt::TextureViewDimension::D2, |v| { + convert_texture_view_dimension(v) + }), + format: bind + .storageTextureFormat + .map_or(wgt::TextureFormat::Bgra8UnormSrgb, |f| { + convert_texture_format(f) + }), + readonly: true, + }, + GPUBindingType::Sampler => wgt::BindingType::Sampler { comparison: false }, + GPUBindingType::Comparison_sampler => { + wgt::BindingType::Sampler { comparison: true } + }, + }; + + wgt::BindGroupLayoutEntry { + binding: bind.binding, + visibility: visibility, + ty, + count: None, + } + }) + .collect::<Vec<_>>(); + + let scope_id = self.use_current_scope(); + + let desc = if valid { + Some(wgpu_bind::BindGroupLayoutDescriptor { + label: convert_label(&descriptor.parent), + entries: Cow::Owned(entries), + }) + } else { + self.handle_server_msg( + scope_id, + WebGPUOpResult::ValidationError(String::from("Invalid GPUShaderStage")), + ); + None + }; + + let bind_group_layout_id = self + .global() + .wgpu_id_hub() + .lock() + .create_bind_group_layout_id(self.device.0.backend()); + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateBindGroupLayout { + device_id: self.device.0, + bind_group_layout_id, + descriptor: desc, + }, + )) + .expect("Failed to create WebGPU BindGroupLayout"); + + let bgl = webgpu::WebGPUBindGroupLayout(bind_group_layout_id); + + GPUBindGroupLayout::new( + &self.global(), + bgl, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createpipelinelayout + fn CreatePipelineLayout( + &self, + descriptor: &GPUPipelineLayoutDescriptor, + ) -> DomRoot<GPUPipelineLayout> { + let desc = wgpu_bind::PipelineLayoutDescriptor { + label: convert_label(&descriptor.parent), + bind_group_layouts: Cow::Owned( + descriptor + .bindGroupLayouts + .iter() + .map(|each| each.id().0) + .collect::<Vec<_>>(), + ), + push_constant_ranges: Cow::Owned(vec![]), + }; + + let scope_id = self.use_current_scope(); + + let pipeline_layout_id = self + .global() + .wgpu_id_hub() + .lock() + .create_pipeline_layout_id(self.device.0.backend()); + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreatePipelineLayout { + device_id: self.device.0, + pipeline_layout_id, + descriptor: desc, + }, + )) + .expect("Failed to create WebGPU PipelineLayout"); + + let bgls = descriptor + .bindGroupLayouts + .iter() + .map(|each| each.id()) + .collect::<Vec<_>>(); + let pipeline_layout = webgpu::WebGPUPipelineLayout(pipeline_layout_id); + GPUPipelineLayout::new( + &self.global(), + pipeline_layout, + descriptor.parent.label.as_ref().cloned(), + bgls, + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbindgroup + fn CreateBindGroup(&self, descriptor: &GPUBindGroupDescriptor) -> DomRoot<GPUBindGroup> { + let entries = descriptor + .entries + .iter() + .map(|bind| wgpu_bind::BindGroupEntry { + binding: bind.binding, + resource: match bind.resource { + GPUBindingResource::GPUSampler(ref s) => { + wgpu_bind::BindingResource::Sampler(s.id().0) + }, + GPUBindingResource::GPUTextureView(ref t) => { + wgpu_bind::BindingResource::TextureView(t.id().0) + }, + GPUBindingResource::GPUBufferBindings(ref b) => { + wgpu_bind::BindingResource::Buffer(wgpu_bind::BufferBinding { + buffer_id: b.buffer.id().0, + offset: b.offset, + size: b.size.and_then(wgt::BufferSize::new), + }) + }, + }, + }) + .collect::<Vec<_>>(); + + let desc = wgpu_bind::BindGroupDescriptor { + label: convert_label(&descriptor.parent), + layout: descriptor.layout.id().0, + entries: Cow::Owned(entries), + }; + + let scope_id = self.use_current_scope(); + + let bind_group_id = self + .global() + .wgpu_id_hub() + .lock() + .create_bind_group_id(self.device.0.backend()); + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateBindGroup { + device_id: self.device.0, + bind_group_id, + descriptor: desc, + }, + )) + .expect("Failed to create WebGPU BindGroup"); + + let bind_group = webgpu::WebGPUBindGroup(bind_group_id); + + GPUBindGroup::new( + &self.global(), + bind_group, + self.device, + &*descriptor.layout, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createshadermodule + fn CreateShaderModule( + &self, + descriptor: RootedTraceableBox<GPUShaderModuleDescriptor>, + ) -> DomRoot<GPUShaderModule> { + let program: Vec<u32> = match &descriptor.code { + Uint32ArrayOrString::Uint32Array(program) => program.to_vec(), + Uint32ArrayOrString::String(program) => { + program.chars().map(|c| c as u32).collect::<Vec<u32>>() + }, + }; + let program_id = self + .global() + .wgpu_id_hub() + .lock() + .create_shader_module_id(self.device.0.backend()); + + let scope_id = self.use_current_scope(); + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateShaderModule { + device_id: self.device.0, + program_id, + program, + }, + )) + .expect("Failed to create WebGPU ShaderModule"); + + let shader_module = webgpu::WebGPUShaderModule(program_id); + GPUShaderModule::new( + &self.global(), + shader_module, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createcomputepipeline + fn CreateComputePipeline( + &self, + descriptor: &GPUComputePipelineDescriptor, + ) -> DomRoot<GPUComputePipeline> { + let compute_pipeline_id = self + .global() + .wgpu_id_hub() + .lock() + .create_compute_pipeline_id(self.device.0.backend()); + + let scope_id = self.use_current_scope(); + let (layout, implicit_ids, bgls) = self.get_pipeline_layout_data(&descriptor.parent.layout); + + let desc = wgpu_pipe::ComputePipelineDescriptor { + label: convert_label(&descriptor.parent.parent), + layout, + compute_stage: wgpu_pipe::ProgrammableStageDescriptor { + module: descriptor.computeStage.module.id().0, + entry_point: Cow::Owned(descriptor.computeStage.entryPoint.to_string()), + }, + }; + + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateComputePipeline { + device_id: self.device.0, + compute_pipeline_id, + descriptor: desc, + implicit_ids, + }, + )) + .expect("Failed to create WebGPU ComputePipeline"); + + let compute_pipeline = webgpu::WebGPUComputePipeline(compute_pipeline_id); + GPUComputePipeline::new( + &self.global(), + compute_pipeline, + descriptor.parent.parent.label.as_ref().cloned(), + bgls, + &self, + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createcommandencoder + fn CreateCommandEncoder( + &self, + descriptor: &GPUCommandEncoderDescriptor, + ) -> DomRoot<GPUCommandEncoder> { + let command_encoder_id = self + .global() + .wgpu_id_hub() + .lock() + .create_command_encoder_id(self.device.0.backend()); + let scope_id = self.use_current_scope(); + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateCommandEncoder { + device_id: self.device.0, + command_encoder_id, + label: convert_label(&descriptor.parent), + }, + )) + .expect("Failed to create WebGPU command encoder"); + + let encoder = webgpu::WebGPUCommandEncoder(command_encoder_id); + + GPUCommandEncoder::new( + &self.global(), + self.channel.clone(), + &self, + encoder, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createtexture + fn CreateTexture(&self, descriptor: &GPUTextureDescriptor) -> DomRoot<GPUTexture> { + let size = convert_texture_size_to_dict(&descriptor.size); + let desc = + wgt::TextureUsage::from_bits(descriptor.usage).map(|usg| wgpu_res::TextureDescriptor { + label: convert_label(&descriptor.parent), + size: convert_texture_size_to_wgt(&size), + mip_level_count: descriptor.mipLevelCount, + sample_count: descriptor.sampleCount, + dimension: match descriptor.dimension { + GPUTextureDimension::_1d => wgt::TextureDimension::D1, + GPUTextureDimension::_2d => wgt::TextureDimension::D2, + GPUTextureDimension::_3d => wgt::TextureDimension::D3, + }, + format: convert_texture_format(descriptor.format), + usage: usg, + }); + + let texture_id = self + .global() + .wgpu_id_hub() + .lock() + .create_texture_id(self.device.0.backend()); + + let scope_id = self.use_current_scope(); + if desc.is_none() { + self.handle_server_msg( + scope_id, + WebGPUOpResult::ValidationError(String::from("Invalid GPUTextureUsage")), + ); + } + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateTexture { + device_id: self.device.0, + texture_id, + descriptor: desc, + }, + )) + .expect("Failed to create WebGPU Texture"); + + let texture = webgpu::WebGPUTexture(texture_id); + + GPUTexture::new( + &self.global(), + texture, + &self, + self.channel.clone(), + size, + descriptor.mipLevelCount, + descriptor.sampleCount, + descriptor.dimension, + descriptor.format, + descriptor.usage, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createsampler + fn CreateSampler(&self, descriptor: &GPUSamplerDescriptor) -> DomRoot<GPUSampler> { + let sampler_id = self + .global() + .wgpu_id_hub() + .lock() + .create_sampler_id(self.device.0.backend()); + let compare_enable = descriptor.compare.is_some(); + let desc = wgpu_res::SamplerDescriptor { + label: convert_label(&descriptor.parent), + address_modes: [ + convert_address_mode(descriptor.addressModeU), + convert_address_mode(descriptor.addressModeV), + convert_address_mode(descriptor.addressModeW), + ], + mag_filter: convert_filter_mode(descriptor.magFilter), + min_filter: convert_filter_mode(descriptor.minFilter), + mipmap_filter: convert_filter_mode(descriptor.mipmapFilter), + lod_min_clamp: *descriptor.lodMinClamp, + lod_max_clamp: *descriptor.lodMaxClamp, + compare: descriptor.compare.map(|c| convert_compare_function(c)), + anisotropy_clamp: None, + ..Default::default() + }; + + let scope_id = self.use_current_scope(); + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateSampler { + device_id: self.device.0, + sampler_id, + descriptor: desc, + }, + )) + .expect("Failed to create WebGPU sampler"); + + let sampler = webgpu::WebGPUSampler(sampler_id); + + GPUSampler::new( + &self.global(), + self.device, + compare_enable, + sampler, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createrenderpipeline + fn CreateRenderPipeline( + &self, + descriptor: &GPURenderPipelineDescriptor, + ) -> DomRoot<GPURenderPipeline> { + let ref rs_desc = descriptor.rasterizationState; + let ref vs_desc = descriptor.vertexState; + let scope_id = self.use_current_scope(); + let mut valid = true; + let color_states = Cow::Owned( + descriptor + .colorStates + .iter() + .map(|state| wgt::ColorStateDescriptor { + format: convert_texture_format(state.format), + alpha_blend: convert_blend_descriptor(&state.alphaBlend), + color_blend: convert_blend_descriptor(&state.colorBlend), + write_mask: match wgt::ColorWrite::from_bits(state.writeMask) { + Some(mask) => mask, + None => { + valid = false; + wgt::ColorWrite::empty() + }, + }, + }) + .collect::<Vec<_>>(), + ); + let (layout, implicit_ids, bgls) = self.get_pipeline_layout_data(&descriptor.parent.layout); + + let desc = if valid { + Some(wgpu_pipe::RenderPipelineDescriptor { + label: convert_label(&descriptor.parent.parent), + layout, + vertex_stage: wgpu_pipe::ProgrammableStageDescriptor { + module: descriptor.vertexStage.module.id().0, + entry_point: Cow::Owned(descriptor.vertexStage.entryPoint.to_string()), + }, + fragment_stage: descriptor.fragmentStage.as_ref().map(|stage| { + wgpu_pipe::ProgrammableStageDescriptor { + module: stage.module.id().0, + entry_point: Cow::Owned(stage.entryPoint.to_string()), + } + }), + rasterization_state: Some(wgt::RasterizationStateDescriptor { + front_face: match rs_desc.frontFace { + GPUFrontFace::Ccw => wgt::FrontFace::Ccw, + GPUFrontFace::Cw => wgt::FrontFace::Cw, + }, + cull_mode: match rs_desc.cullMode { + GPUCullMode::None => wgt::CullMode::None, + GPUCullMode::Front => wgt::CullMode::Front, + GPUCullMode::Back => wgt::CullMode::Back, + }, + clamp_depth: rs_desc.clampDepth, + depth_bias: rs_desc.depthBias, + depth_bias_slope_scale: *rs_desc.depthBiasSlopeScale, + depth_bias_clamp: *rs_desc.depthBiasClamp, + ..Default::default() + }), + primitive_topology: match descriptor.primitiveTopology { + GPUPrimitiveTopology::Point_list => wgt::PrimitiveTopology::PointList, + GPUPrimitiveTopology::Line_list => wgt::PrimitiveTopology::LineList, + GPUPrimitiveTopology::Line_strip => wgt::PrimitiveTopology::LineStrip, + GPUPrimitiveTopology::Triangle_list => wgt::PrimitiveTopology::TriangleList, + GPUPrimitiveTopology::Triangle_strip => wgt::PrimitiveTopology::TriangleStrip, + }, + color_states, + depth_stencil_state: descriptor.depthStencilState.as_ref().map(|dss_desc| { + wgt::DepthStencilStateDescriptor { + format: convert_texture_format(dss_desc.format), + depth_write_enabled: dss_desc.depthWriteEnabled, + depth_compare: convert_compare_function(dss_desc.depthCompare), + stencil: wgt::StencilStateDescriptor { + front: wgt::StencilStateFaceDescriptor { + compare: convert_compare_function(dss_desc.stencilFront.compare), + fail_op: convert_stencil_op(dss_desc.stencilFront.failOp), + depth_fail_op: convert_stencil_op( + dss_desc.stencilFront.depthFailOp, + ), + pass_op: convert_stencil_op(dss_desc.stencilFront.passOp), + }, + back: wgt::StencilStateFaceDescriptor { + compare: convert_compare_function(dss_desc.stencilBack.compare), + fail_op: convert_stencil_op(dss_desc.stencilBack.failOp), + depth_fail_op: convert_stencil_op(dss_desc.stencilBack.depthFailOp), + pass_op: convert_stencil_op(dss_desc.stencilBack.passOp), + }, + read_mask: dss_desc.stencilReadMask, + write_mask: dss_desc.stencilWriteMask, + }, + } + }), + vertex_state: wgpu_pipe::VertexStateDescriptor { + index_format: match vs_desc.indexFormat { + GPUIndexFormat::Uint16 => wgt::IndexFormat::Uint16, + GPUIndexFormat::Uint32 => wgt::IndexFormat::Uint32, + }, + vertex_buffers: Cow::Owned( + vs_desc + .vertexBuffers + .iter() + .map(|buffer| wgpu_pipe::VertexBufferDescriptor { + stride: buffer.arrayStride, + step_mode: match buffer.stepMode { + GPUInputStepMode::Vertex => wgt::InputStepMode::Vertex, + GPUInputStepMode::Instance => wgt::InputStepMode::Instance, + }, + attributes: Cow::Owned( + buffer + .attributes + .iter() + .map(|att| wgt::VertexAttributeDescriptor { + format: convert_vertex_format(att.format), + offset: att.offset, + shader_location: att.shaderLocation, + }) + .collect::<Vec<_>>(), + ), + }) + .collect::<Vec<_>>(), + ), + }, + sample_count: descriptor.sampleCount, + sample_mask: descriptor.sampleMask, + alpha_to_coverage_enabled: descriptor.alphaToCoverageEnabled, + }) + } else { + self.handle_server_msg( + scope_id, + WebGPUOpResult::ValidationError(String::from("Invalid GPUColorWriteFlags")), + ); + None + }; + + let render_pipeline_id = self + .global() + .wgpu_id_hub() + .lock() + .create_render_pipeline_id(self.device.0.backend()); + + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateRenderPipeline { + device_id: self.device.0, + render_pipeline_id, + descriptor: desc, + implicit_ids, + }, + )) + .expect("Failed to create WebGPU render pipeline"); + + let render_pipeline = webgpu::WebGPURenderPipeline(render_pipeline_id); + + GPURenderPipeline::new( + &self.global(), + render_pipeline, + descriptor.parent.parent.label.as_ref().cloned(), + bgls, + &self, + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-createrenderbundleencoder + fn CreateRenderBundleEncoder( + &self, + descriptor: &GPURenderBundleEncoderDescriptor, + ) -> DomRoot<GPURenderBundleEncoder> { + let desc = wgpu_com::RenderBundleEncoderDescriptor { + label: convert_label(&descriptor.parent), + color_formats: Cow::Owned( + descriptor + .colorFormats + .iter() + .map(|f| convert_texture_format(*f)) + .collect::<Vec<_>>(), + ), + depth_stencil_format: descriptor + .depthStencilFormat + .map(|f| convert_texture_format(f)), + sample_count: descriptor.sampleCount, + }; + + // Handle error gracefully + let render_bundle_encoder = + wgpu_com::RenderBundleEncoder::new(&desc, self.device.0, None).unwrap(); + + GPURenderBundleEncoder::new( + &self.global(), + render_bundle_encoder, + &self, + self.channel.clone(), + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-pusherrorscope + fn PushErrorScope(&self, filter: GPUErrorFilter) { + let mut context = self.scope_context.borrow_mut(); + let scope_id = context.next_scope_id; + context.next_scope_id = ErrorScopeId::new(scope_id.get() + 1).unwrap(); + let err_scope = ErrorScopeInfo { + op_count: 0, + error: None, + promise: None, + }; + let res = context.error_scopes.insert(scope_id, err_scope); + context.scope_stack.push(ErrorScopeMetadata { + id: scope_id, + filter, + popped: Cell::new(false), + }); + assert!(res.is_none()); + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpudevice-poperrorscope + fn PopErrorScope(&self, comp: InRealm) -> Rc<Promise> { + let mut context = self.scope_context.borrow_mut(); + let promise = Promise::new_in_current_realm(&self.global(), comp); + let scope_id = + if let Some(meta) = context.scope_stack.iter().rev().find(|m| !m.popped.get()) { + meta.popped.set(true); + meta.id + } else { + promise.reject_error(Error::Operation); + return promise; + }; + let remove = if let Some(mut err_scope) = context.error_scopes.get_mut(&scope_id) { + if let Some(ref e) = err_scope.error { + promise.resolve_native(e); + } else if err_scope.op_count == 0 { + promise.resolve_native(&None::<GPUError>); + } + err_scope.promise = Some(promise.clone()); + err_scope.op_count == 0 + } else { + error!("Could not find ErrorScope with Id({})", scope_id); + false + }; + if remove { + let _ = context.error_scopes.remove(&scope_id); + context.scope_stack.retain(|meta| meta.id != scope_id); + } + promise + } + + // https://gpuweb.github.io/gpuweb/#dom-gpudevice-onuncapturederror + event_handler!(uncapturederror, GetOnuncapturederror, SetOnuncapturederror); +} + +fn convert_address_mode(address_mode: GPUAddressMode) -> wgt::AddressMode { + match address_mode { + GPUAddressMode::Clamp_to_edge => wgt::AddressMode::ClampToEdge, + GPUAddressMode::Repeat => wgt::AddressMode::Repeat, + GPUAddressMode::Mirror_repeat => wgt::AddressMode::MirrorRepeat, + } +} + +fn convert_filter_mode(filter_mode: GPUFilterMode) -> wgt::FilterMode { + match filter_mode { + GPUFilterMode::Nearest => wgt::FilterMode::Nearest, + GPUFilterMode::Linear => wgt::FilterMode::Linear, + } +} + +fn convert_compare_function(compare: GPUCompareFunction) -> wgt::CompareFunction { + match compare { + GPUCompareFunction::Never => wgt::CompareFunction::Never, + GPUCompareFunction::Less => wgt::CompareFunction::Less, + GPUCompareFunction::Equal => wgt::CompareFunction::Equal, + GPUCompareFunction::Less_equal => wgt::CompareFunction::LessEqual, + GPUCompareFunction::Greater => wgt::CompareFunction::Greater, + GPUCompareFunction::Not_equal => wgt::CompareFunction::NotEqual, + GPUCompareFunction::Greater_equal => wgt::CompareFunction::GreaterEqual, + GPUCompareFunction::Always => wgt::CompareFunction::Always, + } +} + +fn convert_blend_descriptor(desc: &GPUBlendDescriptor) -> wgt::BlendDescriptor { + wgt::BlendDescriptor { + src_factor: convert_blend_factor(desc.srcFactor), + dst_factor: convert_blend_factor(desc.dstFactor), + operation: match desc.operation { + GPUBlendOperation::Add => wgt::BlendOperation::Add, + GPUBlendOperation::Subtract => wgt::BlendOperation::Subtract, + GPUBlendOperation::Reverse_subtract => wgt::BlendOperation::ReverseSubtract, + GPUBlendOperation::Min => wgt::BlendOperation::Min, + GPUBlendOperation::Max => wgt::BlendOperation::Max, + }, + } +} + +fn convert_blend_factor(factor: GPUBlendFactor) -> wgt::BlendFactor { + match factor { + GPUBlendFactor::Zero => wgt::BlendFactor::Zero, + GPUBlendFactor::One => wgt::BlendFactor::One, + GPUBlendFactor::Src_color => wgt::BlendFactor::SrcColor, + GPUBlendFactor::One_minus_src_color => wgt::BlendFactor::OneMinusSrcColor, + GPUBlendFactor::Src_alpha => wgt::BlendFactor::SrcAlpha, + GPUBlendFactor::One_minus_src_alpha => wgt::BlendFactor::OneMinusSrcAlpha, + GPUBlendFactor::Dst_color => wgt::BlendFactor::DstColor, + GPUBlendFactor::One_minus_dst_color => wgt::BlendFactor::OneMinusDstColor, + GPUBlendFactor::Dst_alpha => wgt::BlendFactor::DstAlpha, + GPUBlendFactor::One_minus_dst_alpha => wgt::BlendFactor::OneMinusDstAlpha, + GPUBlendFactor::Src_alpha_saturated => wgt::BlendFactor::SrcAlphaSaturated, + GPUBlendFactor::Blend_color => wgt::BlendFactor::BlendColor, + GPUBlendFactor::One_minus_blend_color => wgt::BlendFactor::OneMinusBlendColor, + } +} + +fn convert_stencil_op(operation: GPUStencilOperation) -> wgt::StencilOperation { + match operation { + GPUStencilOperation::Keep => wgt::StencilOperation::Keep, + GPUStencilOperation::Zero => wgt::StencilOperation::Zero, + GPUStencilOperation::Replace => wgt::StencilOperation::Replace, + GPUStencilOperation::Invert => wgt::StencilOperation::Invert, + GPUStencilOperation::Increment_clamp => wgt::StencilOperation::IncrementClamp, + GPUStencilOperation::Decrement_clamp => wgt::StencilOperation::DecrementClamp, + GPUStencilOperation::Increment_wrap => wgt::StencilOperation::IncrementWrap, + GPUStencilOperation::Decrement_wrap => wgt::StencilOperation::DecrementWrap, + } +} + +fn convert_vertex_format(format: GPUVertexFormat) -> wgt::VertexFormat { + match format { + GPUVertexFormat::Uchar2 => wgt::VertexFormat::Uchar2, + GPUVertexFormat::Uchar4 => wgt::VertexFormat::Uchar4, + GPUVertexFormat::Char2 => wgt::VertexFormat::Char2, + GPUVertexFormat::Char4 => wgt::VertexFormat::Char4, + GPUVertexFormat::Uchar2norm => wgt::VertexFormat::Uchar2Norm, + GPUVertexFormat::Uchar4norm => wgt::VertexFormat::Uchar4Norm, + GPUVertexFormat::Char2norm => wgt::VertexFormat::Char2Norm, + GPUVertexFormat::Char4norm => wgt::VertexFormat::Char4Norm, + GPUVertexFormat::Ushort2 => wgt::VertexFormat::Ushort2, + GPUVertexFormat::Ushort4 => wgt::VertexFormat::Ushort4, + GPUVertexFormat::Short2 => wgt::VertexFormat::Short2, + GPUVertexFormat::Short4 => wgt::VertexFormat::Short4, + GPUVertexFormat::Ushort2norm => wgt::VertexFormat::Ushort2Norm, + GPUVertexFormat::Ushort4norm => wgt::VertexFormat::Ushort4Norm, + GPUVertexFormat::Short2norm => wgt::VertexFormat::Short2Norm, + GPUVertexFormat::Short4norm => wgt::VertexFormat::Short4Norm, + GPUVertexFormat::Half2 => wgt::VertexFormat::Half2, + GPUVertexFormat::Half4 => wgt::VertexFormat::Half4, + GPUVertexFormat::Float => wgt::VertexFormat::Float, + GPUVertexFormat::Float2 => wgt::VertexFormat::Float2, + GPUVertexFormat::Float3 => wgt::VertexFormat::Float3, + GPUVertexFormat::Float4 => wgt::VertexFormat::Float4, + GPUVertexFormat::Uint => wgt::VertexFormat::Uint, + GPUVertexFormat::Uint2 => wgt::VertexFormat::Uint2, + GPUVertexFormat::Uint3 => wgt::VertexFormat::Uint3, + GPUVertexFormat::Uint4 => wgt::VertexFormat::Uint4, + GPUVertexFormat::Int => wgt::VertexFormat::Int, + GPUVertexFormat::Int2 => wgt::VertexFormat::Int2, + GPUVertexFormat::Int3 => wgt::VertexFormat::Int3, + GPUVertexFormat::Int4 => wgt::VertexFormat::Int4, + } +} + +pub fn convert_texture_format(format: GPUTextureFormat) -> wgt::TextureFormat { + match format { + GPUTextureFormat::R8unorm => wgt::TextureFormat::R8Unorm, + GPUTextureFormat::R8snorm => wgt::TextureFormat::R8Snorm, + GPUTextureFormat::R8uint => wgt::TextureFormat::R8Uint, + GPUTextureFormat::R8sint => wgt::TextureFormat::R8Sint, + GPUTextureFormat::R16uint => wgt::TextureFormat::R16Uint, + GPUTextureFormat::R16sint => wgt::TextureFormat::R16Sint, + GPUTextureFormat::R16float => wgt::TextureFormat::R16Float, + GPUTextureFormat::Rg8unorm => wgt::TextureFormat::Rg8Unorm, + GPUTextureFormat::Rg8snorm => wgt::TextureFormat::Rg8Snorm, + GPUTextureFormat::Rg8uint => wgt::TextureFormat::Rg8Uint, + GPUTextureFormat::Rg8sint => wgt::TextureFormat::Rg8Sint, + GPUTextureFormat::R32uint => wgt::TextureFormat::R32Uint, + GPUTextureFormat::R32sint => wgt::TextureFormat::R32Sint, + GPUTextureFormat::R32float => wgt::TextureFormat::R32Float, + GPUTextureFormat::Rg16uint => wgt::TextureFormat::Rg16Uint, + GPUTextureFormat::Rg16sint => wgt::TextureFormat::Rg16Sint, + GPUTextureFormat::Rg16float => wgt::TextureFormat::Rg16Float, + GPUTextureFormat::Rgba8unorm => wgt::TextureFormat::Rgba8Unorm, + GPUTextureFormat::Rgba8unorm_srgb => wgt::TextureFormat::Rgba8UnormSrgb, + GPUTextureFormat::Rgba8snorm => wgt::TextureFormat::Rgba8Snorm, + GPUTextureFormat::Rgba8uint => wgt::TextureFormat::Rgba8Uint, + GPUTextureFormat::Rgba8sint => wgt::TextureFormat::Rgba8Sint, + GPUTextureFormat::Bgra8unorm => wgt::TextureFormat::Bgra8Unorm, + GPUTextureFormat::Bgra8unorm_srgb => wgt::TextureFormat::Bgra8UnormSrgb, + GPUTextureFormat::Rgb10a2unorm => wgt::TextureFormat::Rgb10a2Unorm, + GPUTextureFormat::Rg32uint => wgt::TextureFormat::Rg32Uint, + GPUTextureFormat::Rg32sint => wgt::TextureFormat::Rg32Sint, + GPUTextureFormat::Rg32float => wgt::TextureFormat::Rg32Float, + GPUTextureFormat::Rgba16uint => wgt::TextureFormat::Rgba16Uint, + GPUTextureFormat::Rgba16sint => wgt::TextureFormat::Rgba16Sint, + GPUTextureFormat::Rgba16float => wgt::TextureFormat::Rgba16Float, + GPUTextureFormat::Rgba32uint => wgt::TextureFormat::Rgba32Uint, + GPUTextureFormat::Rgba32sint => wgt::TextureFormat::Rgba32Sint, + GPUTextureFormat::Rgba32float => wgt::TextureFormat::Rgba32Float, + GPUTextureFormat::Depth32float => wgt::TextureFormat::Depth32Float, + GPUTextureFormat::Depth24plus => wgt::TextureFormat::Depth24Plus, + GPUTextureFormat::Depth24plus_stencil8 => wgt::TextureFormat::Depth24PlusStencil8, + GPUTextureFormat::Bc1_rgba_unorm => wgt::TextureFormat::Bc1RgbaUnorm, + GPUTextureFormat::Bc1_rgba_unorm_srgb => wgt::TextureFormat::Bc1RgbaUnormSrgb, + GPUTextureFormat::Bc2_rgba_unorm => wgt::TextureFormat::Bc2RgbaUnorm, + GPUTextureFormat::Bc2_rgba_unorm_srgb => wgt::TextureFormat::Bc2RgbaUnormSrgb, + GPUTextureFormat::Bc3_rgba_unorm => wgt::TextureFormat::Bc3RgbaUnorm, + GPUTextureFormat::Bc3_rgba_unorm_srgb => wgt::TextureFormat::Bc3RgbaUnormSrgb, + GPUTextureFormat::Bc4_r_unorm => wgt::TextureFormat::Bc4RUnorm, + GPUTextureFormat::Bc4_r_snorm => wgt::TextureFormat::Bc4RSnorm, + GPUTextureFormat::Bc5_rg_unorm => wgt::TextureFormat::Bc5RgUnorm, + GPUTextureFormat::Bc5_rg_snorm => wgt::TextureFormat::Bc5RgSnorm, + GPUTextureFormat::Bc6h_rgb_ufloat => wgt::TextureFormat::Bc6hRgbUfloat, + GPUTextureFormat::Bc7_rgba_unorm => wgt::TextureFormat::Bc7RgbaUnorm, + GPUTextureFormat::Bc7_rgba_unorm_srgb => wgt::TextureFormat::Bc7RgbaUnormSrgb, + } +} + +fn convert_texture_component_type( + ty: Option<GPUTextureComponentType>, +) -> wgt::TextureComponentType { + if let Some(c) = ty { + match c { + GPUTextureComponentType::Float => wgt::TextureComponentType::Float, + GPUTextureComponentType::Sint => wgt::TextureComponentType::Sint, + GPUTextureComponentType::Uint => wgt::TextureComponentType::Uint, + GPUTextureComponentType::Depth_comparison => wgt::TextureComponentType::DepthComparison, + } + } else { + wgt::TextureComponentType::Float + } +} + +pub fn convert_texture_view_dimension( + dimension: GPUTextureViewDimension, +) -> wgt::TextureViewDimension { + match dimension { + GPUTextureViewDimension::_1d => wgt::TextureViewDimension::D1, + GPUTextureViewDimension::_2d => wgt::TextureViewDimension::D2, + GPUTextureViewDimension::_2d_array => wgt::TextureViewDimension::D2Array, + GPUTextureViewDimension::Cube => wgt::TextureViewDimension::Cube, + GPUTextureViewDimension::Cube_array => wgt::TextureViewDimension::CubeArray, + GPUTextureViewDimension::_3d => wgt::TextureViewDimension::D3, + } +} + +pub fn convert_texture_size_to_dict(size: &GPUExtent3D) -> GPUExtent3DDict { + match *size { + GPUExtent3D::GPUExtent3DDict(ref dict) => GPUExtent3DDict { + width: dict.width, + height: dict.height, + depth: dict.depth, + }, + GPUExtent3D::RangeEnforcedUnsignedLongSequence(ref v) => { + let mut w = v.clone(); + w.resize(3, 1); + GPUExtent3DDict { + width: w[0], + height: w[1], + depth: w[2], + } + }, + } +} + +pub fn convert_texture_size_to_wgt(size: &GPUExtent3DDict) -> wgt::Extent3d { + wgt::Extent3d { + width: size.width, + height: size.height, + depth: size.depth, + } +} + +pub fn convert_label(parent: &GPUObjectDescriptorBase) -> Option<Cow<'static, str>> { + parent.label.as_ref().map(|s| Cow::Owned(s.to_string())) +} diff --git a/components/script/dom/gpudevicelostinfo.rs b/components/script/dom/gpudevicelostinfo.rs new file mode 100644 index 00000000000..f054cbce8cd --- /dev/null +++ b/components/script/dom/gpudevicelostinfo.rs @@ -0,0 +1,38 @@ +/* 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/. */ + +#![allow(dead_code)] + +use crate::dom::bindings::codegen::Bindings::GPUDeviceLostInfoBinding::GPUDeviceLostInfoMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct GPUDeviceLostInfo { + reflector_: Reflector, + message: DOMString, +} + +impl GPUDeviceLostInfo { + fn new_inherited(message: DOMString) -> Self { + Self { + reflector_: Reflector::new(), + message, + } + } + + pub fn new(global: &GlobalScope, message: DOMString) -> DomRoot<Self> { + reflect_dom_object(Box::new(GPUDeviceLostInfo::new_inherited(message)), global) + } +} + +impl GPUDeviceLostInfoMethods for GPUDeviceLostInfo { + /// https://gpuweb.github.io/gpuweb/#dom-gpudevicelostinfo-message + fn Message(&self) -> DOMString { + self.message.clone() + } +} diff --git a/components/script/dom/gpumapmode.rs b/components/script/dom/gpumapmode.rs new file mode 100644 index 00000000000..b216d3f839d --- /dev/null +++ b/components/script/dom/gpumapmode.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::bindings::reflector::Reflector; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct GPUMapMode { + reflector_: Reflector, +} diff --git a/components/script/dom/gpuoutofmemoryerror.rs b/components/script/dom/gpuoutofmemoryerror.rs new file mode 100644 index 00000000000..fb0b2e11d86 --- /dev/null +++ b/components/script/dom/gpuoutofmemoryerror.rs @@ -0,0 +1,31 @@ +/* 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 crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct GPUOutOfMemoryError { + reflector_: Reflector, +} + +impl GPUOutOfMemoryError { + fn new_inherited() -> Self { + Self { + reflector_: Reflector::new(), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<Self> { + reflect_dom_object(Box::new(GPUOutOfMemoryError::new_inherited()), global) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuoutofmemoryerror-gpuoutofmemoryerror + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> DomRoot<Self> { + GPUOutOfMemoryError::new(global) + } +} diff --git a/components/script/dom/gpupipelinelayout.rs b/components/script/dom/gpupipelinelayout.rs new file mode 100644 index 00000000000..563d0ca9220 --- /dev/null +++ b/components/script/dom/gpupipelinelayout.rs @@ -0,0 +1,73 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUPipelineLayoutBinding::GPUPipelineLayoutMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use webgpu::{WebGPUBindGroupLayout, WebGPUPipelineLayout}; + +#[dom_struct] +pub struct GPUPipelineLayout { + reflector_: Reflector, + label: DomRefCell<Option<USVString>>, + pipeline_layout: WebGPUPipelineLayout, + bind_group_layouts: Vec<WebGPUBindGroupLayout>, +} + +impl GPUPipelineLayout { + fn new_inherited( + pipeline_layout: WebGPUPipelineLayout, + label: Option<USVString>, + bgls: Vec<WebGPUBindGroupLayout>, + ) -> Self { + Self { + reflector_: Reflector::new(), + label: DomRefCell::new(label), + pipeline_layout, + bind_group_layouts: bgls, + } + } + + pub fn new( + global: &GlobalScope, + pipeline_layout: WebGPUPipelineLayout, + label: Option<USVString>, + bgls: Vec<WebGPUBindGroupLayout>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUPipelineLayout::new_inherited( + pipeline_layout, + label, + bgls, + )), + global, + ) + } +} + +impl GPUPipelineLayout { + pub fn id(&self) -> WebGPUPipelineLayout { + self.pipeline_layout + } + + pub fn bind_group_layouts(&self) -> Vec<WebGPUBindGroupLayout> { + self.bind_group_layouts.clone() + } +} + +impl GPUPipelineLayoutMethods for GPUPipelineLayout { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/gpuqueue.rs b/components/script/dom/gpuqueue.rs new file mode 100644 index 00000000000..ca571371c8c --- /dev/null +++ b/components/script/dom/gpuqueue.rs @@ -0,0 +1,190 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUBufferBinding::GPUSize64; +use crate::dom::bindings::codegen::Bindings::GPUCommandEncoderBinding::{ + GPUTextureCopyView, GPUTextureDataLayout, +}; +use crate::dom::bindings::codegen::Bindings::GPUQueueBinding::GPUQueueMethods; +use crate::dom::bindings::codegen::Bindings::GPUTextureBinding::GPUExtent3D; +use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer as BufferSource; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubuffer::{GPUBuffer, GPUBufferState}; +use crate::dom::gpucommandbuffer::GPUCommandBuffer; +use crate::dom::gpucommandencoder::{convert_texture_cv, convert_texture_data_layout}; +use crate::dom::gpudevice::{convert_texture_size_to_dict, convert_texture_size_to_wgt, GPUDevice}; +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSharedMemory; +use webgpu::{identity::WebGPUOpResult, wgt, WebGPU, WebGPUQueue, WebGPURequest}; + +#[dom_struct] +pub struct GPUQueue { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + channel: WebGPU, + device: DomRefCell<Option<Dom<GPUDevice>>>, + label: DomRefCell<Option<USVString>>, + queue: WebGPUQueue, +} + +impl GPUQueue { + fn new_inherited(channel: WebGPU, queue: WebGPUQueue) -> Self { + GPUQueue { + channel, + reflector_: Reflector::new(), + device: DomRefCell::new(None), + label: DomRefCell::new(None), + queue, + } + } + + pub fn new(global: &GlobalScope, channel: WebGPU, queue: WebGPUQueue) -> DomRoot<Self> { + reflect_dom_object(Box::new(GPUQueue::new_inherited(channel, queue)), global) + } +} + +impl GPUQueue { + pub fn set_device(&self, device: &GPUDevice) { + *self.device.borrow_mut() = Some(Dom::from_ref(device)); + } +} + +impl GPUQueueMethods for GPUQueue { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuqueue-submit + fn Submit(&self, command_buffers: Vec<DomRoot<GPUCommandBuffer>>) { + let valid = command_buffers.iter().all(|cb| { + cb.buffers().iter().all(|b| match b.state() { + GPUBufferState::Unmapped => true, + _ => false, + }) + }); + let scope_id = self.device.borrow().as_ref().unwrap().use_current_scope(); + if !valid { + self.device.borrow().as_ref().unwrap().handle_server_msg( + scope_id, + WebGPUOpResult::ValidationError(String::from( + "Referenced GPUBuffer(s) are not Unmapped", + )), + ); + return; + } + let command_buffers = command_buffers.iter().map(|cb| cb.id().0).collect(); + self.channel + .0 + .send(( + scope_id, + WebGPURequest::Submit { + queue_id: self.queue.0, + command_buffers, + }, + )) + .unwrap(); + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writebuffer + #[allow(unsafe_code)] + fn WriteBuffer( + &self, + buffer: &GPUBuffer, + buffer_offset: GPUSize64, + data: BufferSource, + data_offset: GPUSize64, + size: Option<GPUSize64>, + ) -> Fallible<()> { + let bytes = match data { + BufferSource::ArrayBufferView(d) => d.to_vec(), + BufferSource::ArrayBuffer(d) => d.to_vec(), + }; + let content_size = if let Some(s) = size { + s + } else { + bytes.len() as GPUSize64 - data_offset + }; + let valid = data_offset + content_size <= bytes.len() as u64 && + buffer.state() == GPUBufferState::Unmapped && + content_size % wgt::COPY_BUFFER_ALIGNMENT == 0 && + buffer_offset % wgt::COPY_BUFFER_ALIGNMENT == 0; + + if !valid { + return Err(Error::Operation); + } + + let final_data = IpcSharedMemory::from_bytes( + &bytes[data_offset as usize..(data_offset + content_size) as usize], + ); + if let Err(e) = self.channel.0.send(( + self.device.borrow().as_ref().unwrap().use_current_scope(), + WebGPURequest::WriteBuffer { + queue_id: self.queue.0, + buffer_id: buffer.id().0, + buffer_offset, + data: final_data, + }, + )) { + warn!("Failed to send WriteBuffer({:?}) ({})", buffer.id(), e); + return Err(Error::Operation); + } + + Ok(()) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writetexture + fn WriteTexture( + &self, + destination: &GPUTextureCopyView, + data: BufferSource, + data_layout: &GPUTextureDataLayout, + size: GPUExtent3D, + ) -> Fallible<()> { + let (bytes, len) = match data { + BufferSource::ArrayBufferView(d) => (d.to_vec(), d.len() as u64), + BufferSource::ArrayBuffer(d) => (d.to_vec(), d.len() as u64), + }; + let valid = data_layout.offset <= len; + + if !valid { + return Err(Error::Operation); + } + + let texture_cv = convert_texture_cv(destination); + let texture_layout = convert_texture_data_layout(data_layout); + let write_size = convert_texture_size_to_wgt(&convert_texture_size_to_dict(&size)); + let final_data = IpcSharedMemory::from_bytes(&bytes); + + if let Err(e) = self.channel.0.send(( + self.device.borrow().as_ref().unwrap().use_current_scope(), + WebGPURequest::WriteTexture { + queue_id: self.queue.0, + texture_cv, + data_layout: texture_layout, + size: write_size, + data: final_data, + }, + )) { + warn!( + "Failed to send WriteTexture({:?}) ({})", + destination.texture.id().0, + e + ); + return Err(Error::Operation); + } + + Ok(()) + } +} diff --git a/components/script/dom/gpurenderbundle.rs b/components/script/dom/gpurenderbundle.rs new file mode 100644 index 00000000000..5c020f8f697 --- /dev/null +++ b/components/script/dom/gpurenderbundle.rs @@ -0,0 +1,75 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPURenderBundleBinding::GPURenderBundleMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use webgpu::{WebGPU, WebGPUDevice, WebGPURenderBundle}; + +#[dom_struct] +pub struct GPURenderBundle { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + channel: WebGPU, + device: WebGPUDevice, + render_bundle: WebGPURenderBundle, + label: DomRefCell<Option<USVString>>, +} + +impl GPURenderBundle { + fn new_inherited( + render_bundle: WebGPURenderBundle, + device: WebGPUDevice, + channel: WebGPU, + label: Option<USVString>, + ) -> Self { + Self { + reflector_: Reflector::new(), + render_bundle, + device, + channel, + label: DomRefCell::new(label), + } + } + + pub fn new( + global: &GlobalScope, + render_bundle: WebGPURenderBundle, + device: WebGPUDevice, + channel: WebGPU, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPURenderBundle::new_inherited( + render_bundle, + device, + channel, + label, + )), + global, + ) + } +} + +impl GPURenderBundle { + pub fn id(&self) -> WebGPURenderBundle { + self.render_bundle + } +} + +impl GPURenderBundleMethods for GPURenderBundle { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/gpurenderbundleencoder.rs b/components/script/dom/gpurenderbundleencoder.rs new file mode 100644 index 00000000000..722afdebc0e --- /dev/null +++ b/components/script/dom/gpurenderbundleencoder.rs @@ -0,0 +1,218 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPURenderBundleBinding::GPURenderBundleDescriptor; +use crate::dom::bindings::codegen::Bindings::GPURenderBundleEncoderBinding::GPURenderBundleEncoderMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgroup::GPUBindGroup; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpudevice::{convert_label, GPUDevice}; +use crate::dom::gpurenderbundle::GPURenderBundle; +use crate::dom::gpurenderpipeline::GPURenderPipeline; +use dom_struct::dom_struct; +use webgpu::{ + wgpu::command::{bundle_ffi as wgpu_bundle, RenderBundleEncoder}, + wgt, WebGPU, WebGPURenderBundle, WebGPURequest, +}; + +#[dom_struct] +pub struct GPURenderBundleEncoder { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + channel: WebGPU, + device: Dom<GPUDevice>, + #[ignore_malloc_size_of = "defined in wgpu-core"] + render_bundle_encoder: DomRefCell<Option<RenderBundleEncoder>>, + label: DomRefCell<Option<USVString>>, +} + +impl GPURenderBundleEncoder { + fn new_inherited( + render_bundle_encoder: RenderBundleEncoder, + device: &GPUDevice, + channel: WebGPU, + label: Option<USVString>, + ) -> Self { + Self { + reflector_: Reflector::new(), + render_bundle_encoder: DomRefCell::new(Some(render_bundle_encoder)), + device: Dom::from_ref(device), + channel, + label: DomRefCell::new(label), + } + } + + pub fn new( + global: &GlobalScope, + render_bundle_encoder: RenderBundleEncoder, + device: &GPUDevice, + channel: WebGPU, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPURenderBundleEncoder::new_inherited( + render_bundle_encoder, + device, + channel, + label, + )), + global, + ) + } +} + +impl GPURenderBundleEncoderMethods for GPURenderBundleEncoder { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuprogrammablepassencoder-setbindgroup + #[allow(unsafe_code)] + fn SetBindGroup(&self, index: u32, bind_group: &GPUBindGroup, dynamic_offsets: Vec<u32>) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + unsafe { + wgpu_bundle::wgpu_render_bundle_set_bind_group( + encoder, + index, + bind_group.id().0, + dynamic_offsets.as_ptr(), + dynamic_offsets.len(), + ) + }; + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setpipeline + fn SetPipeline(&self, pipeline: &GPURenderPipeline) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_set_pipeline(encoder, pipeline.id().0); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setindexbuffer + fn SetIndexBuffer(&self, buffer: &GPUBuffer, offset: u64, size: u64) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_set_index_buffer( + encoder, + buffer.id().0, + offset, + wgt::BufferSize::new(size), + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setvertexbuffer + fn SetVertexBuffer(&self, slot: u32, buffer: &GPUBuffer, offset: u64, size: u64) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_set_vertex_buffer( + encoder, + slot, + buffer.id().0, + offset, + wgt::BufferSize::new(size), + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-draw + fn Draw(&self, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_draw( + encoder, + vertex_count, + instance_count, + first_vertex, + first_instance, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexed + fn DrawIndexed( + &self, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, + ) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_draw_indexed( + encoder, + index_count, + instance_count, + first_index, + base_vertex, + first_instance, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindirect + fn DrawIndirect(&self, indirect_buffer: &GPUBuffer, indirect_offset: u64) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_draw_indirect( + encoder, + indirect_buffer.id().0, + indirect_offset, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexedindirect + fn DrawIndexedIndirect(&self, indirect_buffer: &GPUBuffer, indirect_offset: u64) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_pass_bundle_indexed_indirect( + encoder, + indirect_buffer.id().0, + indirect_offset, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderbundleencoder-finish + fn Finish(&self, descriptor: &GPURenderBundleDescriptor) -> DomRoot<GPURenderBundle> { + let desc = wgt::RenderBundleDescriptor { + label: convert_label(&descriptor.parent), + }; + let encoder = self.render_bundle_encoder.borrow_mut().take().unwrap(); + let render_bundle_id = self + .global() + .wgpu_id_hub() + .lock() + .create_render_bundle_id(self.device.id().0.backend()); + + self.channel + .0 + .send(( + self.device.use_current_scope(), + WebGPURequest::RenderBundleEncoderFinish { + render_bundle_encoder: encoder, + descriptor: desc, + render_bundle_id, + device_id: self.device.id().0, + }, + )) + .expect("Failed to send RenderBundleEncoderFinish"); + + let render_bundle = WebGPURenderBundle(render_bundle_id); + GPURenderBundle::new( + &self.global(), + render_bundle, + self.device.id(), + self.channel.clone(), + descriptor.parent.label.as_ref().cloned(), + ) + } +} diff --git a/components/script/dom/gpurenderpassencoder.rs b/components/script/dom/gpurenderpassencoder.rs new file mode 100644 index 00000000000..ac7424b1999 --- /dev/null +++ b/components/script/dom/gpurenderpassencoder.rs @@ -0,0 +1,283 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUCommandEncoderBinding::GPUColor; +use crate::dom::bindings::codegen::Bindings::GPURenderPassEncoderBinding::GPURenderPassEncoderMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgroup::GPUBindGroup; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpucommandencoder::{GPUCommandEncoder, GPUCommandEncoderState}; +use crate::dom::gpurenderbundle::GPURenderBundle; +use crate::dom::gpurenderpipeline::GPURenderPipeline; +use dom_struct::dom_struct; +use webgpu::{ + wgpu::command::{render_ffi as wgpu_render, RenderPass}, + wgt, WebGPU, WebGPURequest, +}; + +#[dom_struct] +pub struct GPURenderPassEncoder { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + channel: WebGPU, + label: DomRefCell<Option<USVString>>, + #[ignore_malloc_size_of = "defined in wgpu-core"] + render_pass: DomRefCell<Option<RenderPass>>, + command_encoder: Dom<GPUCommandEncoder>, +} + +impl GPURenderPassEncoder { + fn new_inherited( + channel: WebGPU, + render_pass: Option<RenderPass>, + parent: &GPUCommandEncoder, + label: Option<USVString>, + ) -> Self { + Self { + channel, + reflector_: Reflector::new(), + label: DomRefCell::new(label), + render_pass: DomRefCell::new(render_pass), + command_encoder: Dom::from_ref(parent), + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + render_pass: Option<RenderPass>, + parent: &GPUCommandEncoder, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPURenderPassEncoder::new_inherited( + channel, + render_pass, + parent, + label, + )), + global, + ) + } +} + +impl GPURenderPassEncoderMethods for GPURenderPassEncoder { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuprogrammablepassencoder-setbindgroup + #[allow(unsafe_code)] + fn SetBindGroup(&self, index: u32, bind_group: &GPUBindGroup, dynamic_offsets: Vec<u32>) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + unsafe { + wgpu_render::wgpu_render_pass_set_bind_group( + render_pass, + index, + bind_group.id().0, + dynamic_offsets.as_ptr(), + dynamic_offsets.len(), + ) + }; + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-setviewport + fn SetViewport( + &self, + x: Finite<f32>, + y: Finite<f32>, + width: Finite<f32>, + height: Finite<f32>, + min_depth: Finite<f32>, + max_depth: Finite<f32>, + ) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_set_viewport( + render_pass, + *x, + *y, + *width, + *height, + *min_depth, + *max_depth, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-setscissorrect + fn SetScissorRect(&self, x: u32, y: u32, width: u32, height: u32) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_set_scissor_rect(render_pass, x, y, width, height); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-setblendcolor + fn SetBlendColor(&self, color: GPUColor) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + let colors = match color { + GPUColor::GPUColorDict(d) => wgt::Color { + r: *d.r, + g: *d.g, + b: *d.b, + a: *d.a, + }, + GPUColor::DoubleSequence(mut s) => { + if s.len() < 3 { + s.resize(3, Finite::wrap(0.0f64)); + } + s.resize(4, Finite::wrap(1.0f64)); + wgt::Color { + r: *s[0], + g: *s[1], + b: *s[2], + a: *s[3], + } + }, + }; + wgpu_render::wgpu_render_pass_set_blend_color(render_pass, &colors); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-setstencilreference + fn SetStencilReference(&self, reference: u32) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_set_stencil_reference(render_pass, reference); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-endpass + fn EndPass(&self) { + let render_pass = self.render_pass.borrow_mut().take(); + self.channel + .0 + .send(( + None, + WebGPURequest::RunRenderPass { + command_encoder_id: self.command_encoder.id().0, + render_pass, + }, + )) + .expect("Failed to send RunRenderPass"); + + self.command_encoder.set_state( + GPUCommandEncoderState::Open, + GPUCommandEncoderState::EncodingRenderPass, + ); + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setpipeline + fn SetPipeline(&self, pipeline: &GPURenderPipeline) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_set_pipeline(render_pass, pipeline.id().0); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setindexbuffer + fn SetIndexBuffer(&self, buffer: &GPUBuffer, offset: u64, size: u64) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_set_index_buffer( + render_pass, + buffer.id().0, + offset, + wgt::BufferSize::new(size), + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setvertexbuffer + fn SetVertexBuffer(&self, slot: u32, buffer: &GPUBuffer, offset: u64, size: u64) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_set_vertex_buffer( + render_pass, + slot, + buffer.id().0, + offset, + wgt::BufferSize::new(size), + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-draw + fn Draw(&self, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_draw( + render_pass, + vertex_count, + instance_count, + first_vertex, + first_instance, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexed + fn DrawIndexed( + &self, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, + ) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_draw_indexed( + render_pass, + index_count, + instance_count, + first_index, + base_vertex, + first_instance, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindirect + fn DrawIndirect(&self, indirect_buffer: &GPUBuffer, indirect_offset: u64) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_draw_indirect( + render_pass, + indirect_buffer.id().0, + indirect_offset, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexedindirect + fn DrawIndexedIndirect(&self, indirect_buffer: &GPUBuffer, indirect_offset: u64) { + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + wgpu_render::wgpu_render_pass_draw_indexed_indirect( + render_pass, + indirect_buffer.id().0, + indirect_offset, + ); + } + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-executebundles + #[allow(unsafe_code)] + fn ExecuteBundles(&self, bundles: Vec<DomRoot<GPURenderBundle>>) { + let bundle_ids = bundles.iter().map(|b| b.id().0).collect::<Vec<_>>(); + if let Some(render_pass) = self.render_pass.borrow_mut().as_mut() { + unsafe { + wgpu_render::wgpu_render_pass_execute_bundles( + render_pass, + bundle_ids.as_ptr(), + bundle_ids.len(), + ) + }; + } + } +} diff --git a/components/script/dom/gpurenderpipeline.rs b/components/script/dom/gpurenderpipeline.rs new file mode 100644 index 00000000000..9914b1d3114 --- /dev/null +++ b/components/script/dom/gpurenderpipeline.rs @@ -0,0 +1,90 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPURenderPipelineBinding::GPURenderPipelineMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; +use crate::dom::gpudevice::GPUDevice; +use dom_struct::dom_struct; +use std::string::String; +use webgpu::{WebGPUBindGroupLayout, WebGPURenderPipeline}; + +#[dom_struct] +pub struct GPURenderPipeline { + reflector_: Reflector, + label: DomRefCell<Option<USVString>>, + render_pipeline: WebGPURenderPipeline, + bind_group_layouts: Vec<WebGPUBindGroupLayout>, + device: Dom<GPUDevice>, +} + +impl GPURenderPipeline { + fn new_inherited( + render_pipeline: WebGPURenderPipeline, + label: Option<USVString>, + bgls: Vec<WebGPUBindGroupLayout>, + device: &GPUDevice, + ) -> Self { + Self { + reflector_: Reflector::new(), + label: DomRefCell::new(label), + render_pipeline, + bind_group_layouts: bgls, + device: Dom::from_ref(device), + } + } + + pub fn new( + global: &GlobalScope, + render_pipeline: WebGPURenderPipeline, + label: Option<USVString>, + bgls: Vec<WebGPUBindGroupLayout>, + device: &GPUDevice, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPURenderPipeline::new_inherited( + render_pipeline, + label, + bgls, + device, + )), + global, + ) + } +} + +impl GPURenderPipeline { + pub fn id(&self) -> WebGPURenderPipeline { + self.render_pipeline + } +} + +impl GPURenderPipelineMethods for GPURenderPipeline { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpupipelinebase-getbindgrouplayout + fn GetBindGroupLayout(&self, index: u32) -> Fallible<DomRoot<GPUBindGroupLayout>> { + if index > self.bind_group_layouts.len() as u32 { + return Err(Error::Range(String::from("Index out of bounds"))); + } + Ok(GPUBindGroupLayout::new( + &self.global(), + self.bind_group_layouts[index as usize], + None, + )) + } +} diff --git a/components/script/dom/gpusampler.rs b/components/script/dom/gpusampler.rs new file mode 100644 index 00000000000..23395ee058c --- /dev/null +++ b/components/script/dom/gpusampler.rs @@ -0,0 +1,74 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUSamplerBinding::GPUSamplerMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use webgpu::{WebGPUDevice, WebGPUSampler}; + +#[dom_struct] +pub struct GPUSampler { + reflector_: Reflector, + label: DomRefCell<Option<USVString>>, + device: WebGPUDevice, + compare_enable: bool, + sampler: WebGPUSampler, +} + +impl GPUSampler { + fn new_inherited( + device: WebGPUDevice, + compare_enable: bool, + sampler: WebGPUSampler, + label: Option<USVString>, + ) -> Self { + Self { + reflector_: Reflector::new(), + label: DomRefCell::new(label), + device, + sampler, + compare_enable, + } + } + + pub fn new( + global: &GlobalScope, + device: WebGPUDevice, + compare_enable: bool, + sampler: WebGPUSampler, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUSampler::new_inherited( + device, + compare_enable, + sampler, + label, + )), + global, + ) + } +} + +impl GPUSampler { + pub fn id(&self) -> WebGPUSampler { + self.sampler + } +} + +impl GPUSamplerMethods for GPUSampler { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/gpushadermodule.rs b/components/script/dom/gpushadermodule.rs new file mode 100644 index 00000000000..c4f20124ea8 --- /dev/null +++ b/components/script/dom/gpushadermodule.rs @@ -0,0 +1,58 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUShaderModuleBinding::GPUShaderModuleMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use webgpu::WebGPUShaderModule; + +#[dom_struct] +pub struct GPUShaderModule { + reflector_: Reflector, + label: DomRefCell<Option<USVString>>, + shader_module: WebGPUShaderModule, +} + +impl GPUShaderModule { + fn new_inherited(shader_module: WebGPUShaderModule, label: Option<USVString>) -> Self { + Self { + reflector_: Reflector::new(), + label: DomRefCell::new(label), + shader_module, + } + } + + pub fn new( + global: &GlobalScope, + shader_module: WebGPUShaderModule, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUShaderModule::new_inherited(shader_module, label)), + global, + ) + } +} + +impl GPUShaderModule { + pub fn id(&self) -> WebGPUShaderModule { + self.shader_module + } +} + +impl GPUShaderModuleMethods for GPUShaderModule { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/gpushaderstage.rs b/components/script/dom/gpushaderstage.rs new file mode 100644 index 00000000000..a9cd1d90589 --- /dev/null +++ b/components/script/dom/gpushaderstage.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::bindings::reflector::Reflector; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct GPUShaderStage { + reflector_: Reflector, +} diff --git a/components/script/dom/gpuswapchain.rs b/components/script/dom/gpuswapchain.rs new file mode 100644 index 00000000000..a60449ae616 --- /dev/null +++ b/components/script/dom/gpuswapchain.rs @@ -0,0 +1,95 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUSwapChainBinding::GPUSwapChainMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpucanvascontext::GPUCanvasContext; +use crate::dom::gputexture::GPUTexture; +use dom_struct::dom_struct; +use webgpu::{WebGPU, WebGPURequest, WebGPUTexture}; + +#[dom_struct] +pub struct GPUSwapChain { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + channel: WebGPU, + label: DomRefCell<Option<USVString>>, + context: Dom<GPUCanvasContext>, + texture: Dom<GPUTexture>, +} + +impl GPUSwapChain { + fn new_inherited( + channel: WebGPU, + context: &GPUCanvasContext, + texture: &GPUTexture, + label: Option<USVString>, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + context: Dom::from_ref(context), + texture: Dom::from_ref(texture), + label: DomRefCell::new(label), + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + context: &GPUCanvasContext, + texture: &GPUTexture, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUSwapChain::new_inherited( + channel, context, texture, label, + )), + global, + ) + } +} + +impl GPUSwapChain { + pub fn destroy(&self, external_id: u64, image_key: webrender_api::ImageKey) { + if let Err(e) = self.channel.0.send(( + None, + WebGPURequest::DestroySwapChain { + external_id, + image_key, + }, + )) { + warn!( + "Failed to send DestroySwapChain-ImageKey({:?}) ({})", + image_key, e + ); + } + } + + pub fn texture_id(&self) -> WebGPUTexture { + self.texture.id() + } +} + +impl GPUSwapChainMethods for GPUSwapChain { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuswapchain-getcurrenttexture + fn GetCurrentTexture(&self) -> DomRoot<GPUTexture> { + self.context.mark_as_dirty(); + DomRoot::from_ref(&*self.texture) + } +} diff --git a/components/script/dom/gputexture.rs b/components/script/dom/gputexture.rs new file mode 100644 index 00000000000..b473e52eaae --- /dev/null +++ b/components/script/dom/gputexture.rs @@ -0,0 +1,218 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUTextureBinding::{ + GPUExtent3DDict, GPUTextureDimension, GPUTextureFormat, GPUTextureMethods, +}; +use crate::dom::bindings::codegen::Bindings::GPUTextureViewBinding::{ + GPUTextureAspect, GPUTextureViewDescriptor, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpudevice::{ + convert_label, convert_texture_format, convert_texture_view_dimension, GPUDevice, +}; +use crate::dom::gputextureview::GPUTextureView; +use dom_struct::dom_struct; +use std::cell::Cell; +use std::num::NonZeroU32; +use std::string::String; +use webgpu::{ + identity::WebGPUOpResult, wgpu::resource, wgt, WebGPU, WebGPURequest, WebGPUTexture, + WebGPUTextureView, +}; + +#[dom_struct] +pub struct GPUTexture { + reflector_: Reflector, + texture: WebGPUTexture, + label: DomRefCell<Option<USVString>>, + device: Dom<GPUDevice>, + #[ignore_malloc_size_of = "channels are hard"] + channel: WebGPU, + #[ignore_malloc_size_of = "defined in webgpu"] + texture_size: GPUExtent3DDict, + mip_level_count: u32, + sample_count: u32, + dimension: GPUTextureDimension, + format: GPUTextureFormat, + texture_usage: u32, + destroyed: Cell<bool>, +} + +impl GPUTexture { + fn new_inherited( + texture: WebGPUTexture, + device: &GPUDevice, + channel: WebGPU, + texture_size: GPUExtent3DDict, + mip_level_count: u32, + sample_count: u32, + dimension: GPUTextureDimension, + format: GPUTextureFormat, + texture_usage: u32, + label: Option<USVString>, + ) -> Self { + Self { + reflector_: Reflector::new(), + texture, + label: DomRefCell::new(label), + device: Dom::from_ref(device), + channel, + texture_size, + mip_level_count, + sample_count, + dimension, + format, + texture_usage, + destroyed: Cell::new(false), + } + } + + pub fn new( + global: &GlobalScope, + texture: WebGPUTexture, + device: &GPUDevice, + channel: WebGPU, + texture_size: GPUExtent3DDict, + mip_level_count: u32, + sample_count: u32, + dimension: GPUTextureDimension, + format: GPUTextureFormat, + texture_usage: u32, + label: Option<USVString>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUTexture::new_inherited( + texture, + device, + channel, + texture_size, + mip_level_count, + sample_count, + dimension, + format, + texture_usage, + label, + )), + global, + ) + } +} + +impl Drop for GPUTexture { + fn drop(&mut self) { + self.Destroy() + } +} + +impl GPUTexture { + pub fn id(&self) -> WebGPUTexture { + self.texture + } +} + +impl GPUTextureMethods for GPUTexture { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } + + /// https://gpuweb.github.io/gpuweb/#dom-gputexture-createview + fn CreateView(&self, descriptor: &GPUTextureViewDescriptor) -> DomRoot<GPUTextureView> { + let scope_id = self.device.use_current_scope(); + let mut valid = true; + let level_count = descriptor.mipLevelCount.and_then(|count| { + if count == 0 { + valid = false; + } + NonZeroU32::new(count) + }); + let array_layer_count = descriptor.arrayLayerCount.and_then(|count| { + if count == 0 { + valid = false; + } + NonZeroU32::new(count) + }); + + let desc = if valid { + Some(resource::TextureViewDescriptor { + label: convert_label(&descriptor.parent), + format: descriptor.format.map(convert_texture_format), + dimension: descriptor.dimension.map(convert_texture_view_dimension), + aspect: match descriptor.aspect { + GPUTextureAspect::All => wgt::TextureAspect::All, + GPUTextureAspect::Stencil_only => wgt::TextureAspect::StencilOnly, + GPUTextureAspect::Depth_only => wgt::TextureAspect::DepthOnly, + }, + base_mip_level: descriptor.baseMipLevel, + level_count, + base_array_layer: descriptor.baseArrayLayer, + array_layer_count, + }) + } else { + self.device.handle_server_msg( + scope_id, + WebGPUOpResult::ValidationError(String::from( + "arrayLayerCount and mipLevelCount cannot be 0", + )), + ); + None + }; + + let texture_view_id = self + .global() + .wgpu_id_hub() + .lock() + .create_texture_view_id(self.device.id().0.backend()); + + self.channel + .0 + .send(( + scope_id, + WebGPURequest::CreateTextureView { + texture_id: self.texture.0, + texture_view_id, + device_id: self.device.id().0, + descriptor: desc, + }, + )) + .expect("Failed to create WebGPU texture view"); + + let texture_view = WebGPUTextureView(texture_view_id); + + GPUTextureView::new( + &self.global(), + texture_view, + &self, + descriptor.parent.label.as_ref().cloned(), + ) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gputexture-destroy + fn Destroy(&self) { + if self.destroyed.get() { + return; + } + if let Err(e) = self + .channel + .0 + .send((None, WebGPURequest::DestroyTexture(self.texture.0))) + { + warn!( + "Failed to send WebGPURequest::DestroyTexture({:?}) ({})", + self.texture.0, e + ); + }; + self.destroyed.set(true); + } +} diff --git a/components/script/dom/gputextureusage.rs b/components/script/dom/gputextureusage.rs new file mode 100644 index 00000000000..ae426bed3ba --- /dev/null +++ b/components/script/dom/gputextureusage.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::bindings::reflector::Reflector; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct GPUTextureUsage { + reflector_: Reflector, +} diff --git a/components/script/dom/gputextureview.rs b/components/script/dom/gputextureview.rs new file mode 100644 index 00000000000..af346d92250 --- /dev/null +++ b/components/script/dom/gputextureview.rs @@ -0,0 +1,66 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::GPUTextureViewBinding::GPUTextureViewMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gputexture::GPUTexture; +use dom_struct::dom_struct; +use webgpu::WebGPUTextureView; + +#[dom_struct] +pub struct GPUTextureView { + reflector_: Reflector, + label: DomRefCell<Option<USVString>>, + texture_view: WebGPUTextureView, + texture: Dom<GPUTexture>, +} + +impl GPUTextureView { + fn new_inherited( + texture_view: WebGPUTextureView, + texture: &GPUTexture, + label: Option<USVString>, + ) -> GPUTextureView { + Self { + reflector_: Reflector::new(), + texture: Dom::from_ref(texture), + label: DomRefCell::new(label), + texture_view, + } + } + + pub fn new( + global: &GlobalScope, + texture_view: WebGPUTextureView, + texture: &GPUTexture, + label: Option<USVString>, + ) -> DomRoot<GPUTextureView> { + reflect_dom_object( + Box::new(GPUTextureView::new_inherited(texture_view, texture, label)), + global, + ) + } +} + +impl GPUTextureView { + pub fn id(&self) -> WebGPUTextureView { + self.texture_view + } +} + +impl GPUTextureViewMethods for GPUTextureView { + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn GetLabel(&self) -> Option<USVString> { + self.label.borrow().clone() + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label + fn SetLabel(&self, value: Option<USVString>) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/gpuuncapturederrorevent.rs b/components/script/dom/gpuuncapturederrorevent.rs new file mode 100644 index 00000000000..30c9692a27d --- /dev/null +++ b/components/script/dom/gpuuncapturederrorevent.rs @@ -0,0 +1,84 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::GPUUncapturedErrorEventBinding::{ + GPUUncapturedErrorEventInit, GPUUncapturedErrorEventMethods, +}; +use crate::dom::bindings::codegen::Bindings::GPUValidationErrorBinding::GPUError; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct GPUUncapturedErrorEvent { + event: Event, + #[ignore_malloc_size_of = "Because it is non-owning"] + gpu_error: GPUError, +} + +impl GPUUncapturedErrorEvent { + fn new_inherited(init: &GPUUncapturedErrorEventInit) -> Self { + Self { + gpu_error: clone_gpu_error(&init.error), + event: Event::new_inherited(), + } + } + + pub fn new( + global: &GlobalScope, + type_: DOMString, + init: &GPUUncapturedErrorEventInit, + ) -> DomRoot<Self> { + let ev = reflect_dom_object( + Box::new(GPUUncapturedErrorEvent::new_inherited(init)), + global, + ); + ev.event.init_event( + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + ); + ev + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuuncapturederrorevent-gpuuncapturederrorevent + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + type_: DOMString, + init: &GPUUncapturedErrorEventInit, + ) -> DomRoot<Self> { + GPUUncapturedErrorEvent::new(global, type_, init) + } +} + +impl GPUUncapturedErrorEvent { + pub fn event(&self) -> &Event { + &self.event + } +} + +impl GPUUncapturedErrorEventMethods for GPUUncapturedErrorEvent { + /// https://gpuweb.github.io/gpuweb/#dom-gpuuncapturederrorevent-error + fn Error(&self) -> GPUError { + clone_gpu_error(&self.gpu_error) + } + + /// https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} + +fn clone_gpu_error(error: &GPUError) -> GPUError { + match *error { + GPUError::GPUValidationError(ref v) => GPUError::GPUValidationError(v.clone()), + GPUError::GPUOutOfMemoryError(ref w) => GPUError::GPUOutOfMemoryError(w.clone()), + } +} diff --git a/components/script/dom/gpuvalidationerror.rs b/components/script/dom/gpuvalidationerror.rs new file mode 100644 index 00000000000..4b0b6ec1627 --- /dev/null +++ b/components/script/dom/gpuvalidationerror.rs @@ -0,0 +1,42 @@ +/* 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 crate::dom::bindings::codegen::Bindings::GPUValidationErrorBinding::GPUValidationErrorMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct GPUValidationError { + reflector_: Reflector, + message: DOMString, +} + +impl GPUValidationError { + fn new_inherited(message: DOMString) -> Self { + Self { + reflector_: Reflector::new(), + message, + } + } + + pub fn new(global: &GlobalScope, message: DOMString) -> DomRoot<Self> { + reflect_dom_object(Box::new(GPUValidationError::new_inherited(message)), global) + } + + /// https://gpuweb.github.io/gpuweb/#dom-gpuvalidationerror-gpuvalidationerror + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope, message: DOMString) -> DomRoot<Self> { + GPUValidationError::new(global, message) + } +} + +impl GPUValidationErrorMethods for GPUValidationError { + /// https://gpuweb.github.io/gpuweb/#dom-gpuvalidationerror-message + fn Message(&self) -> DOMString { + self.message.clone() + } +} diff --git a/components/script/dom/hashchangeevent.rs b/components/script/dom/hashchangeevent.rs index 4505d79f440..046500b9b6d 100644 --- a/components/script/dom/hashchangeevent.rs +++ b/components/script/dom/hashchangeevent.rs @@ -1,17 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::HashChangeEventBinding; -use dom::bindings::codegen::Bindings::HashChangeEventBinding::HashChangeEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::{DOMString, USVString}; -use dom::event::Event; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::HashChangeEventBinding; +use crate::dom::bindings::codegen::Bindings::HashChangeEventBinding::HashChangeEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::event::Event; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; @@ -32,22 +32,25 @@ impl HashChangeEvent { } } - pub fn new_uninitialized(window: &Window) -> Root<HashChangeEvent> { - reflect_dom_object(box HashChangeEvent::new_inherited(String::new(), String::new()), - window, - HashChangeEventBinding::Wrap) + pub fn new_uninitialized(window: &Window) -> DomRoot<HashChangeEvent> { + reflect_dom_object( + Box::new(HashChangeEvent::new_inherited(String::new(), String::new())), + window, + ) } - pub fn new(window: &Window, - type_: Atom, - bubbles: bool, - cancelable: bool, - old_url: String, - new_url: String) - -> Root<HashChangeEvent> { - let ev = reflect_dom_object(box HashChangeEvent::new_inherited(old_url, new_url), - window, - HashChangeEventBinding::Wrap); + pub fn new( + window: &Window, + type_: Atom, + bubbles: bool, + cancelable: bool, + old_url: String, + new_url: String, + ) -> DomRoot<HashChangeEvent> { + let ev = reflect_dom_object( + Box::new(HashChangeEvent::new_inherited(old_url, new_url)), + window, + ); { let event = ev.upcast::<Event>(); event.init_event(type_, bubbles, cancelable); @@ -55,16 +58,20 @@ impl HashChangeEvent { ev } - pub fn Constructor(window: &Window, - type_: DOMString, - init: &HashChangeEventBinding::HashChangeEventInit) - -> Fallible<Root<HashChangeEvent>> { - Ok(HashChangeEvent::new(window, - Atom::from(type_), - init.parent.bubbles, - init.parent.cancelable, - init.oldURL.0.clone(), - init.newURL.0.clone())) + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &HashChangeEventBinding::HashChangeEventInit, + ) -> Fallible<DomRoot<HashChangeEvent>> { + Ok(HashChangeEvent::new( + window, + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + init.oldURL.0.clone(), + init.newURL.0.clone(), + )) } } diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index ac5b332b51b..03fecd79c5a 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -1,32 +1,32 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods, HeadersWrap}; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::iterable::Iterable; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::{ByteString, is_token}; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::iterable::Iterable; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::{is_token, ByteString}; +use crate::dom::globalscope::GlobalScope; +use data_url::mime::Mime as DataUrlMime; use dom_struct::dom_struct; -use hyper::header::Headers as HyperHeaders; -use mime::{Mime, TopLevel, SubLevel}; +use http::header::{HeaderMap as HyperHeaders, HeaderName, HeaderValue}; +use net_traits::request::is_cors_safelisted_request_header; use std::cell::Cell; -use std::result::Result; -use std::str; +use std::str::{self, FromStr}; #[dom_struct] pub struct Headers { reflector_: Reflector, guard: Cell<Guard>, - #[ignore_heap_size_of = "Defined in hyper"] - header_list: DOMRefCell<HyperHeaders> + #[ignore_malloc_size_of = "Defined in hyper"] + header_list: DomRefCell<HyperHeaders>, } // https://fetch.spec.whatwg.org/#concept-headers-guard -#[derive(Copy, Clone, JSTraceable, HeapSizeOf, PartialEq)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub enum Guard { Immutable, Request, @@ -40,19 +40,22 @@ impl Headers { Headers { reflector_: Reflector::new(), guard: Cell::new(Guard::None), - header_list: DOMRefCell::new(HyperHeaders::new()), + header_list: DomRefCell::new(HyperHeaders::new()), } } - pub fn new(global: &GlobalScope) -> Root<Headers> { - reflect_dom_object(box Headers::new_inherited(), global, HeadersWrap) + pub fn new(global: &GlobalScope) -> DomRoot<Headers> { + reflect_dom_object(Box::new(Headers::new_inherited()), global) } // https://fetch.spec.whatwg.org/#dom-headers - pub fn Constructor(global: &GlobalScope, init: Option<HeadersInit>) - -> Fallible<Root<Headers>> { + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + init: Option<HeadersInit>, + ) -> Fallible<DomRoot<Headers>> { let dom_headers_new = Headers::new(global); - try!(dom_headers_new.fill(init)); + dom_headers_new.fill(init)?; Ok(dom_headers_new) } } @@ -63,7 +66,7 @@ impl HeadersMethods for Headers { // Step 1 let value = normalize_value(value); // Step 2 - let (mut valid_name, valid_value) = try!(validate_name_and_value(name, value)); + let (mut valid_name, valid_value) = validate_name_and_value(name, value)?; valid_name = valid_name.to_lowercase(); // Step 3 if self.guard.get() == Guard::Immutable { @@ -74,7 +77,9 @@ impl HeadersMethods for Headers { return Ok(()); } // Step 5 - if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name, &valid_value) { + if self.guard.get() == Guard::RequestNoCors && + !is_cors_safelisted_request_header(&valid_name, &valid_value) + { return Ok(()); } // Step 6 @@ -82,20 +87,40 @@ impl HeadersMethods for Headers { return Ok(()); } // Step 7 + // FIXME: this is NOT what WHATWG says to do when appending + // another copy of an existing header. HyperHeaders + // might not expose the information we need to do it right. let mut combined_value: Vec<u8> = vec![]; - if let Some(v) = self.header_list.borrow().get_raw(&valid_name) { - combined_value = v[0].clone(); - combined_value.push(b','); + if let Some(v) = self + .header_list + .borrow() + .get(HeaderName::from_str(&valid_name).unwrap()) + { + combined_value = v.as_bytes().to_vec(); + combined_value.extend(b", "); } combined_value.extend(valid_value.iter().cloned()); - self.header_list.borrow_mut().set_raw(valid_name, vec![combined_value]); + match HeaderValue::from_bytes(&combined_value) { + Ok(value) => { + self.header_list + .borrow_mut() + .insert(HeaderName::from_str(&valid_name).unwrap(), value); + }, + Err(_) => { + // can't add the header, but we don't need to panic the browser over it + warn!( + "Servo thinks \"{:?}\" is a valid HTTP header value but HeaderValue doesn't.", + combined_value + ); + }, + }; Ok(()) } // https://fetch.spec.whatwg.org/#dom-headers-delete fn Delete(&self, name: ByteString) -> ErrorResult { // Step 1 - let valid_name = try!(validate_name(name)); + let valid_name = validate_name(name)?; // Step 2 if self.guard.get() == Guard::Immutable { return Err(Error::Type("Guard is immutable".to_string())); @@ -106,33 +131,36 @@ impl HeadersMethods for Headers { } // Step 4 if self.guard.get() == Guard::RequestNoCors && - !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec()) { - return Ok(()); - } + !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(()); } // Step 6 - self.header_list.borrow_mut().remove_raw(&valid_name); + self.header_list.borrow_mut().remove(&valid_name); Ok(()) } // https://fetch.spec.whatwg.org/#dom-headers-get fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> { // Step 1 - let valid_name = &try!(validate_name(name)); - Ok(self.header_list.borrow().get_raw(&valid_name).map(|v| { - ByteString::new(v[0].clone()) - })) + let valid_name = validate_name(name)?; + Ok(self + .header_list + .borrow() + .get(HeaderName::from_str(&valid_name).unwrap()) + .map(|v| ByteString::new(v.as_bytes().to_vec()))) } // https://fetch.spec.whatwg.org/#dom-headers-has fn Has(&self, name: ByteString) -> Fallible<bool> { // Step 1 - let valid_name = try!(validate_name(name)); + let valid_name = validate_name(name)?; // Step 2 - Ok(self.header_list.borrow_mut().get_raw(&valid_name).is_some()) + Ok(self.header_list.borrow_mut().get(&valid_name).is_some()) } // https://fetch.spec.whatwg.org/#dom-headers-set @@ -140,7 +168,7 @@ impl HeadersMethods for Headers { // Step 1 let value = normalize_value(value); // Step 2 - let (mut valid_name, valid_value) = try!(validate_name_and_value(name, value)); + let (mut valid_name, valid_value) = validate_name_and_value(name, value)?; valid_name = valid_name.to_lowercase(); // Step 3 if self.guard.get() == Guard::Immutable { @@ -151,7 +179,9 @@ impl HeadersMethods for Headers { return Ok(()); } // Step 5 - if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name, &valid_value) { + if self.guard.get() == Guard::RequestNoCors && + !is_cors_safelisted_request_header(&valid_name, &valid_value) + { return Ok(()); } // Step 6 @@ -160,32 +190,34 @@ impl HeadersMethods for Headers { } // Step 7 // https://fetch.spec.whatwg.org/#concept-header-list-set - self.header_list.borrow_mut().set_raw(valid_name, vec![valid_value]); + self.header_list.borrow_mut().insert( + HeaderName::from_str(&valid_name).unwrap(), + HeaderValue::from_bytes(&valid_value).unwrap(), + ); Ok(()) } } impl Headers { + pub fn copy_from_headers(&self, headers: DomRoot<Headers>) -> ErrorResult { + for (name, value) in headers.header_list.borrow().iter() { + self.Append( + ByteString::new(Vec::from(name.as_str())), + ByteString::new(Vec::from(value.as_bytes())), + )?; + } + Ok(()) + } + // https://fetch.spec.whatwg.org/#concept-headers-fill pub fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult { match filler { - // Step 1 - Some(HeadersInit::Headers(h)) => { - for header in h.header_list.borrow().iter() { - try!(self.Append( - ByteString::new(Vec::from(header.name())), - ByteString::new(Vec::from(header.value_string().into_bytes())) - )); - } - Ok(()) - }, - // Step 2 Some(HeadersInit::ByteStringSequenceSequence(v)) => { for mut seq in v { if seq.len() == 2 { let val = seq.pop().unwrap(); let name = seq.pop().unwrap(); - try!(self.Append(name, val)); + self.Append(name, val)?; } else { return Err(Error::Type( format!("Each header object must be a sequence of length 2 - found one with length {}", @@ -194,11 +226,9 @@ impl Headers { } Ok(()) }, - Some(HeadersInit::ByteStringMozMap(m)) => { + Some(HeadersInit::ByteStringByteStringRecord(m)) => { for (key, value) in m.iter() { - let key_vec = key.as_ref().to_string().into(); - let headers_key = ByteString::new(key_vec); - try!(self.Append(headers_key, value.clone())); + self.Append(key.clone(), value.clone())?; } Ok(()) }, @@ -206,13 +236,13 @@ impl Headers { } } - pub fn for_request(global: &GlobalScope) -> Root<Headers> { + pub fn for_request(global: &GlobalScope) -> DomRoot<Headers> { let headers_for_request = Headers::new(global); headers_for_request.guard.set(Guard::Request); headers_for_request } - pub fn for_response(global: &GlobalScope) -> Root<Headers> { + pub fn for_response(global: &GlobalScope) -> DomRoot<Headers> { let headers_for_response = Headers::new(global); headers_for_response.guard.set(Guard::Response); headers_for_response @@ -234,18 +264,22 @@ impl Headers { *self.header_list.borrow_mut() = hyper_headers; } + pub fn get_headers_list(&self) -> HyperHeaders { + self.header_list.borrow_mut().clone() + } + // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type pub fn extract_mime_type(&self) -> Vec<u8> { - self.header_list.borrow().get_raw("content-type").map_or(vec![], |v| v[0].clone()) + extract_mime_type(&*self.header_list.borrow()).unwrap_or(vec![]) } - pub fn sort_header_list(&self) -> Vec<(String, String)> { + pub fn sort_header_list(&self) -> Vec<(String, Vec<u8>)> { let borrowed_header_list = self.header_list.borrow(); let headers_iter = borrowed_header_list.iter(); let mut header_vec = vec![]; - for header in headers_iter { - let name = header.name().to_string(); - let value = header.value_string(); + for (name, value) in headers_iter { + let name = name.as_str().to_owned(); + let value = value.as_bytes().to_vec(); let name_value = (name, value); header_vec.push(name_value); } @@ -265,7 +299,7 @@ impl Iterable for Headers { fn get_value_at_index(&self, n: u32) -> ByteString { let sorted_header_vec = self.sort_header_list(); let value = sorted_header_vec[n as usize].1.clone(); - ByteString::new(value.into_bytes().to_vec()) + ByteString::new(value) } fn get_key_at_index(&self, n: u32) -> ByteString { @@ -275,94 +309,60 @@ impl Iterable for Headers { } } -fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool { - let value_string = if let Ok(s) = str::from_utf8(value) { - s - } else { - return false; - }; - let value_mime_result: Result<Mime, _> = value_string.parse(); - match value_mime_result { - Err(_) => false, - Ok(value_mime) => { - match value_mime { - Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _) | - Mime(TopLevel::Multipart, SubLevel::FormData, _) | - Mime(TopLevel::Text, SubLevel::Plain, _) => true, - _ => false, - } - } - } -} - -// TODO: "DPR", "Downlink", "Save-Data", "Viewport-Width", "Width": -// ... once parsed, the value should not be failure. -// https://fetch.spec.whatwg.org/#cors-safelisted-request-header -fn is_cors_safelisted_request_header(name: &str, value: &[u8]) -> bool { - match name { - "accept" | - "accept-language" | - "content-language" => true, - "content-type" => is_cors_safelisted_request_content_type(value), - _ => false, - } -} - // https://fetch.spec.whatwg.org/#forbidden-response-header-name fn is_forbidden_response_header(name: &str) -> bool { match name { - "set-cookie" | - "set-cookie2" => true, + "set-cookie" | "set-cookie2" => true, _ => false, } } // https://fetch.spec.whatwg.org/#forbidden-header-name pub fn is_forbidden_header_name(name: &str) -> bool { - let disallowed_headers = - ["accept-charset", "accept-encoding", - "access-control-request-headers", - "access-control-request-method", - "connection", "content-length", - "cookie", "cookie2", "date", "dnt", - "expect", "host", "keep-alive", "origin", - "referer", "te", "trailer", "transfer-encoding", - "upgrade", "via"]; + let disallowed_headers = [ + "accept-charset", + "accept-encoding", + "access-control-request-headers", + "access-control-request-method", + "connection", + "content-length", + "cookie", + "cookie2", + "date", + "dnt", + "expect", + "host", + "keep-alive", + "origin", + "referer", + "te", + "trailer", + "transfer-encoding", + "upgrade", + "via", + ]; let disallowed_header_prefixes = ["sec-", "proxy-"]; disallowed_headers.iter().any(|header| *header == name) || - disallowed_header_prefixes.iter().any(|prefix| name.starts_with(prefix)) + disallowed_header_prefixes + .iter() + .any(|prefix| name.starts_with(prefix)) } // There is some unresolved confusion over the definition of a name and a value. -// The fetch spec [1] defines a name as "a case-insensitive byte -// sequence that matches the field-name token production. The token -// productions are viewable in [2]." A field-name is defined as a -// token, which is defined in [3]. -// ISSUE 1: -// It defines a value as "a byte sequence that matches the field-content token production." -// To note, there is a difference between field-content and -// field-value (which is made up of field-content and obs-fold). The -// current definition does not allow for obs-fold (which are white -// space and newlines) in values. So perhaps a value should be defined -// as "a byte sequence that matches the field-value token production." -// However, this would then allow values made up entirely of white space and newlines. -// RELATED ISSUE 2: -// According to a previously filed Errata ID: 4189 in [4], "the -// specified field-value rule does not allow single field-vchar -// surrounded by whitespace anywhere". They provided a fix for the -// field-content production, but ISSUE 1 has still not been resolved. -// The production definitions likely need to be re-written. -// [1] https://fetch.spec.whatwg.org/#concept-header-value -// [2] https://tools.ietf.org/html/rfc7230#section-3.2 -// [3] https://tools.ietf.org/html/rfc7230#section-3.2.6 -// [4] https://www.rfc-editor.org/errata_search.php?rfc=7230 -fn validate_name_and_value(name: ByteString, value: ByteString) - -> Fallible<(String, Vec<u8>)> { - let valid_name = try!(validate_name(name)); - if !is_field_content(&value) { - return Err(Error::Type("Value is not valid".to_string())); +// +// 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())) } @@ -380,19 +380,22 @@ 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)) { + 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 { +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) { + if !is_http_whitespace(byte) { return Some(index); } } @@ -401,7 +404,7 @@ fn index_of_first_non_whitespace(value: &ByteString) -> Option<usize> { fn index_of_last_non_whitespace(value: &ByteString) -> Option<usize> { for (index, &byte) in value.iter().enumerate().rev() { - if !is_HTTP_whitespace(byte) { + if !is_http_whitespace(byte) { return Some(index); } } @@ -413,53 +416,46 @@ fn is_field_name(name: &ByteString) -> bool { is_token(&*name) } -// https://tools.ietf.org/html/rfc7230#section-3.2 -// http://www.rfc-editor.org/errata_search.php?rfc=7230 -// Errata ID: 4189 -// field-content = field-vchar [ 1*( SP / HTAB / field-vchar ) -// field-vchar ] -fn is_field_content(value: &ByteString) -> bool { +// https://fetch.spec.whatg.org/#concept-header-value +fn is_legal_header_value(value: &ByteString) -> bool { let value_len = value.len(); - if value_len == 0 { - return false; - } - if !is_field_vchar(value[0]) { - return false; + return true; } - - if value_len > 2 { - for &ch in &value[1..value_len - 1] { - if !is_field_vchar(ch) && !is_space(ch) && !is_htab(ch) { - return false; - } + match value[0] { + b' ' | b'\t' => return false, + _ => {}, + }; + match value[value_len - 1] { + b' ' | b'\t' => return false, + _ => {}, + }; + for &ch in &value[..] { + match ch { + b'\0' | b'\n' | b'\r' => return false, + _ => {}, } } - - if !is_field_vchar(value[value_len - 1]) { - return false; - } - - return true; -} - -fn is_space(x: u8) -> bool { - x == b' ' -} - -fn is_htab(x: u8) -> bool { - x == b'\t' -} - -// https://tools.ietf.org/html/rfc7230#section-3.2 -fn is_field_vchar(x: u8) -> bool { - is_vchar(x) || is_obs_text(x) + true + // If accepting non-UTF8 header values causes breakage, + // removing the above "true" and uncommenting the below code + // would ameliorate it while still accepting most reasonable headers: + //match str::from_utf8(value) { + // Ok(_) => true, + // Err(_) => { + // warn!( + // "Rejecting spec-legal but non-UTF8 header value: {:?}", + // value + // ); + // false + // }, + // } } // https://tools.ietf.org/html/rfc5234#appendix-B.1 pub fn is_vchar(x: u8) -> bool { match x { - 0x21...0x7E => true, + 0x21..=0x7E => true, _ => false, } } @@ -467,7 +463,76 @@ pub fn is_vchar(x: u8) -> bool { // http://tools.ietf.org/html/rfc7230#section-3.2.6 pub fn is_obs_text(x: u8) -> bool { match x { - 0x80...0xFF => true, + 0x80..=0xFF => true, _ => false, } } + +// 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 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()); + essence = temp_essence.to_owned(); + } 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 + return mime_type.map(|m| format!("{}", m).into_bytes()); +} diff --git a/components/script/dom/history.rs b/components/script/dom/history.rs index 537e9cbbb22..9023a397c66 100644 --- a/components/script/dom/history.rs +++ b/components/script/dom/history.rs @@ -1,66 +1,313 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HistoryBinding; -use dom::bindings::codegen::Bindings::HistoryBinding::HistoryMethods; -use dom::bindings::codegen::Bindings::LocationBinding::LocationBinding::LocationMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::HistoryBinding::HistoryMethods; +use crate::dom::bindings::codegen::Bindings::LocationBinding::LocationBinding::LocationMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::structuredclone; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::hashchangeevent::HashChangeEvent; +use crate::dom::popstateevent::PopStateEvent; +use crate::dom::window::Window; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use ipc_channel::ipc; -use msg::constellation_msg::TraversalDirection; -use script_traits::ScriptMsg as ConstellationMsg; +use js::jsapi::Heap; +use js::jsval::{JSVal, NullValue, UndefinedValue}; +use js::rust::HandleValue; +use msg::constellation_msg::{HistoryStateId, TraversalDirection}; +use net_traits::{CoreResourceMsg, IpcSend}; +use profile_traits::ipc; +use profile_traits::ipc::channel; +use script_traits::{ScriptMsg, StructuredSerializedData}; +use servo_url::ServoUrl; +use std::cell::Cell; + +enum PushOrReplace { + Push, + Replace, +} // https://html.spec.whatwg.org/multipage/#the-history-interface #[dom_struct] pub struct History { reflector_: Reflector, - window: JS<Window>, + window: Dom<Window>, + #[ignore_malloc_size_of = "mozjs"] + state: Heap<JSVal>, + state_id: Cell<Option<HistoryStateId>>, } impl History { pub fn new_inherited(window: &Window) -> History { + let state = Heap::default(); + state.set(NullValue()); History { reflector_: Reflector::new(), - window: JS::from_ref(&window), + window: Dom::from_ref(&window), + state: state, + state_id: Cell::new(None), } } - pub fn new(window: &Window) -> Root<History> { - reflect_dom_object(box History::new_inherited(window), - window, - HistoryBinding::Wrap) + pub fn new(window: &Window) -> DomRoot<History> { + reflect_dom_object(Box::new(History::new_inherited(window)), window) } } impl History { - fn traverse_history(&self, direction: TraversalDirection) { + fn traverse_history(&self, direction: TraversalDirection) -> ErrorResult { + if !self.window.Document().is_fully_active() { + return Err(Error::Security); + } + let msg = ScriptMsg::TraverseHistory(direction); + let _ = self + .window + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(msg); + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#history-traversal + // Steps 5-16 + #[allow(unsafe_code)] + pub fn activate_state(&self, state_id: Option<HistoryStateId>, url: ServoUrl) { + // Steps 5 + let document = self.window.Document(); + let old_url = document.url().clone(); + document.set_url(url.clone()); + + // Step 6 + let hash_changed = old_url.fragment() != url.fragment(); + + // Step 8 + if let Some(fragment) = url.fragment() { + document.check_and_scroll_fragment(fragment); + } + + // Step 11 + let state_changed = state_id != self.state_id.get(); + self.state_id.set(state_id); + let serialized_data = match state_id { + Some(state_id) => { + let (tx, rx) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); + let _ = self + .window + .upcast::<GlobalScope>() + .resource_threads() + .send(CoreResourceMsg::GetHistoryState(state_id, tx)); + rx.recv().unwrap() + }, + None => None, + }; + + match serialized_data { + Some(data) => { + let data = StructuredSerializedData { + serialized: data, + ports: None, + blobs: None, + }; + let global_scope = self.window.upcast::<GlobalScope>(); + rooted!(in(*global_scope.get_cx()) let mut state = UndefinedValue()); + if let Err(_) = structuredclone::read(&global_scope, data, state.handle_mut()) { + warn!("Error reading structuredclone data"); + } + self.state.set(state.get()); + }, + None => { + self.state.set(NullValue()); + }, + } + + // TODO: Queue events on DOM Manipulation task source if non-blocking flag is set. + // Step 16.1 + if state_changed { + PopStateEvent::dispatch_jsval( + self.window.upcast::<EventTarget>(), + &*self.window, + unsafe { HandleValue::from_raw(self.state.handle()) }, + ); + } + + // Step 16.3 + if hash_changed { + let event = HashChangeEvent::new( + &self.window, + atom!("hashchange"), + false, + false, + old_url.into_string(), + url.into_string(), + ); + event + .upcast::<Event>() + .fire(self.window.upcast::<EventTarget>()); + } + } + + pub fn remove_states(&self, states: Vec<HistoryStateId>) { + let _ = self + .window + .upcast::<GlobalScope>() + .resource_threads() + .send(CoreResourceMsg::RemoveHistoryStates(states)); + } + + // https://html.spec.whatwg.org/multipage/#dom-history-pushstate + // https://html.spec.whatwg.org/multipage/#dom-history-replacestate + fn push_or_replace_state( + &self, + cx: JSContext, + data: HandleValue, + _title: DOMString, + url: Option<USVString>, + push_or_replace: PushOrReplace, + ) -> ErrorResult { + // Step 1 + let document = self.window.Document(); + + // Step 2 + if !document.is_fully_active() { + return Err(Error::Security); + } + + // TODO: Step 3 Optionally abort these steps + // https://github.com/servo/servo/issues/19159 + + // TODO: Step 4 + + // Step 5 + let serialized_data = structuredclone::write(cx, data, None)?; + + let new_url: ServoUrl = match url { + // Step 6 + Some(urlstring) => { + let document_url = document.url(); + + // Step 6.1 + let new_url = match ServoUrl::parse_with_base(Some(&document_url), &urlstring.0) { + // Step 6.3 + Ok(parsed_url) => parsed_url, + // Step 6.2 + Err(_) => return Err(Error::Security), + }; + + // Step 6.4 + if new_url.scheme() != document_url.scheme() || + new_url.host() != document_url.host() || + new_url.port() != document_url.port() || + new_url.username() != document_url.username() || + new_url.password() != document_url.password() + { + return Err(Error::Security); + } + + // Step 6.5 + if new_url.origin() != document_url.origin() { + return Err(Error::Security); + } + + new_url + }, + // Step 7 + None => document.url(), + }; + + // Step 8 + let state_id = match push_or_replace { + PushOrReplace::Push => { + let state_id = HistoryStateId::new(); + self.state_id.set(Some(state_id)); + let msg = ScriptMsg::PushHistoryState(state_id, new_url.clone()); + let _ = self + .window + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(msg); + state_id + }, + PushOrReplace::Replace => { + let state_id = match self.state_id.get() { + Some(state_id) => state_id, + None => { + let state_id = HistoryStateId::new(); + self.state_id.set(Some(state_id)); + state_id + }, + }; + let msg = ScriptMsg::ReplaceHistoryState(state_id, new_url.clone()); + let _ = self + .window + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(msg); + state_id + }, + }; + + let _ = self.window.upcast::<GlobalScope>().resource_threads().send( + CoreResourceMsg::SetHistoryState(state_id, serialized_data.serialized.clone()), + ); + + // TODO: Step 9 Update current entry to represent a GET request + // https://github.com/servo/servo/issues/19156 + + // Step 10 + document.set_url(new_url); + + // Step 11 let global_scope = self.window.upcast::<GlobalScope>(); - let pipeline = global_scope.pipeline_id(); - let msg = ConstellationMsg::TraverseHistory(Some(pipeline), direction); - let _ = global_scope.constellation_chan().send(msg); + rooted!(in(*cx) let mut state = UndefinedValue()); + if let Err(_) = structuredclone::read(&global_scope, serialized_data, state.handle_mut()) { + warn!("Error reading structuredclone data"); + } + + // Step 12 + self.state.set(state.get()); + + // TODO: Step 13 Update Document's latest entry to current entry + // https://github.com/servo/servo/issues/19158 + + Ok(()) } } impl HistoryMethods for History { + // https://html.spec.whatwg.org/multipage/#dom-history-state + fn GetState(&self, _cx: JSContext) -> Fallible<JSVal> { + if !self.window.Document().is_fully_active() { + return Err(Error::Security); + } + Ok(self.state.get()) + } + // https://html.spec.whatwg.org/multipage/#dom-history-length - fn Length(&self) -> u32 { - let global_scope = self.window.upcast::<GlobalScope>(); - let pipeline = global_scope.pipeline_id(); - let (sender, recv) = ipc::channel().expect("Failed to create channel to send jsh length."); - let msg = ConstellationMsg::JointSessionHistoryLength(pipeline, sender); - let _ = global_scope.constellation_chan().send(msg); - recv.recv().unwrap() + fn GetLength(&self) -> Fallible<u32> { + if !self.window.Document().is_fully_active() { + return Err(Error::Security); + } + let (sender, recv) = channel(self.global().time_profiler_chan().clone()) + .expect("Failed to create channel to send jsh length."); + let msg = ScriptMsg::JointSessionHistoryLength(sender); + let _ = self + .window + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(msg); + Ok(recv.recv().unwrap()) } // https://html.spec.whatwg.org/multipage/#dom-history-go - fn Go(&self, delta: i32) -> Fallible<()> { + fn Go(&self, delta: i32) -> ErrorResult { let direction = if delta > 0 { TraversalDirection::Forward(delta as usize) } else if delta < 0 { @@ -69,17 +316,38 @@ impl HistoryMethods for History { return self.window.Location().Reload(); }; - self.traverse_history(direction); - Ok(()) + self.traverse_history(direction) } // https://html.spec.whatwg.org/multipage/#dom-history-back - fn Back(&self) { - self.traverse_history(TraversalDirection::Back(1)); + fn Back(&self) -> ErrorResult { + self.traverse_history(TraversalDirection::Back(1)) } // https://html.spec.whatwg.org/multipage/#dom-history-forward - fn Forward(&self) { - self.traverse_history(TraversalDirection::Forward(1)); + fn Forward(&self) -> ErrorResult { + self.traverse_history(TraversalDirection::Forward(1)) + } + + // https://html.spec.whatwg.org/multipage/#dom-history-pushstate + fn PushState( + &self, + cx: JSContext, + data: HandleValue, + title: DOMString, + url: Option<USVString>, + ) -> ErrorResult { + self.push_or_replace_state(cx, data, title, url, PushOrReplace::Push) + } + + // https://html.spec.whatwg.org/multipage/#dom-history-replacestate + fn ReplaceState( + &self, + cx: JSContext, + data: HandleValue, + title: DOMString, + url: Option<USVString>, + ) -> ErrorResult { + self.push_or_replace_state(cx, data, title, url, PushOrReplace::Replace) } } diff --git a/components/script/dom/htmlanchorelement.rs b/components/script/dom/htmlanchorelement.rs index a139afe0384..e6583329880 100644 --- a/components/script/dom/htmlanchorelement.rs +++ b/components/script/dom/htmlanchorelement.rs @@ -1,35 +1,38 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::activation::Activatable; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; -use dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods; -use dom::bindings::codegen::Bindings::HTMLAnchorElementBinding; -use dom::bindings::codegen::Bindings::HTMLAnchorElementBinding::HTMLAnchorElementMethods; -use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::{DOMString, USVString}; -use dom::document::Document; -use dom::domtokenlist::DOMTokenList; -use dom::element::Element; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::htmlelement::HTMLElement; -use dom::htmlimageelement::HTMLImageElement; -use dom::mouseevent::MouseEvent; -use dom::node::{Node, document_from_node}; -use dom::urlhelper::UrlHelper; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::activation::Activatable; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; +use crate::dom::bindings::codegen::Bindings::HTMLAnchorElementBinding::HTMLAnchorElementMethods; +use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::document::Document; +use crate::dom::domtokenlist::DOMTokenList; +use crate::dom::element::{referrer_policy_for_element, Element}; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlareaelement::HTMLAreaElement; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlformelement::HTMLFormElement; +use crate::dom::htmlimageelement::HTMLImageElement; +use crate::dom::mouseevent::MouseEvent; +use crate::dom::node::{document_from_node, Node}; +use crate::dom::urlhelper::UrlHelper; +use crate::dom::virtualmethods::VirtualMethods; +use crate::task_source::TaskSource; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use net_traits::ReferrerPolicy; +use html5ever::{LocalName, Prefix}; +use net_traits::request::Referrer; use num_traits::ToPrimitive; -use script_traits::MozBrowserEvent; -use servo_config::prefs::PREFS; +use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin}; +use servo_atoms::Atom; use servo_url::ServoUrl; use std::default::Default; use style::attr::AttrValue; @@ -37,34 +40,42 @@ use style::attr::AttrValue; #[dom_struct] pub struct HTMLAnchorElement { htmlelement: HTMLElement, - rel_list: MutNullableJS<DOMTokenList>, - url: DOMRefCell<Option<ServoUrl>>, + rel_list: MutNullableDom<DOMTokenList>, + url: DomRefCell<Option<ServoUrl>>, } impl HTMLAnchorElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLAnchorElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLAnchorElement { HTMLAnchorElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), rel_list: Default::default(), - url: DOMRefCell::new(None), + url: DomRefCell::new(None), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLAnchorElement> { - Node::reflect_node(box HTMLAnchorElement::new_inherited(local_name, prefix, document), - document, - HTMLAnchorElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLAnchorElement> { + Node::reflect_node( + Box::new(HTMLAnchorElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } // https://html.spec.whatwg.org/multipage/#concept-hyperlink-url-set fn set_url(&self) { - let attribute = self.upcast::<Element>().get_attribute(&ns!(), &local_name!("href")); + let attribute = self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("href")); *self.url.borrow_mut() = attribute.and_then(|attribute| { let document = document_from_node(self); document.base_url().join(&attribute.value()).ok() @@ -85,19 +96,23 @@ impl HTMLAnchorElement { // https://html.spec.whatwg.org/multipage/#update-href fn update_href(&self, url: DOMString) { - self.upcast::<Element>().set_string_attribute(&local_name!("href"), url); + self.upcast::<Element>() + .set_string_attribute(&local_name!("href"), url); } } impl VirtualMethods for HTMLAnchorElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match name { &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } } @@ -118,12 +133,23 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // https://html.spec.whatwg.org/multipage/#dom-a-rel fn SetRel(&self, rel: DOMString) { - self.upcast::<Element>().set_tokenlist_attribute(&local_name!("rel"), rel); + self.upcast::<Element>() + .set_tokenlist_attribute(&local_name!("rel"), rel); } // https://html.spec.whatwg.org/multipage/#dom-a-rellist - fn RelList(&self) -> Root<DOMTokenList> { - self.rel_list.or_init(|| DOMTokenList::new(self.upcast(), &local_name!("rel"))) + fn RelList(&self) -> DomRoot<DOMTokenList> { + self.rel_list.or_init(|| { + DOMTokenList::new( + self.upcast(), + &local_name!("rel"), + Some(vec![ + Atom::from("noopener"), + Atom::from("noreferrer"), + Atom::from("opener"), + ]), + ) + }) } // https://html.spec.whatwg.org/multipage/#dom-a-coords @@ -136,7 +162,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { make_getter!(Name, "name"); // https://html.spec.whatwg.org/multipage/#dom-a-name - make_setter!(SetName, "name"); + make_atomic_setter!(SetName, "name"); // https://html.spec.whatwg.org/multipage/#dom-a-rev make_getter!(Rev, "rev"); @@ -167,7 +193,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(ref url) => { // Steps 3-4. UrlHelper::Hash(url) - } + }, } } @@ -185,7 +211,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(url) => { UrlHelper::SetHash(url, value); DOMString::from(url.as_str()) - } + }, }; // Step 6. self.update_href(url); @@ -206,7 +232,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // Steps 4-5. UrlHelper::Host(url) } - } + }, } } @@ -224,7 +250,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(url) => { UrlHelper::SetHost(url, value); DOMString::from(url.as_str()) - } + }, }; // Step 5. self.update_href(url); @@ -241,7 +267,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(ref url) => { // Step 4. UrlHelper::Hostname(url) - } + }, } } @@ -259,7 +285,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(url) => { UrlHelper::SetHostname(url, value); DOMString::from(url.as_str()) - } + }, }; // Step 5. self.update_href(url); @@ -272,7 +298,10 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { USVString(match *self.url.borrow() { None => { - match self.upcast::<Element>().get_attribute(&ns!(), &local_name!("href")) { + match self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("href")) + { // Step 3. None => String::new(), // Step 4. @@ -286,8 +315,8 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // https://html.spec.whatwg.org/multipage/#dom-hyperlink-href fn SetHref(&self, value: USVString) { - self.upcast::<Element>().set_string_attribute(&local_name!("href"), - DOMString::from_string(value.0)); + self.upcast::<Element>() + .set_string_attribute(&local_name!("href"), DOMString::from_string(value.0)); self.set_url(); } @@ -303,7 +332,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { }, Some(ref url) => { // Step 3. - url.origin().unicode_serialization() + url.origin().ascii_serialization() }, }) } @@ -317,13 +346,13 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // Step 3. None => USVString(String::new()), // Steps 3-4. - Some(ref url) => UrlHelper::Password(url) + Some(ref url) => UrlHelper::Password(url), } } // https://html.spec.whatwg.org/multipage/#dom-hyperlink-password fn SetPassword(&self, value: USVString) { - // Step 1. + // Step 1. self.reinitialize_url(); // Step 2. @@ -335,7 +364,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(url) => { UrlHelper::SetPassword(url, value); DOMString::from(url.as_str()) - } + }, }; // Step 5. self.update_href(url); @@ -343,14 +372,14 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // https://html.spec.whatwg.org/multipage/#dom-hyperlink-pathname fn Pathname(&self) -> USVString { - // Step 1. + // Step 1. self.reinitialize_url(); match *self.url.borrow() { // Step 3. None => USVString(String::new()), // Steps 4-5. - Some(ref url) => UrlHelper::Pathname(url) + Some(ref url) => UrlHelper::Pathname(url), } } @@ -368,7 +397,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(url) => { UrlHelper::SetPathname(url, value); DOMString::from(url.as_str()) - } + }, }; // Step 6. self.update_href(url); @@ -383,7 +412,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // Step 3. None => USVString(String::new()), // Step 4. - Some(ref url) => UrlHelper::Port(url) + Some(ref url) => UrlHelper::Port(url), } } @@ -394,15 +423,17 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // Step 3. let url = match self.url.borrow_mut().as_mut() { - Some(ref url) if url.host().is_none() || - url.cannot_be_a_base() || - url.scheme() == "file" => return, + Some(ref url) + if url.host().is_none() || url.cannot_be_a_base() || url.scheme() == "file" => + { + return; + } None => return, // Step 4. Some(url) => { UrlHelper::SetPort(url, value); DOMString::from(url.as_str()) - } + }, }; // Step 5. self.update_href(url); @@ -417,7 +448,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // Step 2. None => USVString(":".to_owned()), // Step 3. - Some(ref url) => UrlHelper::Protocol(url) + Some(ref url) => UrlHelper::Protocol(url), } } @@ -433,7 +464,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(url) => { UrlHelper::SetProtocol(url, value); DOMString::from(url.as_str()) - } + }, }; // Step 4. self.update_href(url); @@ -448,7 +479,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // Step 2. None => USVString(String::new()), // Step 3. - Some(ref url) => UrlHelper::Search(url) + Some(ref url) => UrlHelper::Search(url), } } @@ -467,7 +498,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(url) => { UrlHelper::SetSearch(url, value); DOMString::from(url.as_str()) - } + }, }; // Step 6. self.update_href(url); @@ -482,7 +513,7 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { // Step 2. None => USVString(String::new()), // Step 3. - Some(ref url) => UrlHelper::Username(url) + Some(ref url) => UrlHelper::Username(url), } } @@ -500,16 +531,11 @@ impl HTMLAnchorElementMethods for HTMLAnchorElement { Some(url) => { UrlHelper::SetUsername(url, value); DOMString::from(url.as_str()) - } + }, }; // Step 5. self.update_href(url); } - - // https://html.spec.whatwg.org/multipage/#dom-hyperlink-href - fn Stringifier(&self) -> DOMString { - DOMString::from(self.Href().0) - } } impl Activatable for HTMLAnchorElement { @@ -523,103 +549,178 @@ impl Activatable for HTMLAnchorElement { // hyperlink" // https://html.spec.whatwg.org/multipage/#the-a-element // "The activation behaviour of a elements *that create hyperlinks*" - self.upcast::<Element>().has_attribute(&local_name!("href")) - } - - - //TODO:https://html.spec.whatwg.org/multipage/#the-a-element - fn pre_click_activation(&self) { - } - - //TODO:https://html.spec.whatwg.org/multipage/#the-a-element - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self) { + self.as_element().has_attribute(&local_name!("href")) } //https://html.spec.whatwg.org/multipage/#the-a-element:activation-behaviour fn activation_behavior(&self, event: &Event, target: &EventTarget) { - //Step 1. If the node document is not fully active, abort. - let doc = document_from_node(self); - if !doc.is_fully_active() { - return; - } - //TODO: Step 2. Check if browsing context is specified and act accordingly. - //Step 3. Handle <img ismap/>. - let element = self.upcast::<Element>(); + let element = self.as_element(); let mouse_event = event.downcast::<MouseEvent>().unwrap(); let mut ismap_suffix = None; + + // Step 1: If the target of the click event is an img element with an ismap attribute + // specified, then server-side image map processing must be performed. if let Some(element) = target.downcast::<Element>() { if target.is::<HTMLImageElement>() && element.has_attribute(&local_name!("ismap")) { let target_node = element.upcast::<Node>(); let rect = target_node.bounding_content_box_or_zero(); - ismap_suffix = Some( - format!("?{},{}", mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(), - mouse_event.ClientY().to_f32().unwrap() - rect.origin.y.to_f32_px()) - ) + ismap_suffix = Some(format!( + "?{},{}", + mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(), + mouse_event.ClientY().to_f32().unwrap() - rect.origin.y.to_f32_px() + )) } } - // Step 4. + // Step 2. //TODO: Download the link is `download` attribute is set. - - // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-delivery - let referrer_policy = match self.RelList().Contains("noreferrer".into()) { - true => Some(ReferrerPolicy::NoReferrer), - false => None, - }; - - follow_hyperlink(element, ismap_suffix, referrer_policy); - } - - //TODO:https://html.spec.whatwg.org/multipage/#the-a-element - fn implicit_submission(&self, _ctrl_key: bool, _shift_key: bool, _alt_key: bool, _meta_key: bool) { + follow_hyperlink(element, ismap_suffix); } } -/// https://html.spec.whatwg.org/multipage/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name -fn is_current_browsing_context(target: DOMString) -> bool { - target.is_empty() || target == "_self" +/// <https://html.spec.whatwg.org/multipage/#get-an-element's-target> +pub fn get_element_target(subject: &Element) -> Option<DOMString> { + if !(subject.is::<HTMLAreaElement>() || + subject.is::<HTMLAnchorElement>() || + subject.is::<HTMLFormElement>()) + { + return None; + } + if subject.has_attribute(&local_name!("target")) { + return Some(subject.get_string_attribute(&local_name!("target"))); + } + + let doc = document_from_node(subject).base_element(); + match doc { + Some(doc) => { + let element = doc.upcast::<Element>(); + if element.has_attribute(&local_name!("target")) { + return Some(element.get_string_attribute(&local_name!("target"))); + } else { + return None; + } + }, + None => return None, + }; } -/// https://html.spec.whatwg.org/multipage/#following-hyperlinks-2 -pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option<String>, referrer_policy: Option<ReferrerPolicy>) { - // Step 1: replace. - // Step 2: source browsing context. - // Step 3: target browsing context. - let target = subject.get_attribute(&ns!(), &local_name!("target")); - - // Step 4: disown target's opener if needed. - let attribute = subject.get_attribute(&ns!(), &local_name!("href")).unwrap(); - let mut href = attribute.Value(); +/// < https://html.spec.whatwg.org/multipage/#get-an-element's-noopener> +pub fn get_element_noopener(subject: &Element, target_attribute_value: Option<DOMString>) -> bool { + if !(subject.is::<HTMLAreaElement>() || + subject.is::<HTMLAnchorElement>() || + subject.is::<HTMLFormElement>()) + { + return false; + } + let target_is_blank = target_attribute_value + .as_ref() + .map_or(false, |target| target.to_lowercase() == "_blank"); + let link_types = match subject.get_attribute(&ns!(), &local_name!("rel")) { + Some(rel) => rel.Value(), + None => return target_is_blank, + }; + return link_types.contains("noreferrer") || + link_types.contains("noopener") || + (!link_types.contains("opener") && target_is_blank); +} - // Step 7: append a hyperlink suffix. - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28925 - if let Some(suffix) = hyperlink_suffix { - href.push_str(&suffix); +/// <https://html.spec.whatwg.org/multipage/#following-hyperlinks-2> +pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option<String>) { + // Step 1. + if subject.cannot_navigate() { + return; } + // Step 2, done in Step 7. - // Step 5: parse the URL. - // Step 6: navigate to an error document if parsing failed. let document = document_from_node(subject); - let url = match document.url().join(&href) { - Ok(url) => url, - Err(_) => return, + let window = document.window(); + + // Step 3: source browsing context. + let source = document.browsing_context().unwrap(); + + // Step 4-5: target attribute. + let target_attribute_value = + if subject.is::<HTMLAreaElement>() || subject.is::<HTMLAnchorElement>() { + get_element_target(subject) + } else { + None + }; + // Step 6. + let noopener = get_element_noopener(subject, target_attribute_value.clone()); + + // Step 7. + let (maybe_chosen, replace) = match target_attribute_value { + Some(name) => { + let (maybe_chosen, new) = source.choose_browsing_context(name, noopener); + let replace = if new { + HistoryEntryReplacement::Enabled + } else { + HistoryEntryReplacement::Disabled + }; + (maybe_chosen, replace) + }, + None => ( + Some(window.window_proxy()), + HistoryEntryReplacement::Disabled, + ), + }; + + // Step 8. + let chosen = match maybe_chosen { + Some(proxy) => proxy, + None => return, }; - // Step 8: navigate to the URL. - if let Some(target) = target { - if PREFS.is_mozbrowser_enabled() && !is_current_browsing_context(target.Value()) { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowseropenwindow - // TODO: referrer and opener - // TODO: should we send the normalized url or the non-normalized href? - let event = MozBrowserEvent::OpenWindow(url.into_string(), Some(String::from(target.Value())), None); - document.trigger_mozbrowser_event(event); - return + if let Some(target_document) = chosen.document() { + let target_window = target_document.window(); + // Step 9, dis-owning target's opener, if necessary + // will have been done as part of Step 7 above + // in choose_browsing_context/create_auxiliary_browsing_context. + + // Step 10, 11. TODO: if parsing the URL failed, navigate to error page. + let attribute = subject.get_attribute(&ns!(), &local_name!("href")).unwrap(); + let mut href = attribute.Value(); + // Step 11: append a hyperlink suffix. + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28925 + if let Some(suffix) = hyperlink_suffix { + href.push_str(&suffix); } - } + let url = match document.base_url().join(&href) { + Ok(url) => url, + Err(_) => return, + }; - debug!("following hyperlink to {}", url); + // Step 12. + let referrer_policy = referrer_policy_for_element(subject); - let window = document.window(); - window.load_url(url, false, false, referrer_policy); + // Step 13 + let referrer = match subject.get_attribute(&ns!(), &local_name!("rel")) { + Some(ref link_types) if link_types.Value().contains("noreferrer") => { + Referrer::NoReferrer + }, + _ => target_window.upcast::<GlobalScope>().get_referrer(), + }; + + // Step 14 + let pipeline_id = target_window.upcast::<GlobalScope>().pipeline_id(); + let secure = target_window.upcast::<GlobalScope>().is_secure_context(); + let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + url, + Some(pipeline_id), + referrer, + referrer_policy, + Some(secure), + ); + let target = Trusted::new(target_window); + let task = task!(navigate_follow_hyperlink: move || { + debug!("following hyperlink to {}", load_data.url); + target.root().load_url(replace, false, load_data); + }); + target_window + .task_manager() + .dom_manipulation_task_source() + .queue(task, target_window.upcast()) + .unwrap(); + }; } diff --git a/components/script/dom/htmlappletelement.rs b/components/script/dom/htmlappletelement.rs deleted file mode 100644 index 73de3061cfe..00000000000 --- a/components/script/dom/htmlappletelement.rs +++ /dev/null @@ -1,62 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HTMLAppletElementBinding; -use dom::bindings::codegen::Bindings::HTMLAppletElementBinding::HTMLAppletElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; -use dom::virtualmethods::VirtualMethods; -use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use style::attr::AttrValue; - -#[dom_struct] -pub struct HTMLAppletElement { - htmlelement: HTMLElement -} - -impl HTMLAppletElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLAppletElement { - HTMLAppletElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) - } - } - - #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLAppletElement> { - Node::reflect_node(box HTMLAppletElement::new_inherited(local_name, prefix, document), - document, - HTMLAppletElementBinding::Wrap) - } -} - -impl HTMLAppletElementMethods for HTMLAppletElement { - // https://html.spec.whatwg.org/multipage/#the-applet-element:dom-applet-name - make_getter!(Name, "name"); - - // https://html.spec.whatwg.org/multipage/#the-applet-element:dom-applet-name - make_atomic_setter!(SetName, "name"); -} - -impl VirtualMethods for HTMLAppletElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) - } - - fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { - match name { - &local_name!("name") => AttrValue::from_atomic(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), - } - } -} diff --git a/components/script/dom/htmlareaelement.rs b/components/script/dom/htmlareaelement.rs index 724deb2bdcd..ef85bb8791e 100644 --- a/components/script/dom/htmlareaelement.rs +++ b/components/script/dom/htmlareaelement.rs @@ -1,37 +1,44 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::activation::Activatable; -use dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods; -use dom::bindings::codegen::Bindings::HTMLAreaElementBinding; -use dom::bindings::codegen::Bindings::HTMLAreaElementBinding::HTMLAreaElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::domtokenlist::DOMTokenList; -use dom::element::Element; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::htmlanchorelement::follow_hyperlink; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, document_from_node}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::activation::Activatable; +use crate::dom::bindings::codegen::Bindings::HTMLAreaElementBinding::HTMLAreaElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::domtokenlist::DOMTokenList; +use crate::dom::element::Element; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlanchorelement::follow_hyperlink; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use euclid::point::Point2D; -use html5ever_atoms::LocalName; -use net_traits::ReferrerPolicy; +use euclid::default::Point2D; +use html5ever::{LocalName, Prefix}; +use servo_atoms::Atom; use std::default::Default; use std::f32; +use std::str; use style::attr::AttrValue; -#[derive(PartialEq)] -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Area { - Circle { left: f32, top: f32, radius: f32 }, - Rectangle { top_left: (f32, f32), bottom_right: (f32, f32) }, - Polygon { points: Vec<f32> }, + Circle { + left: f32, + top: f32, + radius: f32, + }, + Rectangle { + top_left: (f32, f32), + bottom_right: (f32, f32), + }, + Polygon { + points: Vec<f32>, + }, } pub enum Shape { @@ -44,7 +51,7 @@ pub enum Shape { // https://html.spec.whatwg.org/multipage/#image-map-processing-model impl Area { pub fn parse(coord: &str, target: Shape) -> Option<Area> { - let points_count = match target { + let points_count = match target { Shape::Circle => 3, Shape::Rectangle => 4, Shape::Polygon => 0, @@ -58,7 +65,7 @@ impl Area { while index < size { let val = num[index]; match val { - b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => {}, + b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => {}, _ => break, } @@ -68,7 +75,6 @@ impl Area { //This vector will hold all parsed coordinates let mut number_list = Vec::new(); let mut array = Vec::new(); - let ar_ref = &mut array; // Step 5: walk till end of string while index < size { @@ -76,7 +82,7 @@ impl Area { while index < size { let val = num[index]; match val { - b'0'...b'9' | b'.' | b'-' | b'E' | b'e' => break, + b'0'..=b'9' | b'.' | b'-' | b'E' | b'e' => break, _ => {}, } @@ -89,24 +95,27 @@ impl Area { match val { b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => break, - _ => (*ar_ref).push(val), + _ => array.push(val), } index += 1; } // The input does not consist any valid charecters - if (*ar_ref).is_empty() { + if array.is_empty() { break; } // Convert String to float - match String::from_utf8((*ar_ref).clone()).unwrap().parse::<f32>() { - Ok(v) => number_list.push(v), - Err(_) => number_list.push(0.0), + match str::from_utf8(&array) + .ok() + .and_then(|s| s.parse::<f32>().ok()) + { + Some(v) => number_list.push(v), + None => number_list.push(0.0), }; - (*ar_ref).clear(); + array.clear(); // For rectangle and circle, stop parsing once we have three // and four coordinates respectively @@ -124,10 +133,10 @@ impl Area { None } else { Some(Area::Circle { - left: number_list[0], - top: number_list[1], - radius: number_list[2] - }) + left: number_list[0], + top: number_list[1], + radius: number_list[2], + }) } } else { None @@ -145,9 +154,9 @@ impl Area { } Some(Area::Rectangle { - top_left: (number_list[0], number_list[1]), - bottom_right: (number_list[2], number_list[3]) - }) + top_left: (number_list[0], number_list[1]), + bottom_right: (number_list[2], number_list[3]), + }) } else { None } @@ -159,7 +168,9 @@ impl Area { // Drop last element if there are odd number of coordinates number_list.remove(final_size - 1); } - Some(Area::Polygon { points: number_list }) + Some(Area::Polygon { + points: number_list, + }) } else { None } @@ -167,17 +178,20 @@ impl Area { } } - pub fn hit_test(&self, p: Point2D<f32>) -> bool { + pub fn hit_test(&self, p: &Point2D<f32>) -> bool { match *self { Area::Circle { left, top, radius } => { - (p.x - left) * (p.x - left) + - (p.y - top) * (p.y - top) - - radius * radius <= 0.0 + (p.x - left) * (p.x - left) + (p.y - top) * (p.y - top) - radius * radius <= 0.0 }, - Area::Rectangle { top_left, bottom_right } => { - p.x <= bottom_right.0 && p.x >= top_left.0 && - p.y <= bottom_right.1 && p.y >= top_left.1 + Area::Rectangle { + top_left, + bottom_right, + } => { + p.x <= bottom_right.0 && + p.x >= top_left.0 && + p.y <= bottom_right.1 && + p.y >= top_left.1 }, //TODO polygon hit_test @@ -187,28 +201,30 @@ impl Area { pub fn absolute_coords(&self, p: Point2D<f32>) -> Area { match *self { - Area::Rectangle { top_left, bottom_right } => { - Area::Rectangle { - top_left: (top_left.0 + p.x, top_left.1 + p.y), - bottom_right: (bottom_right.0 + p.x, bottom_right.1 + p.y) - } + Area::Rectangle { + top_left, + bottom_right, + } => Area::Rectangle { + top_left: (top_left.0 + p.x, top_left.1 + p.y), + bottom_right: (bottom_right.0 + p.x, bottom_right.1 + p.y), }, - Area::Circle { left, top, radius } => { - Area::Circle { - left: (left + p.x), - top: (top + p.y), - radius: radius - } + Area::Circle { left, top, radius } => Area::Circle { + left: (left + p.x), + top: (top + p.y), + radius: radius, }, Area::Polygon { ref points } => { -// let new_points = Vec::new(); - let iter = points.iter().enumerate().map(|(index, point)| { - match index % 2 { + // let new_points = Vec::new(); + let iter = points + .iter() + .enumerate() + .map(|(index, point)| match index % 2 { 0 => point + p.x as f32, _ => point + p.y as f32, - } - }); - Area::Polygon { points: iter.collect::<Vec<_>>() } + }); + Area::Polygon { + points: iter.collect::<Vec<_>>(), + } }, } } @@ -217,11 +233,15 @@ impl Area { #[dom_struct] pub struct HTMLAreaElement { htmlelement: HTMLElement, - rel_list: MutNullableJS<DOMTokenList>, + rel_list: MutNullableDom<DOMTokenList>, } impl HTMLAreaElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLAreaElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLAreaElement { HTMLAreaElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), rel_list: Default::default(), @@ -229,18 +249,21 @@ impl HTMLAreaElement { } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLAreaElement> { - Node::reflect_node(box HTMLAreaElement::new_inherited(local_name, prefix, document), - document, - HTMLAreaElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLAreaElement> { + Node::reflect_node( + Box::new(HTMLAreaElement::new_inherited(local_name, prefix, document)), + document, + ) } pub fn get_shape_from_coords(&self) -> Option<Area> { - let elem = self.upcast::<Element>(); - let shape = elem.get_string_attribute(&"shape".into()); - let shp: Shape = match shape.to_lowercase().as_ref() { + let elem = self.upcast::<Element>(); + let shape = elem.get_string_attribute(&"shape".into()); + let shp: Shape = match_ignore_ascii_case! { &shape, "circle" => Shape::Circle, "circ" => Shape::Circle, "rectangle" => Shape::Rectangle, @@ -259,23 +282,49 @@ impl HTMLAreaElement { } impl VirtualMethods for HTMLAreaElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match name { &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } } impl HTMLAreaElementMethods for HTMLAreaElement { + // https://html.spec.whatwg.org/multipage/#attr-hyperlink-target + make_getter!(Target, "target"); + + // https://html.spec.whatwg.org/multipage/#attr-hyperlink-target + make_setter!(SetTarget, "target"); + + // https://html.spec.whatwg.org/multipage/#dom-a-rel + make_getter!(Rel, "rel"); + + // https://html.spec.whatwg.org/multipage/#dom-a-rel + fn SetRel(&self, rel: DOMString) { + self.upcast::<Element>() + .set_tokenlist_attribute(&local_name!("rel"), rel); + } + // https://html.spec.whatwg.org/multipage/#dom-area-rellist - fn RelList(&self) -> Root<DOMTokenList> { + fn RelList(&self) -> DomRoot<DOMTokenList> { self.rel_list.or_init(|| { - DOMTokenList::new(self.upcast(), &local_name!("rel")) + DOMTokenList::new( + self.upcast(), + &local_name!("rel"), + Some(vec![ + Atom::from("noopener"), + Atom::from("noreferrer"), + Atom::from("opener"), + ]), + ) }) } } @@ -290,29 +339,7 @@ impl Activatable for HTMLAreaElement { self.as_element().has_attribute(&local_name!("href")) } - fn pre_click_activation(&self) { - } - - fn canceled_activation(&self) { - } - - fn implicit_submission(&self, _ctrl_key: bool, _shift_key: bool, - _alt_key: bool, _meta_key: bool) { - } - fn activation_behavior(&self, _event: &Event, _target: &EventTarget) { - // Step 1 - let doc = document_from_node(self); - if !doc.is_fully_active() { - return; - } - // Step 2 - // TODO : We should be choosing a browsing context and navigating to it. - // Step 3 - let referrer_policy = match self.RelList().Contains("noreferrer".into()) { - true => Some(ReferrerPolicy::NoReferrer), - false => None, - }; - follow_hyperlink(self.upcast::<Element>(), None, referrer_policy); + follow_hyperlink(self.as_element(), None); } } diff --git a/components/script/dom/htmlaudioelement.rs b/components/script/dom/htmlaudioelement.rs index bb8b12f06d6..db8f1877a6d 100644 --- a/components/script/dom/htmlaudioelement.rs +++ b/components/script/dom/htmlaudioelement.rs @@ -1,37 +1,75 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HTMLAudioElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlmediaelement::HTMLMediaElement; -use dom::node::Node; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator}; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::node::Node; +use crate::dom::window::Window; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix, QualName}; #[dom_struct] pub struct HTMLAudioElement { - htmlmediaelement: HTMLMediaElement + htmlmediaelement: HTMLMediaElement, } impl HTMLAudioElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLAudioElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLAudioElement { HTMLAudioElement { - htmlmediaelement: - HTMLMediaElement::new_inherited(local_name, prefix, document) + htmlmediaelement: HTMLMediaElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLAudioElement> { - Node::reflect_node(box HTMLAudioElement::new_inherited(local_name, prefix, document), - document, - HTMLAudioElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLAudioElement> { + Node::reflect_node( + Box::new(HTMLAudioElement::new_inherited( + local_name, prefix, document, + )), + document, + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-audio + #[allow(non_snake_case)] + pub fn Audio(window: &Window, src: Option<DOMString>) -> Fallible<DomRoot<HTMLAudioElement>> { + let element = Element::create( + QualName::new(None, ns!(html), local_name!("audio")), + None, + &window.Document(), + ElementCreator::ScriptCreated, + CustomElementCreationMode::Synchronous, + ); + + let audio = DomRoot::downcast::<HTMLAudioElement>(element).unwrap(); + + audio + .upcast::<Element>() + .SetAttribute(DOMString::from("preload"), DOMString::from("auto")) + .expect("should be infallible"); + if let Some(s) = src { + audio + .upcast::<Element>() + .SetAttribute(DOMString::from("src"), s) + .expect("should be infallible"); + } + + Ok(audio) } } diff --git a/components/script/dom/htmlbaseelement.rs b/components/script/dom/htmlbaseelement.rs index fe34e6d77f0..e6568716e1f 100644 --- a/components/script/dom/htmlbaseelement.rs +++ b/components/script/dom/htmlbaseelement.rs @@ -1,49 +1,58 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::HTMLBaseElementBinding; -use dom::bindings::codegen::Bindings::HTMLBaseElementBinding::HTMLBaseElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, UnbindContext, document_from_node}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLBaseElementBinding::HTMLBaseElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{document_from_node, BindContext, Node, UnbindContext}; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use servo_url::ServoUrl; -use style::attr::AttrValue; #[dom_struct] pub struct HTMLBaseElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLBaseElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLBaseElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLBaseElement { HTMLBaseElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLBaseElement> { - Node::reflect_node(box HTMLBaseElement::new_inherited(local_name, prefix, document), - document, - HTMLBaseElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLBaseElement> { + Node::reflect_node( + Box::new(HTMLBaseElement::new_inherited(local_name, prefix, document)), + document, + ) } - /// https://html.spec.whatwg.org/multipage/#frozen-base-url + /// <https://html.spec.whatwg.org/multipage/#frozen-base-url> pub fn frozen_base_url(&self) -> ServoUrl { - let href = self.upcast::<Element>().get_attribute(&ns!(), &local_name!("href")) - .expect("The frozen base url is only defined for base elements \ - that have a base url."); + let href = self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("href")) + .expect( + "The frozen base url is only defined for base elements \ + that have a base url.", + ); let document = document_from_node(self); let base = document.fallback_base_url(); let parsed = base.join(&href.value()); @@ -53,7 +62,7 @@ impl HTMLBaseElement { /// Update the cached base element in response to binding or unbinding from /// a tree. pub fn bind_unbind(&self, tree_in_doc: bool) { - if !tree_in_doc { + if !tree_in_doc || self.upcast::<Node>().containing_shadow_root().is_some() { return; } @@ -67,33 +76,38 @@ impl HTMLBaseElement { impl HTMLBaseElementMethods for HTMLBaseElement { // https://html.spec.whatwg.org/multipage/#dom-base-href fn Href(&self) -> DOMString { - let document = document_from_node(self); - // Step 1. - if !self.upcast::<Element>().has_attribute(&local_name!("href")) { - return DOMString::from(document.base_url().as_str()); - } + let document = document_from_node(self); // Step 2. - let fallback_base_url = document.fallback_base_url(); + let attr = self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("href")); + let value = attr.as_ref().map(|attr| attr.value()); + let url = value.as_ref().map_or("", |value| &**value); // Step 3. - let url = self.upcast::<Element>().get_url_attribute(&local_name!("href")); - - // Step 4. - let url_record = fallback_base_url.join(&*url); - - // Step 5, 6. - DOMString::from(url_record.as_ref().map(|url| url.as_str()).unwrap_or("")) + let url_record = document.fallback_base_url().join(url); + + match url_record { + Err(_) => { + // Step 4. + url.into() + }, + Ok(url_record) => { + // Step 5. + url_record.into_string().into() + }, + } } // https://html.spec.whatwg.org/multipage/#dom-base-href - make_url_setter!(SetHref, "href"); + make_setter!(SetHref, "href"); } impl VirtualMethods for HTMLBaseElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -103,9 +117,9 @@ impl VirtualMethods for HTMLBaseElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { - self.super_type().unwrap().bind_to_tree(tree_in_doc); - self.bind_unbind(tree_in_doc); + fn bind_to_tree(&self, context: &BindContext) { + self.super_type().unwrap().bind_to_tree(context); + self.bind_unbind(context.tree_in_doc); } fn unbind_from_tree(&self, context: &UnbindContext) { diff --git a/components/script/dom/htmlbodyelement.rs b/components/script/dom/htmlbodyelement.rs index ef2855fa512..775af6a686c 100644 --- a/components/script/dom/htmlbodyelement.rs +++ b/components/script/dom/htmlbodyelement.rs @@ -1,28 +1,25 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLBodyElementBinding::HTMLBodyElementMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{document_from_node, window_from_node, BindContext, Node}; +use crate::dom::virtualmethods::VirtualMethods; use cssparser::RGBA; -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::EventHandlerBinding::{EventHandlerNonNull, OnBeforeUnloadEventHandlerNonNull}; -use dom::bindings::codegen::Bindings::HTMLBodyElementBinding::{self, HTMLBodyElementMethods}; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, document_from_node, window_from_node}; -use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use script_traits::ScriptMsg as ConstellationMsg; +use embedder_traits::EmbedderMsg; +use html5ever::{LocalName, Prefix}; use servo_url::ServoUrl; use style::attr::AttrValue; -use time; /// How long we should wait before performing the initial reflow after `<body>` is parsed, in /// nanoseconds. @@ -34,30 +31,38 @@ pub struct HTMLBodyElement { } impl HTMLBodyElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) - -> HTMLBodyElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLBodyElement { HTMLBodyElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, prefix: Option<DOMString>, document: &Document) - -> Root<HTMLBodyElement> { - Node::reflect_node(box HTMLBodyElement::new_inherited(local_name, prefix, document), - document, - HTMLBodyElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLBodyElement> { + Node::reflect_node( + Box::new(HTMLBodyElement::new_inherited(local_name, prefix, document)), + document, + ) } - /// https://drafts.csswg.org/cssom-view/#the-html-body-element + /// <https://drafts.csswg.org/cssom-view/#the-html-body-element> pub fn is_the_html_body_element(&self) -> bool { let self_node = self.upcast::<Node>(); let root_elem = self.upcast::<Element>().root_element(); let root_node = root_elem.upcast::<Node>(); root_node.is_parent_of(self_node) && - self_node.preceding_siblings().all(|n| !n.is::<HTMLBodyElement>()) + self_node + .preceding_siblings() + .all(|n| !n.is::<HTMLBodyElement>()) } - } impl HTMLBodyElementMethods for HTMLBodyElement { @@ -77,79 +82,91 @@ impl HTMLBodyElementMethods for HTMLBodyElement { make_getter!(Background, "background"); // https://html.spec.whatwg.org/multipage/#dom-body-background - make_url_setter!(SetBackground, "background"); + fn SetBackground(&self, input: DOMString) { + let value = + AttrValue::from_resolved_url(&document_from_node(self).base_url(), input.into()); + self.upcast::<Element>() + .set_attribute(&local_name!("background"), value); + } // https://html.spec.whatwg.org/multipage/#windoweventhandlers window_event_handlers!(ForwardToWindow); } pub trait HTMLBodyElementLayoutHelpers { - fn get_background_color(&self) -> Option<RGBA>; - fn get_color(&self) -> Option<RGBA>; - fn get_background(&self) -> Option<ServoUrl>; + fn get_background_color(self) -> Option<RGBA>; + fn get_color(self) -> Option<RGBA>; + fn get_background(self) -> Option<ServoUrl>; } -impl HTMLBodyElementLayoutHelpers for LayoutJS<HTMLBodyElement> { - #[allow(unsafe_code)] - fn get_background_color(&self) -> Option<RGBA> { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) - .and_then(AttrValue::as_color) - .cloned() - } +impl HTMLBodyElementLayoutHelpers for LayoutDom<'_, HTMLBodyElement> { + fn get_background_color(self) -> Option<RGBA> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) + .and_then(AttrValue::as_color) + .cloned() } - #[allow(unsafe_code)] - fn get_color(&self) -> Option<RGBA> { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("text")) - .and_then(AttrValue::as_color) - .cloned() - } + fn get_color(self) -> Option<RGBA> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("text")) + .and_then(AttrValue::as_color) + .cloned() } - #[allow(unsafe_code)] - fn get_background(&self) -> Option<ServoUrl> { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("background")) - .and_then(AttrValue::as_url) - .cloned() - } + fn get_background(self) -> Option<ServoUrl> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("background")) + .and_then(AttrValue::as_resolved_url) + .cloned() } } impl VirtualMethods for HTMLBodyElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool { + if attr.local_name() == &local_name!("bgcolor") { + return true; + } + + self.super_type() + .unwrap() + .attribute_affects_presentational_hints(attr) + } + + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - if !tree_in_doc { - return + if !context.tree_in_doc { + return; } let window = window_from_node(self); let document = window.Document(); document.set_reflow_timeout(time::precise_time_ns() + INITIAL_REFLOW_DELAY); - let event = ConstellationMsg::HeadParsed; - window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap(); + if window.is_top_level() { + let msg = EmbedderMsg::HeadParsed; + window.send_to_embedder(msg); + } } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match *name { - local_name!("bgcolor") | - local_name!("text") => AttrValue::from_legacy_color(value.into()), + local_name!("bgcolor") | local_name!("text") => { + AttrValue::from_legacy_color(value.into()) + }, local_name!("background") => { - AttrValue::from_url(document_from_node(self).url(), value.into()) + AttrValue::from_resolved_url(&document_from_node(self).base_url(), value.into()) }, - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } @@ -160,23 +177,34 @@ impl VirtualMethods for HTMLBodyElement { // https://html.spec.whatwg.org/multipage/ // #event-handlers-on-elements,-document-objects,-and-window-objects:event-handlers-3 match name { - &local_name!("onfocus") | &local_name!("onload") | &local_name!("onscroll") | - &local_name!("onafterprint") | &local_name!("onbeforeprint") | - &local_name!("onbeforeunload") | &local_name!("onhashchange") | - &local_name!("onlanguagechange") | &local_name!("onmessage") | - &local_name!("onoffline") | &local_name!("ononline") | - &local_name!("onpagehide") | &local_name!("onpageshow") | - &local_name!("onpopstate") | &local_name!("onstorage") | - &local_name!("onresize") | &local_name!("onunload") | &local_name!("onerror") - => { - let evtarget = window.upcast::<EventTarget>(); // forwarded event - let source_line = 1; //TODO(#9604) obtain current JS execution line - evtarget.set_event_handler_uncompiled(window.get_url(), - source_line, - &name[2..], - DOMString::from((**attr.value()).to_owned())); - false - } + &local_name!("onfocus") | + &local_name!("onload") | + &local_name!("onscroll") | + &local_name!("onafterprint") | + &local_name!("onbeforeprint") | + &local_name!("onbeforeunload") | + &local_name!("onhashchange") | + &local_name!("onlanguagechange") | + &local_name!("onmessage") | + &local_name!("onoffline") | + &local_name!("ononline") | + &local_name!("onpagehide") | + &local_name!("onpageshow") | + &local_name!("onpopstate") | + &local_name!("onstorage") | + &local_name!("onresize") | + &local_name!("onunload") | + &local_name!("onerror") => { + let evtarget = window.upcast::<EventTarget>(); // forwarded event + let source_line = 1; //TODO(#9604) obtain current JS execution line + evtarget.set_event_handler_uncompiled( + window.get_url(), + source_line, + &name[2..], + DOMString::from((**attr.value()).to_owned()), + ); + false + }, _ => true, // HTMLElement::attribute_mutated will take care of this. } }, diff --git a/components/script/dom/htmlbrelement.rs b/components/script/dom/htmlbrelement.rs index ebe6ed43706..d087dfffb02 100644 --- a/components/script/dom/htmlbrelement.rs +++ b/components/script/dom/htmlbrelement.rs @@ -1,15 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLBRElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLBRElement { @@ -17,18 +15,25 @@ pub struct HTMLBRElement { } impl HTMLBRElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLBRElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLBRElement { HTMLBRElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLBRElement> { - Node::reflect_node(box HTMLBRElement::new_inherited(local_name, prefix, document), - document, - HTMLBRElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLBRElement> { + Node::reflect_node( + Box::new(HTMLBRElement::new_inherited(local_name, prefix, document)), + document, + ) } } diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index b36b276bfd7..9e9898f328c 100755 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -1,80 +1,90 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::activation::{Activatable, ActivationSource, synthetic_click_activation}; -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::HTMLButtonElementBinding; -use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlformelement::{FormControl, FormDatum, FormDatumValue}; -use dom::htmlformelement::{FormSubmitter, ResetFrom, SubmittedFrom}; -use dom::htmlformelement::HTMLFormElement; -use dom::node::{Node, UnbindContext, document_from_node, window_from_node}; -use dom::nodelist::NodeList; -use dom::validation::Validatable; -use dom::validitystate::{ValidityState, ValidationFlags}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::activation::Activatable; +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlformelement::HTMLFormElement; +use crate::dom::htmlformelement::{FormControl, FormDatum, FormDatumValue}; +use crate::dom::htmlformelement::{FormSubmitter, ResetFrom, SubmittedFrom}; +use crate::dom::node::{window_from_node, BindContext, Node, UnbindContext}; +use crate::dom::nodelist::NodeList; +use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable}; +use crate::dom::validitystate::ValidityState; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use std::cell::Cell; use std::default::Default; -use style::element_state::*; +use style::element_state::ElementState; -#[derive(JSTraceable, PartialEq, Copy, Clone)] -#[derive(HeapSizeOf)] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] enum ButtonType { Submit, Reset, Button, - Menu } #[dom_struct] pub struct HTMLButtonElement { htmlelement: HTMLElement, button_type: Cell<ButtonType>, - form_owner: MutNullableJS<HTMLFormElement>, + form_owner: MutNullableDom<HTMLFormElement>, + labels_node_list: MutNullableDom<NodeList>, + validity_state: MutNullableDom<ValidityState>, } impl HTMLButtonElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLButtonElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLButtonElement { HTMLButtonElement { - htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, - local_name, prefix, document), + htmlelement: HTMLElement::new_inherited_with_state( + ElementState::IN_ENABLED_STATE, + local_name, + prefix, + document, + ), button_type: Cell::new(ButtonType::Submit), form_owner: Default::default(), + labels_node_list: Default::default(), + validity_state: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLButtonElement> { - Node::reflect_node(box HTMLButtonElement::new_inherited(local_name, prefix, document), - document, - HTMLButtonElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLButtonElement> { + Node::reflect_node( + Box::new(HTMLButtonElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } -} -impl HTMLButtonElementMethods for HTMLButtonElement { - // https://html.spec.whatwg.org/multipage/#dom-cva-validity - fn Validity(&self) -> Root<ValidityState> { - let window = window_from_node(self); - ValidityState::new(&window, self.upcast()) + #[inline] + pub fn is_submit_button(&self) -> bool { + self.button_type.get() == ButtonType::Submit } +} +impl HTMLButtonElementMethods for HTMLButtonElement { // https://html.spec.whatwg.org/multipage/#dom-fe-disabled make_bool_getter!(Disabled, "disabled"); @@ -82,27 +92,29 @@ impl HTMLButtonElementMethods for HTMLButtonElement { make_bool_setter!(SetDisabled, "disabled"); // https://html.spec.whatwg.org/multipage/#dom-fae-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner() } // https://html.spec.whatwg.org/multipage/#dom-button-type - make_enumerated_getter!(Type, "type", "submit", "reset" | "button" | "menu"); + make_enumerated_getter!(Type, "type", "submit", "reset" | "button"); // https://html.spec.whatwg.org/multipage/#dom-button-type make_setter!(SetType, "type"); // https://html.spec.whatwg.org/multipage/#dom-fs-formaction - make_url_or_base_getter!(FormAction, "formaction"); + make_form_action_getter!(FormAction, "formaction"); // https://html.spec.whatwg.org/multipage/#dom-fs-formaction make_setter!(SetFormAction, "formaction"); // https://html.spec.whatwg.org/multipage/#dom-fs-formenctype - make_enumerated_getter!(FormEnctype, - "formenctype", - "application/x-www-form-urlencoded", - "text/plain" | "multipart/form-data"); + make_enumerated_getter!( + FormEnctype, + "formenctype", + "application/x-www-form-urlencoded", + "text/plain" | "multipart/form-data" + ); // https://html.spec.whatwg.org/multipage/#dom-fs-formenctype make_setter!(SetFormEnctype, "formenctype"); @@ -129,7 +141,7 @@ impl HTMLButtonElementMethods for HTMLButtonElement { make_getter!(Name, "name"); // https://html.spec.whatwg.org/multipage/#dom-fe-name - make_setter!(SetName, "name"); + make_atomic_setter!(SetName, "name"); // https://html.spec.whatwg.org/multipage/#dom-button-value make_getter!(Value, "value"); @@ -138,13 +150,41 @@ impl HTMLButtonElementMethods for HTMLButtonElement { make_setter!(SetValue, "value"); // https://html.spec.whatwg.org/multipage/#dom-lfe-labels - fn Labels(&self) -> Root<NodeList> { - self.upcast::<HTMLElement>().labels() + make_labels_getter!(Labels, labels_node_list); + + // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate + fn WillValidate(&self) -> bool { + self.is_instance_validatable() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validity + fn Validity(&self) -> DomRoot<ValidityState> { + self.validity_state() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity + fn CheckValidity(&self) -> bool { + self.check_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity + fn ReportValidity(&self) -> bool { + self.report_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage + fn ValidationMessage(&self) -> DOMString { + self.validation_message() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity + fn SetCustomValidity(&self, error: DOMString) { + self.validity_state().set_custom_error_message(error); } } impl HTMLButtonElement { - /// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set + /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set> /// Steps range from 3.1 to 3.7 (specific to HTMLButtonElement) pub fn form_datum(&self, submitter: Option<FormSubmitter>) -> Option<FormDatum> { // Step 3.1: disabled state check is in get_unclean_dataset @@ -152,10 +192,10 @@ impl HTMLButtonElement { // Step 3.1: only run steps if this is the submitter if let Some(FormSubmitter::ButtonElement(submitter)) = submitter { if submitter != self { - return None + return None; } } else { - return None + return None; } // Step 3.2 let ty = self.Type(); @@ -171,14 +211,14 @@ impl HTMLButtonElement { Some(FormDatum { ty: ty, name: name, - value: FormDatumValue::String(self.Value()) + value: FormDatumValue::String(self.Value()), }) } } impl VirtualMethods for HTMLButtonElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -187,7 +227,7 @@ impl VirtualMethods for HTMLButtonElement { &local_name!("disabled") => { let el = self.upcast::<Element>(); match mutation { - AttributeMutation::Set(Some(_)) => {} + AttributeMutation::Set(Some(_)) => {}, AttributeMutation::Set(None) => { el.set_disabled_state(true); el.set_enabled_state(false); @@ -196,38 +236,37 @@ impl VirtualMethods for HTMLButtonElement { el.set_disabled_state(false); el.set_enabled_state(true); el.check_ancestors_disabled_state_for_form_control(); - } + }, } + el.update_sequentially_focusable_status(); }, - &local_name!("type") => { - match mutation { - AttributeMutation::Set(_) => { - let value = match &**attr.value() { - "reset" => ButtonType::Reset, - "button" => ButtonType::Button, - "menu" => ButtonType::Menu, - _ => ButtonType::Submit, - }; - self.button_type.set(value); - } - AttributeMutation::Removed => { - self.button_type.set(ButtonType::Submit); - } - } + &local_name!("type") => match mutation { + AttributeMutation::Set(_) => { + let value = match &**attr.value() { + "reset" => ButtonType::Reset, + "button" => ButtonType::Button, + _ => ButtonType::Submit, + }; + self.button_type.set(value); + }, + AttributeMutation::Removed => { + self.button_type.set(ButtonType::Submit); + }, }, &local_name!("form") => { self.form_attribute_mutated(mutation); - } + }, _ => {}, } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - self.upcast::<Element>().check_ancestors_disabled_state_for_form_control(); + self.upcast::<Element>() + .check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { @@ -235,7 +274,10 @@ impl VirtualMethods for HTMLButtonElement { let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); - if node.ancestors().any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) { + if node + .ancestors() + .any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) + { el.check_ancestors_disabled_state_for_form_control(); } else { el.check_disabled_attribute(); @@ -244,7 +286,7 @@ impl VirtualMethods for HTMLButtonElement { } impl FormControl for HTMLButtonElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } @@ -258,13 +300,22 @@ impl FormControl for HTMLButtonElement { } impl Validatable for HTMLButtonElement { - fn is_instance_validatable(&self) -> bool { - true + fn as_element(&self) -> &Element { + self.upcast() } - fn validate(&self, validate_flags: ValidationFlags) -> bool { - if validate_flags.is_empty() {} - // Need more flag check for different validation types later - true + + fn validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), self.upcast())) + } + + fn is_instance_validatable(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#the-button-element%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation + self.button_type.get() == ButtonType::Submit && + !self.upcast::<Element>().disabled_state() && + !is_barred_by_datalist_ancestor(self.upcast()) } } @@ -278,15 +329,6 @@ impl Activatable for HTMLButtonElement { !self.upcast::<Element>().disabled_state() } - // https://html.spec.whatwg.org/multipage/#run-pre-click-activation-steps - // https://html.spec.whatwg.org/multipage/#the-button-element:activation-behavior - fn pre_click_activation(&self) { - } - - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self) { - } - // https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps fn activation_behavior(&self, _event: &Event, _target: &EventTarget) { let ty = self.button_type.get(); @@ -295,37 +337,19 @@ impl Activatable for HTMLButtonElement { ButtonType::Submit => { // TODO: is document owner fully active? if let Some(owner) = self.form_owner() { - owner.submit(SubmittedFrom::NotFromForm, - FormSubmitter::ButtonElement(self.clone())); + owner.submit( + SubmittedFrom::NotFromForm, + FormSubmitter::ButtonElement(self), + ); } - } + }, ButtonType::Reset => { // TODO: is document owner fully active? if let Some(owner) = self.form_owner() { owner.reset(ResetFrom::NotFromForm); } - } + }, _ => (), } } - - // https://html.spec.whatwg.org/multipage/#implicit-submission - #[allow(unsafe_code)] - fn implicit_submission(&self, ctrl_key: bool, shift_key: bool, alt_key: bool, meta_key: bool) { - let doc = document_from_node(self); - let node = doc.upcast::<Node>(); - let owner = self.form_owner(); - if owner.is_none() || self.upcast::<Element>().click_in_progress() { - return; - } - node.query_selector_iter(DOMString::from("button[type=submit]")).unwrap() - .filter_map(Root::downcast::<HTMLButtonElement>) - .find(|r| r.form_owner() == owner) - .map(|s| synthetic_click_activation(s.as_element(), - ctrl_key, - shift_key, - alt_key, - meta_key, - ActivationSource::NotFromClick)); - } } diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 577537b421f..67531761ce7 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -1,90 +1,114 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::{ref_filter_map, DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::{ + HTMLCanvasElementMethods, RenderingContext, +}; +use crate::dom::bindings::codegen::Bindings::MediaStreamBinding::MediaStreamMethods; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes; +use crate::dom::bindings::conversions::ConversionResult; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::canvasrenderingcontext2d::{ + CanvasRenderingContext2D, LayoutCanvasRenderingContext2DHelpers, +}; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpucanvascontext::GPUCanvasContext; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::mediastream::MediaStream; +use crate::dom::mediastreamtrack::MediaStreamTrack; +use crate::dom::node::{window_from_node, Node}; +use crate::dom::virtualmethods::VirtualMethods; +use crate::dom::webgl2renderingcontext::WebGL2RenderingContext; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::script_runtime::JSContext; use base64; -use canvas_traits::{CanvasMsg, FromScriptMsg}; -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods; -use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding; -use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::HTMLCanvasElementMethods; -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes; -use dom::bindings::codegen::UnionTypes::CanvasRenderingContext2DOrWebGLRenderingContext; -use dom::bindings::conversions::ConversionResult; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, LayoutJS, Root}; -use dom::bindings::num::Finite; -use dom::bindings::str::DOMString; -use dom::canvasrenderingcontext2d::{CanvasRenderingContext2D, LayoutCanvasRenderingContext2DHelpers}; -use dom::document::Document; -use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, window_from_node}; -use dom::virtualmethods::VirtualMethods; -use dom::webglrenderingcontext::{LayoutCanvasWebGLRenderingContextHelpers, WebGLRenderingContext}; +use canvas_traits::canvas::{CanvasId, CanvasMsg, FromScriptMsg}; +use canvas_traits::webgl::{GLContextAttributes, WebGLVersion}; use dom_struct::dom_struct; -use euclid::size::Size2D; -use html5ever_atoms::LocalName; +use euclid::default::{Rect, Size2D}; +use html5ever::{LocalName, Prefix}; +use image::png::PngEncoder; use image::ColorType; -use image::png::PNGEncoder; -use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::ipc::{self as ipcchan, IpcSharedMemory}; use js::error::throw_type_error; -use js::jsapi::{HandleValue, JSContext}; -use offscreen_gl_context::GLContextAttributes; -use script_layout_interface::HTMLCanvasData; -use std::iter::repeat; +use js::rust::HandleValue; +use profile_traits::ipc; +use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource}; +use script_traits::ScriptMsg; +use servo_media::streams::registry::MediaStreamId; +use servo_media::streams::MediaStreamType; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; const DEFAULT_WIDTH: u32 = 300; const DEFAULT_HEIGHT: u32 = 150; -#[must_root] -#[derive(JSTraceable, Clone, HeapSizeOf)] +#[unrooted_must_root_lint::must_root] +#[derive(Clone, JSTraceable, MallocSizeOf)] pub enum CanvasContext { - Context2d(JS<CanvasRenderingContext2D>), - WebGL(JS<WebGLRenderingContext>), + Context2d(Dom<CanvasRenderingContext2D>), + WebGL(Dom<WebGLRenderingContext>), + WebGL2(Dom<WebGL2RenderingContext>), + WebGPU(Dom<GPUCanvasContext>), } #[dom_struct] pub struct HTMLCanvasElement { htmlelement: HTMLElement, - context: DOMRefCell<Option<CanvasContext>>, + context: DomRefCell<Option<CanvasContext>>, } impl HTMLCanvasElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLCanvasElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLCanvasElement { HTMLCanvasElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), - context: DOMRefCell::new(None), + context: DomRefCell::new(None), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLCanvasElement> { - Node::reflect_node(box HTMLCanvasElement::new_inherited(local_name, prefix, document), - document, - HTMLCanvasElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLCanvasElement> { + Node::reflect_node( + Box::new(HTMLCanvasElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } fn recreate_contexts(&self) { let size = self.get_size(); if let Some(ref context) = *self.context.borrow() { match *context { - CanvasContext::Context2d(ref context) => context.set_bitmap_dimensions(size), + CanvasContext::Context2d(ref context) => { + context.set_canvas_bitmap_dimensions(size.to_u64()) + }, CanvasContext::WebGL(ref context) => context.recreate(size), + CanvasContext::WebGL2(ref context) => context.recreate(size), + CanvasContext::WebGPU(_) => unimplemented!(), } } } - pub fn get_size(&self) -> Size2D<i32> { - Size2D::new(self.Width() as i32, self.Height() as i32) + pub fn get_size(&self) -> Size2D<u32> { + Size2D::new(self.Width(), self.Height()) } pub fn origin_is_clean(&self) -> bool { @@ -95,118 +119,185 @@ impl HTMLCanvasElement { } } +pub trait LayoutCanvasRenderingContextHelpers { + #[allow(unsafe_code)] + unsafe fn canvas_data_source(self) -> HTMLCanvasDataSource; +} + pub trait LayoutHTMLCanvasElementHelpers { - fn data(&self) -> HTMLCanvasData; - fn get_width(&self) -> LengthOrPercentageOrAuto; - fn get_height(&self) -> LengthOrPercentageOrAuto; + fn data(self) -> HTMLCanvasData; + fn get_width(self) -> LengthOrPercentageOrAuto; + fn get_height(self) -> LengthOrPercentageOrAuto; + fn get_canvas_id_for_layout(self) -> CanvasId; } -impl LayoutHTMLCanvasElementHelpers for LayoutJS<HTMLCanvasElement> { +impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { #[allow(unsafe_code)] - fn data(&self) -> HTMLCanvasData { - unsafe { - let canvas = &*self.unsafe_get(); - let ipc_renderer = canvas.context.borrow_for_layout().as_ref().map(|context| { - match *context { - CanvasContext::Context2d(ref context) => { - context.to_layout().get_ipc_renderer() - }, - CanvasContext::WebGL(ref context) => { - context.to_layout().get_ipc_renderer() - }, - } - }); - - let width_attr = canvas.upcast::<Element>().get_attr_for_layout(&ns!(), &local_name!("width")); - let height_attr = canvas.upcast::<Element>().get_attr_for_layout(&ns!(), &local_name!("height")); - HTMLCanvasData { - ipc_renderer: ipc_renderer, - width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()), - height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()), + fn data(self) -> HTMLCanvasData { + let source = unsafe { + match self.unsafe_get().context.borrow_for_layout().as_ref() { + Some(&CanvasContext::Context2d(ref context)) => { + HTMLCanvasDataSource::Image(Some(context.to_layout().get_ipc_renderer())) + }, + Some(&CanvasContext::WebGL(ref context)) => { + context.to_layout().canvas_data_source() + }, + Some(&CanvasContext::WebGL2(ref context)) => { + context.to_layout().canvas_data_source() + }, + Some(&CanvasContext::WebGPU(ref context)) => { + context.to_layout().canvas_data_source() + }, + None => HTMLCanvasDataSource::Image(None), } + }; + + let width_attr = self + .upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("width")); + let height_attr = self + .upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("height")); + HTMLCanvasData { + source: source, + width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()), + height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()), + canvas_id: self.get_canvas_id_for_layout(), } } - #[allow(unsafe_code)] - fn get_width(&self) -> LengthOrPercentageOrAuto { - unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("width")) - .map(AttrValue::as_uint_px_dimension) - .unwrap_or(LengthOrPercentageOrAuto::Auto) - } + fn get_width(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("width")) + .map(AttrValue::as_uint_px_dimension) + .unwrap_or(LengthOrPercentageOrAuto::Auto) + } + + fn get_height(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("height")) + .map(AttrValue::as_uint_px_dimension) + .unwrap_or(LengthOrPercentageOrAuto::Auto) } #[allow(unsafe_code)] - fn get_height(&self) -> LengthOrPercentageOrAuto { + fn get_canvas_id_for_layout(self) -> CanvasId { unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("height")) - .map(AttrValue::as_uint_px_dimension) - .unwrap_or(LengthOrPercentageOrAuto::Auto) + let canvas = &*self.unsafe_get(); + if let &Some(CanvasContext::Context2d(ref context)) = canvas.context.borrow_for_layout() + { + context.to_layout().get_canvas_id() + } else { + CanvasId(0) + } } } } - impl HTMLCanvasElement { - pub fn ipc_renderer(&self) -> Option<IpcSender<CanvasMsg>> { - self.context.borrow().as_ref().map(|context| { - match *context { - CanvasContext::Context2d(ref context) => context.ipc_renderer(), - CanvasContext::WebGL(ref context) => context.ipc_renderer(), - } - }) + pub fn context(&self) -> Option<Ref<CanvasContext>> { + ref_filter_map(self.context.borrow(), |ctx| ctx.as_ref()) } - pub fn get_or_init_2d_context(&self) -> Option<Root<CanvasRenderingContext2D>> { - if self.context.borrow().is_none() { - let window = window_from_node(self); - let size = self.get_size(); - let context = CanvasRenderingContext2D::new(window.upcast::<GlobalScope>(), self, size); - *self.context.borrow_mut() = Some(CanvasContext::Context2d(JS::from_ref(&*context))); + fn get_or_init_2d_context(&self) -> Option<DomRoot<CanvasRenderingContext2D>> { + if let Some(ctx) = self.context() { + return match *ctx { + CanvasContext::Context2d(ref ctx) => Some(DomRoot::from_ref(ctx)), + _ => None, + }; } + let window = window_from_node(self); + let size = self.get_size(); + let context = CanvasRenderingContext2D::new(window.upcast::<GlobalScope>(), self, size); + *self.context.borrow_mut() = Some(CanvasContext::Context2d(Dom::from_ref(&*context))); + Some(context) + } - match *self.context.borrow().as_ref().unwrap() { - CanvasContext::Context2d(ref context) => Some(Root::from_ref(&*context)), - _ => None, + fn get_or_init_webgl_context( + &self, + cx: JSContext, + options: HandleValue, + ) -> Option<DomRoot<WebGLRenderingContext>> { + if let Some(ctx) = self.context() { + return match *ctx { + CanvasContext::WebGL(ref ctx) => Some(DomRoot::from_ref(ctx)), + _ => None, + }; } + let window = window_from_node(self); + let size = self.get_size(); + let attrs = Self::get_gl_attributes(cx, options)?; + let context = WebGLRenderingContext::new(&window, self, WebGLVersion::WebGL1, size, attrs)?; + *self.context.borrow_mut() = Some(CanvasContext::WebGL(Dom::from_ref(&*context))); + Some(context) } - #[allow(unsafe_code)] - pub fn get_or_init_webgl_context(&self, - cx: *mut JSContext, - attrs: Option<HandleValue>) -> Option<Root<WebGLRenderingContext>> { - if self.context.borrow().is_none() { - let window = window_from_node(self); - let size = self.get_size(); - - let attrs = if let Some(webgl_attributes) = attrs { - match unsafe { - WebGLContextAttributes::new(cx, webgl_attributes) } { - Ok(ConversionResult::Success(ref attrs)) => From::from(attrs), - Ok(ConversionResult::Failure(ref error)) => { - unsafe { throw_type_error(cx, &error); } - return None; - } - _ => { - debug!("Unexpected error on conversion of WebGLContextAttributes"); - return None; - } - } - } else { - GLContextAttributes::default() + fn get_or_init_webgl2_context( + &self, + cx: JSContext, + options: HandleValue, + ) -> Option<DomRoot<WebGL2RenderingContext>> { + if !WebGL2RenderingContext::is_webgl2_enabled(cx, self.global().reflector().get_jsobject()) + { + return None; + } + if let Some(ctx) = self.context() { + return match *ctx { + CanvasContext::WebGL2(ref ctx) => Some(DomRoot::from_ref(ctx)), + _ => None, }; + } + let window = window_from_node(self); + let size = self.get_size(); + let attrs = Self::get_gl_attributes(cx, options)?; + let context = WebGL2RenderingContext::new(&window, self, size, attrs)?; + *self.context.borrow_mut() = Some(CanvasContext::WebGL2(Dom::from_ref(&*context))); + Some(context) + } - let maybe_ctx = WebGLRenderingContext::new(&window, self, size, attrs); + fn get_or_init_webgpu_context(&self) -> Option<DomRoot<GPUCanvasContext>> { + if let Some(ctx) = self.context() { + return match *ctx { + CanvasContext::WebGPU(ref ctx) => Some(DomRoot::from_ref(ctx)), + _ => None, + }; + } + let (sender, receiver) = ipcchan::channel().unwrap(); + let _ = self + .global() + .script_to_constellation_chan() + .send(ScriptMsg::GetWebGPUChan(sender)); + let window = window_from_node(self); + let size = self.get_size(); + let channel = receiver.recv().expect("Failed to get WebGPU channel"); + let context = GPUCanvasContext::new(window.upcast::<GlobalScope>(), self, size, channel); + *self.context.borrow_mut() = Some(CanvasContext::WebGPU(Dom::from_ref(&*context))); + Some(context) + } - *self.context.borrow_mut() = maybe_ctx.map( |ctx| CanvasContext::WebGL(JS::from_ref(&*ctx))); + /// Gets the base WebGLRenderingContext for WebGL or WebGL 2, if exists. + pub fn get_base_webgl_context(&self) -> Option<DomRoot<WebGLRenderingContext>> { + match *self.context.borrow() { + Some(CanvasContext::WebGL(ref context)) => Some(DomRoot::from_ref(&*context)), + Some(CanvasContext::WebGL2(ref context)) => Some(context.base_context()), + _ => None, } + } - if let Some(CanvasContext::WebGL(ref context)) = *self.context.borrow() { - Some(Root::from_ref(&*context)) - } else { - None + #[allow(unsafe_code)] + fn get_gl_attributes(cx: JSContext, options: HandleValue) -> Option<GLContextAttributes> { + unsafe { + match WebGLContextAttributes::new(cx, options) { + Ok(ConversionResult::Success(ref attrs)) => Some(From::from(attrs)), + Ok(ConversionResult::Failure(ref error)) => { + throw_type_error(*cx, &error); + None + }, + _ => { + debug!("Unexpected error on conversion of WebGLContextAttributes"); + None + }, + } } } @@ -214,29 +305,38 @@ impl HTMLCanvasElement { self.Height() != 0 && self.Width() != 0 } - pub fn fetch_all_data(&self) -> Option<(Vec<u8>, Size2D<i32>)> { + pub fn fetch_all_data(&self) -> Option<(Option<IpcSharedMemory>, Size2D<u32>)> { let size = self.get_size(); if size.width == 0 || size.height == 0 { - return None + return None; } - let data = if let Some(renderer) = self.ipc_renderer() { - let (sender, receiver) = ipc::channel().unwrap(); - let msg = CanvasMsg::FromScript(FromScriptMsg::SendPixels(sender)); - renderer.send(msg).unwrap(); - - match receiver.recv().unwrap() { - Some(pixels) => pixels, - None => { - // TODO(emilio, #14109): Not sure if WebGL canvas is - // required for 2d spec, but I think it's not, if so, make - // this return a readback from the GL context. - return None; - } - } - } else { - repeat(0xffu8).take((size.height as usize) * (size.width as usize) * 4).collect() + let data = match self.context.borrow().as_ref() { + Some(&CanvasContext::Context2d(ref context)) => { + let (sender, receiver) = + ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); + let msg = CanvasMsg::FromScript( + FromScriptMsg::SendPixels(sender), + context.get_canvas_id(), + ); + context.get_ipc_renderer().send(msg).unwrap(); + + Some(receiver.recv().unwrap()) + }, + Some(&CanvasContext::WebGL(_)) => { + // TODO: add a method in WebGLRenderingContext to get the pixels. + return None; + }, + Some(&CanvasContext::WebGL2(_)) => { + // TODO: add a method in WebGL2RenderingContext to get the pixels. + return None; + }, + Some(&CanvasContext::WebGPU(_)) => { + // TODO: add a method in GPUCanvasContext to get the pixels. + return None; + }, + None => None, }; Some((data, size)) @@ -256,76 +356,99 @@ impl HTMLCanvasElementMethods for HTMLCanvasElement { // https://html.spec.whatwg.org/multipage/#dom-canvas-height make_uint_setter!(SetHeight, "height", DEFAULT_HEIGHT); - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-canvas-getcontext - unsafe fn GetContext(&self, - cx: *mut JSContext, - id: DOMString, - attributes: Vec<HandleValue>) - -> Option<CanvasRenderingContext2DOrWebGLRenderingContext> { + fn GetContext( + &self, + cx: JSContext, + id: DOMString, + options: HandleValue, + ) -> Option<RenderingContext> { match &*id { - "2d" => { - self.get_or_init_2d_context() - .map(CanvasRenderingContext2DOrWebGLRenderingContext::CanvasRenderingContext2D) - } - "webgl" | "experimental-webgl" => { - self.get_or_init_webgl_context(cx, attributes.get(0).cloned()) - .map(CanvasRenderingContext2DOrWebGLRenderingContext::WebGLRenderingContext) - } - _ => None + "2d" => self + .get_or_init_2d_context() + .map(RenderingContext::CanvasRenderingContext2D), + "webgl" | "experimental-webgl" => self + .get_or_init_webgl_context(cx, options) + .map(RenderingContext::WebGLRenderingContext), + "webgl2" | "experimental-webgl2" => self + .get_or_init_webgl2_context(cx, options) + .map(RenderingContext::WebGL2RenderingContext), + "gpupresent" => self + .get_or_init_webgpu_context() + .map(RenderingContext::GPUCanvasContext), + _ => None, } } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-canvas-todataurl - unsafe fn ToDataURL(&self, - _context: *mut JSContext, - _mime_type: Option<DOMString>, - _arguments: Vec<HandleValue>) -> Fallible<DOMString> { + fn ToDataURL( + &self, + _context: JSContext, + _mime_type: Option<DOMString>, + _quality: HandleValue, + ) -> Fallible<USVString> { // Step 1. - if let Some(CanvasContext::Context2d(ref context)) = *self.context.borrow() { - if !context.origin_is_clean() { - return Err(Error::Security); - } + if !self.origin_is_clean() { + return Err(Error::Security); } // Step 2. if self.Width() == 0 || self.Height() == 0 { - return Ok(DOMString::from("data:,")); + return Ok(USVString("data:,".into())); } // Step 3. - let raw_data = match *self.context.borrow() { + let file = match *self.context.borrow() { Some(CanvasContext::Context2d(ref context)) => { - let image_data = try!(context.GetImageData(Finite::wrap(0f64), Finite::wrap(0f64), - Finite::wrap(self.Width() as f64), - Finite::wrap(self.Height() as f64))); - image_data.get_data_array() - } + context.get_rect(Rect::from_size(self.get_size())) + }, + Some(CanvasContext::WebGL(ref context)) => { + match context.get_image_data(self.get_size()) { + Some(data) => data, + None => return Ok(USVString("data:,".into())), + } + }, + Some(CanvasContext::WebGL2(ref context)) => { + match context.base_context().get_image_data(self.get_size()) { + Some(data) => data, + None => return Ok(USVString("data:,".into())), + } + }, + //TODO: Add method get_image_data to GPUCanvasContext + Some(CanvasContext::WebGPU(_)) => return Ok(USVString("data:,".into())), None => { // Each pixel is fully-transparent black. vec![0; (self.Width() * self.Height() * 4) as usize] - } - _ => return Err(Error::NotSupported) // WebGL + }, }; - // Only handle image/png for now. - let mime_type = "image/png"; - - let mut encoded = Vec::new(); - { - let encoder: PNGEncoder<&mut Vec<u8>> = PNGEncoder::new(&mut encoded); - encoder.encode(&raw_data, self.Width(), self.Height(), ColorType::RGBA(8)).unwrap(); - } + // FIXME: Only handle image/png for now. + let mut png = Vec::new(); + // FIXME(nox): https://github.com/image-rs/image-png/issues/86 + // FIXME(nox): https://github.com/image-rs/image-png/issues/87 + PngEncoder::new(&mut png) + .encode(&file, self.Width(), self.Height(), ColorType::Rgba8) + .unwrap(); + let mut url = "data:image/png;base64,".to_owned(); + // FIXME(nox): Should this use base64::URL_SAFE? + // FIXME(nox): https://github.com/marshallpierce/rust-base64/pull/56 + base64::encode_config_buf(&png, base64::STANDARD, &mut url); + Ok(USVString(url)) + } - let encoded = base64::encode(&encoded); - Ok(DOMString::from(format!("data:{};base64,{}", mime_type, encoded))) + /// https://w3c.github.io/mediacapture-fromelement/#dom-htmlcanvaselement-capturestream + fn CaptureStream(&self, _frame_request_rate: Option<Finite<f64>>) -> DomRoot<MediaStream> { + let global = self.global(); + let stream = MediaStream::new(&*global); + let track = MediaStreamTrack::new(&*global, MediaStreamId::new(), MediaStreamType::Video); + stream.AddTrack(&track); + stream } } impl VirtualMethods for HTMLCanvasElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -340,7 +463,10 @@ impl VirtualMethods for HTMLCanvasElement { match name { &local_name!("width") => AttrValue::from_u32(value.into(), DEFAULT_WIDTH), &local_name!("height") => AttrValue::from_u32(value.into(), DEFAULT_HEIGHT), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } } @@ -359,21 +485,26 @@ impl<'a> From<&'a WebGLContextAttributes> for GLContextAttributes { } pub mod utils { - use dom::window::Window; - use net_traits::image_cache::{ImageResponse, UsePlaceholder, ImageOrMetadataAvailable}; - use net_traits::image_cache::CanRequestImages; + use crate::dom::window::Window; + use net_traits::image_cache::ImageResponse; + use net_traits::request::CorsSettings; use servo_url::ServoUrl; - pub fn request_image_from_cache(window: &Window, url: ServoUrl) -> ImageResponse { + pub fn request_image_from_cache( + window: &Window, + url: ServoUrl, + cors_setting: Option<CorsSettings>, + ) -> ImageResponse { let image_cache = window.image_cache(); - let response = - image_cache.find_image_or_metadata(url.into(), - UsePlaceholder::No, - CanRequestImages::No); - match response { - Ok(ImageOrMetadataAvailable::ImageAvailable(image)) => - ImageResponse::Loaded(image), - _ => ImageResponse::None, + let result = image_cache.get_image( + url.clone(), + window.origin().immutable().clone(), + cors_setting, + ); + + match result { + Some(image) => ImageResponse::Loaded(image, url), + None => ImageResponse::None, } } } diff --git a/components/script/dom/htmlcollection.rs b/components/script/dom/htmlcollection.rs index 4e05de86922..d5198b94986 100644 --- a/components/script/dom/htmlcollection.rs +++ b/components/script/dom/htmlcollection.rs @@ -1,32 +1,31 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HTMLCollectionBinding; -use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root, MutNullableJS}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::bindings::trace::JSTraceable; -use dom::bindings::xmlname::namespace_from_domstring; -use dom::element::Element; -use dom::node::Node; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::bindings::xmlname::namespace_from_domstring; +use crate::dom::element::Element; +use crate::dom::node::{document_from_node, Node}; +use crate::dom::window::Window; use dom_struct::dom_struct; -use html5ever_atoms::{LocalName, QualName}; +use html5ever::{LocalName, QualName}; use servo_atoms::Atom; use std::cell::Cell; use style::str::split_html_space_chars; -pub trait CollectionFilter : JSTraceable { +pub trait CollectionFilter: JSTraceable { fn filter<'a>(&self, elem: &'a Element, root: &'a Node) -> bool; } // An optional u32, using maxint to represent None. // It would be nicer just to use Option<u32> for this, but that would produce word // alignment issues since Option<u32> uses 33 bits. -#[derive(Clone, Copy, JSTraceable, HeapSizeOf)] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf)] struct OptionU32 { bits: u32, } @@ -41,53 +40,80 @@ impl OptionU32 { } fn some(bits: u32) -> OptionU32 { - assert!(bits != u32::max_value()); + assert_ne!(bits, u32::max_value()); OptionU32 { bits: bits } } fn none() -> OptionU32 { - OptionU32 { bits: u32::max_value() } + OptionU32 { + bits: u32::max_value(), + } } } #[dom_struct] pub struct HTMLCollection { reflector_: Reflector, - root: JS<Node>, - #[ignore_heap_size_of = "Contains a trait object; can't measure due to #6870"] - filter: Box<CollectionFilter + 'static>, + root: Dom<Node>, + #[ignore_malloc_size_of = "Contains a trait object; can't measure due to #6870"] + filter: Box<dyn CollectionFilter + 'static>, // We cache the version of the root node and all its decendents, // the length of the collection, and a cursor into the collection. // FIXME: make the cached cursor element a weak pointer cached_version: Cell<u64>, - cached_cursor_element: MutNullableJS<Element>, + cached_cursor_element: MutNullableDom<Element>, cached_cursor_index: Cell<OptionU32>, cached_length: Cell<OptionU32>, } impl HTMLCollection { #[allow(unrooted_must_root)] - pub fn new_inherited(root: &Node, filter: Box<CollectionFilter + 'static>) -> HTMLCollection { + pub fn new_inherited( + root: &Node, + filter: Box<dyn CollectionFilter + 'static>, + ) -> HTMLCollection { HTMLCollection { reflector_: Reflector::new(), - root: JS::from_ref(root), + root: Dom::from_ref(root), filter: filter, // Default values for the cache cached_version: Cell::new(root.inclusive_descendants_version()), - cached_cursor_element: MutNullableJS::new(None), + cached_cursor_element: MutNullableDom::new(None), cached_cursor_index: Cell::new(OptionU32::none()), cached_length: Cell::new(OptionU32::none()), } } + /// Returns a collection which is always empty. + pub fn always_empty(window: &Window, root: &Node) -> DomRoot<Self> { + #[derive(JSTraceable)] + struct NoFilter; + impl CollectionFilter for NoFilter { + fn filter<'a>(&self, _: &'a Element, _: &'a Node) -> bool { + false + } + } + + Self::new(window, root, Box::new(NoFilter)) + } + #[allow(unrooted_must_root)] - pub fn new(window: &Window, root: &Node, filter: Box<CollectionFilter + 'static>) -> Root<HTMLCollection> { - reflect_dom_object(box HTMLCollection::new_inherited(root, filter), - window, HTMLCollectionBinding::Wrap) + pub fn new( + window: &Window, + root: &Node, + filter: Box<dyn CollectionFilter + 'static>, + ) -> DomRoot<HTMLCollection> { + reflect_dom_object( + Box::new(HTMLCollection::new_inherited(root, filter)), + window, + ) } - pub fn create(window: &Window, root: &Node, - filter: Box<CollectionFilter + 'static>) -> Root<HTMLCollection> { + pub fn create( + window: &Window, + root: &Node, + filter: Box<dyn CollectionFilter + 'static>, + ) -> DomRoot<HTMLCollection> { HTMLCollection::new(window, root, filter) } @@ -104,7 +130,11 @@ impl HTMLCollection { } } - fn set_cached_cursor(&self, index: u32, element: Option<Root<Element>>) -> Option<Root<Element>> { + fn set_cached_cursor( + &self, + index: u32, + element: Option<DomRoot<Element>>, + ) -> Option<DomRoot<Element>> { if let Some(element) = element { self.cached_cursor_index.set(OptionU32::some(index)); self.cached_cursor_element.set(Some(&element)); @@ -115,30 +145,35 @@ impl HTMLCollection { } // https://dom.spec.whatwg.org/#concept-getelementsbytagname - pub fn by_qualified_name(window: &Window, root: &Node, qualified_name: LocalName) - -> Root<HTMLCollection> { + pub fn by_qualified_name( + window: &Window, + root: &Node, + qualified_name: LocalName, + ) -> DomRoot<HTMLCollection> { // case 1 if qualified_name == local_name!("*") { - #[derive(JSTraceable, HeapSizeOf)] + #[derive(JSTraceable, MallocSizeOf)] struct AllFilter; impl CollectionFilter for AllFilter { fn filter(&self, _elem: &Element, _root: &Node) -> bool { true } } - return HTMLCollection::create(window, root, box AllFilter); + return HTMLCollection::create(window, root, Box::new(AllFilter)); } - #[derive(JSTraceable, HeapSizeOf)] + #[derive(JSTraceable, MallocSizeOf)] struct HtmlDocumentFilter { qualified_name: LocalName, ascii_lower_qualified_name: LocalName, } impl CollectionFilter for HtmlDocumentFilter { fn filter(&self, elem: &Element, root: &Node) -> bool { - if root.is_in_html_doc() && elem.namespace() == &ns!(html) { // case 2 + if root.is_in_html_doc() && elem.namespace() == &ns!(html) { + // case 2 HTMLCollection::match_element(elem, &self.ascii_lower_qualified_name) - } else { // case 2 and 3 + } else { + // case 2 and 3 HTMLCollection::match_element(elem, &self.qualified_name) } } @@ -148,122 +183,124 @@ impl HTMLCollection { ascii_lower_qualified_name: qualified_name.to_ascii_lowercase(), qualified_name: qualified_name, }; - HTMLCollection::create(window, root, box filter) + HTMLCollection::create(window, root, Box::new(filter)) } fn match_element(elem: &Element, qualified_name: &LocalName) -> bool { - match elem.prefix() { + match elem.prefix().as_ref() { None => elem.local_name() == qualified_name, - Some(prefix) => qualified_name.starts_with(&**prefix) && - qualified_name.find(":") == Some(prefix.len()) && - qualified_name.ends_with(&**elem.local_name()), + Some(prefix) => { + qualified_name.starts_with(&**prefix) && + qualified_name.find(":") == Some(prefix.len()) && + qualified_name.ends_with(&**elem.local_name()) + }, } } - pub fn by_tag_name_ns(window: &Window, root: &Node, tag: DOMString, - maybe_ns: Option<DOMString>) -> Root<HTMLCollection> { + pub fn by_tag_name_ns( + window: &Window, + root: &Node, + tag: DOMString, + maybe_ns: Option<DOMString>, + ) -> DomRoot<HTMLCollection> { let local = LocalName::from(tag); let ns = namespace_from_domstring(maybe_ns); - let qname = QualName::new(ns, local); + let qname = QualName::new(None, ns, local); HTMLCollection::by_qual_tag_name(window, root, qname) } - pub fn by_qual_tag_name(window: &Window, root: &Node, qname: QualName) -> Root<HTMLCollection> { - #[derive(JSTraceable, HeapSizeOf)] + pub fn by_qual_tag_name( + window: &Window, + root: &Node, + qname: QualName, + ) -> DomRoot<HTMLCollection> { + #[derive(JSTraceable, MallocSizeOf)] struct TagNameNSFilter { - qname: QualName + qname: QualName, } impl CollectionFilter for TagNameNSFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { - ((self.qname.ns == namespace_url!("*")) || (self.qname.ns == *elem.namespace())) && - ((self.qname.local == local_name!("*")) || (self.qname.local == *elem.local_name())) + ((self.qname.ns == namespace_url!("*")) || (self.qname.ns == *elem.namespace())) && + ((self.qname.local == local_name!("*")) || + (self.qname.local == *elem.local_name())) } } - let filter = TagNameNSFilter { - qname: qname - }; - HTMLCollection::create(window, root, box filter) + let filter = TagNameNSFilter { qname: qname }; + HTMLCollection::create(window, root, Box::new(filter)) } - pub fn by_class_name(window: &Window, root: &Node, classes: DOMString) - -> Root<HTMLCollection> { + pub fn by_class_name( + window: &Window, + root: &Node, + classes: DOMString, + ) -> DomRoot<HTMLCollection> { let class_atoms = split_html_space_chars(&classes).map(Atom::from).collect(); HTMLCollection::by_atomic_class_name(window, root, class_atoms) } - pub fn by_atomic_class_name(window: &Window, root: &Node, classes: Vec<Atom>) - -> Root<HTMLCollection> { - #[derive(JSTraceable, HeapSizeOf)] + pub fn by_atomic_class_name( + window: &Window, + root: &Node, + classes: Vec<Atom>, + ) -> DomRoot<HTMLCollection> { + #[derive(JSTraceable, MallocSizeOf)] struct ClassNameFilter { - classes: Vec<Atom> + classes: Vec<Atom>, } impl CollectionFilter for ClassNameFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { - self.classes.iter().all(|class| elem.has_class(class)) + let case_sensitivity = document_from_node(elem) + .quirks_mode() + .classes_and_ids_case_sensitivity(); + self.classes + .iter() + .all(|class| elem.has_class(class, case_sensitivity)) } } - let filter = ClassNameFilter { - classes: classes - }; - HTMLCollection::create(window, root, box filter) + let filter = ClassNameFilter { classes: classes }; + HTMLCollection::create(window, root, Box::new(filter)) } - pub fn children(window: &Window, root: &Node) -> Root<HTMLCollection> { - #[derive(JSTraceable, HeapSizeOf)] + pub fn children(window: &Window, root: &Node) -> DomRoot<HTMLCollection> { + #[derive(JSTraceable, MallocSizeOf)] struct ElementChildFilter; impl CollectionFilter for ElementChildFilter { fn filter(&self, elem: &Element, root: &Node) -> bool { root.is_parent_of(elem.upcast()) } } - HTMLCollection::create(window, root, box ElementChildFilter) + HTMLCollection::create(window, root, Box::new(ElementChildFilter)) } - pub fn elements_iter_after<'a>(&'a self, after: &'a Node) -> impl Iterator<Item=Root<Element>> + 'a { + pub fn elements_iter_after<'a>( + &'a self, + after: &'a Node, + ) -> impl Iterator<Item = DomRoot<Element>> + 'a { // Iterate forwards from a node. - HTMLCollectionElementsIter { - node_iter: after.following_nodes(&self.root), - root: Root::from_ref(&self.root), - filter: &self.filter, - } + after + .following_nodes(&self.root) + .filter_map(DomRoot::downcast) + .filter(move |element| self.filter.filter(&element, &self.root)) } - pub fn elements_iter<'a>(&'a self) -> impl Iterator<Item=Root<Element>> + 'a { + pub fn elements_iter<'a>(&'a self) -> impl Iterator<Item = DomRoot<Element>> + 'a { // Iterate forwards from the root. self.elements_iter_after(&*self.root) } - pub fn elements_iter_before<'a>(&'a self, before: &'a Node) -> impl Iterator<Item=Root<Element>> + 'a { + pub fn elements_iter_before<'a>( + &'a self, + before: &'a Node, + ) -> impl Iterator<Item = DomRoot<Element>> + 'a { // Iterate backwards from a node. - HTMLCollectionElementsIter { - node_iter: before.preceding_nodes(&self.root), - root: Root::from_ref(&self.root), - filter: &self.filter, - } - } - - pub fn root_node(&self) -> Root<Node> { - Root::from_ref(&self.root) + before + .preceding_nodes(&self.root) + .filter_map(DomRoot::downcast) + .filter(move |element| self.filter.filter(&element, &self.root)) } -} - -// TODO: Make this generic, and avoid code duplication -struct HTMLCollectionElementsIter<'a, I> { - node_iter: I, - root: Root<Node>, - filter: &'a Box<CollectionFilter>, -} - -impl<'a, I: Iterator<Item=Root<Node>>> Iterator for HTMLCollectionElementsIter<'a, I> { - type Item = Root<Element>; - fn next(&mut self) -> Option<Self::Item> { - let filter = &self.filter; - let root = &self.root; - self.node_iter.by_ref() - .filter_map(Root::downcast) - .filter(|element| filter.filter(&element, root)) - .next() + pub fn root_node(&self) -> DomRoot<Node> { + DomRoot::from_ref(&self.root) } } @@ -284,7 +321,7 @@ impl HTMLCollectionMethods for HTMLCollection { } // https://dom.spec.whatwg.org/#dom-htmlcollection-item - fn Item(&self, index: u32) -> Option<Root<Element>> { + fn Item(&self, index: u32) -> Option<DomRoot<Element>> { self.validate_cache(); if let Some(element) = self.cached_cursor_element.get() { @@ -297,14 +334,14 @@ impl HTMLCollectionMethods for HTMLCollection { // The cursor is before the element we're looking for // Iterate forwards, starting at the cursor. let offset = index - (cached_index + 1); - let node: Root<Node> = Root::upcast(element); + let node: DomRoot<Node> = DomRoot::upcast(element); let mut iter = self.elements_iter_after(&node); self.set_cached_cursor(index, iter.nth(offset as usize)) } else { // The cursor is after the element we're looking for // Iterate backwards, starting at the cursor. let offset = cached_index - (index + 1); - let node: Root<Node> = Root::upcast(element); + let node: DomRoot<Node> = DomRoot::upcast(element); let mut iter = self.elements_iter_before(&node); self.set_cached_cursor(index, iter.nth(offset as usize)) } @@ -321,26 +358,28 @@ impl HTMLCollectionMethods for HTMLCollection { } // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem - fn NamedItem(&self, key: DOMString) -> Option<Root<Element>> { + fn NamedItem(&self, key: DOMString) -> Option<DomRoot<Element>> { // Step 1. if key.is_empty() { return None; } + let key = Atom::from(key); + // Step 2. self.elements_iter().find(|elem| { - elem.get_string_attribute(&local_name!("id")) == key || - (elem.namespace() == &ns!(html) && elem.get_string_attribute(&local_name!("name")) == key) + elem.get_id().map_or(false, |id| id == key) || + (elem.namespace() == &ns!(html) && elem.get_name().map_or(false, |id| id == key)) }) } // https://dom.spec.whatwg.org/#dom-htmlcollection-item - fn IndexedGetter(&self, index: u32) -> Option<Root<Element>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> { self.Item(index) } // check-tidy: no specs after this line - fn NamedGetter(&self, name: DOMString) -> Option<Root<Element>> { + fn NamedGetter(&self, name: DOMString) -> Option<DomRoot<Element>> { self.NamedItem(name) } @@ -352,14 +391,20 @@ impl HTMLCollectionMethods for HTMLCollection { // Step 2 for elem in self.elements_iter() { // Step 2.1 - let id_attr = elem.get_string_attribute(&local_name!("id")); - if !id_attr.is_empty() && !result.contains(&id_attr) { - result.push(id_attr) + if let Some(id_atom) = elem.get_id() { + let id_str = DOMString::from(&*id_atom); + if !result.contains(&id_str) { + result.push(id_str); + } } // Step 2.2 - let name_attr = elem.get_string_attribute(&local_name!("name")); - if !name_attr.is_empty() && !result.contains(&name_attr) && *elem.namespace() == ns!(html) { - result.push(name_attr) + if *elem.namespace() == ns!(html) { + if let Some(name_atom) = elem.get_name() { + let name_str = DOMString::from(&*name_atom); + if !result.contains(&name_str) { + result.push(name_str) + } + } } } diff --git a/components/script/dom/htmldataelement.rs b/components/script/dom/htmldataelement.rs index 8dc53d9860b..93594ec8816 100644 --- a/components/script/dom/htmldataelement.rs +++ b/components/script/dom/htmldataelement.rs @@ -1,38 +1,42 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLDataElementBinding; -use dom::bindings::codegen::Bindings::HTMLDataElementBinding::HTMLDataElementMethods; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::codegen::Bindings::HTMLDataElementBinding::HTMLDataElementMethods; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLDataElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLDataElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLDataElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLDataElement { HTMLDataElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLDataElement> { - Node::reflect_node(box HTMLDataElement::new_inherited(local_name, prefix, document), - document, - HTMLDataElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLDataElement> { + Node::reflect_node( + Box::new(HTMLDataElement::new_inherited(local_name, prefix, document)), + document, + ) } } diff --git a/components/script/dom/htmldatalistelement.rs b/components/script/dom/htmldatalistelement.rs index 48c39c500ed..6bddb2800e4 100644 --- a/components/script/dom/htmldatalistelement.rs +++ b/components/script/dom/htmldatalistelement.rs @@ -1,57 +1,61 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLDataListElementBinding; -use dom::bindings::codegen::Bindings::HTMLDataListElementBinding::HTMLDataListElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::Element; -use dom::htmlcollection::{CollectionFilter, HTMLCollection}; -use dom::htmlelement::HTMLElement; -use dom::htmloptionelement::HTMLOptionElement; -use dom::node::{Node, window_from_node}; +use crate::dom::bindings::codegen::Bindings::HTMLDataListElementBinding::HTMLDataListElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmloptionelement::HTMLOptionElement; +use crate::dom::node::{window_from_node, Node}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLDataListElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLDataListElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLDataListElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLDataListElement { HTMLDataListElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLDataListElement> { - Node::reflect_node(box HTMLDataListElement::new_inherited(local_name, prefix, document), - document, - HTMLDataListElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLDataListElement> { + Node::reflect_node( + Box::new(HTMLDataListElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } impl HTMLDataListElementMethods for HTMLDataListElement { // https://html.spec.whatwg.org/multipage/#dom-datalist-options - fn Options(&self) -> Root<HTMLCollection> { - #[derive(JSTraceable, HeapSizeOf)] + fn Options(&self) -> DomRoot<HTMLCollection> { + #[derive(JSTraceable, MallocSizeOf)] struct HTMLDataListOptionsFilter; impl CollectionFilter for HTMLDataListOptionsFilter { fn filter(&self, elem: &Element, _root: &Node) -> bool { elem.is::<HTMLOptionElement>() } } - let filter = box HTMLDataListOptionsFilter; + let filter = Box::new(HTMLDataListOptionsFilter); let window = window_from_node(self); HTMLCollection::create(&window, self.upcast(), filter) } diff --git a/components/script/dom/htmldetailselement.rs b/components/script/dom/htmldetailselement.rs index 1937c8f09b6..5ec9ab68604 100644 --- a/components/script/dom/htmldetailselement.rs +++ b/components/script/dom/htmldetailselement.rs @@ -1,54 +1,57 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::HTMLDetailsElementBinding; -use dom::bindings::codegen::Bindings::HTMLDetailsElementBinding::HTMLDetailsElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::refcounted::Trusted; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::AttributeMutation; -use dom::eventtarget::EventTarget; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, window_from_node}; -use dom::virtualmethods::VirtualMethods; +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLDetailsElementBinding::HTMLDetailsElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::element::AttributeMutation; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{window_from_node, Node, NodeDamage}; +use crate::dom::virtualmethods::VirtualMethods; +use crate::task_source::TaskSource; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use script_thread::Runnable; +use html5ever::{LocalName, Prefix}; use std::cell::Cell; -use task_source::TaskSource; #[dom_struct] pub struct HTMLDetailsElement { htmlelement: HTMLElement, - toggle_counter: Cell<u32> + toggle_counter: Cell<u32>, } impl HTMLDetailsElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLDetailsElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLDetailsElement { HTMLDetailsElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), - toggle_counter: Cell::new(0) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + toggle_counter: Cell::new(0), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLDetailsElement> { - Node::reflect_node(box HTMLDetailsElement::new_inherited(local_name, prefix, document), - document, - HTMLDetailsElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLDetailsElement> { + Node::reflect_node( + Box::new(HTMLDetailsElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } - pub fn check_toggle_count(&self, number: u32) -> bool { - number == self.toggle_counter.get() + pub fn toggle(&self) { + self.SetOpen(!self.Open()); } } @@ -61,8 +64,8 @@ impl HTMLDetailsElementMethods for HTMLDetailsElement { } impl VirtualMethods for HTMLDetailsElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -73,29 +76,18 @@ impl VirtualMethods for HTMLDetailsElement { self.toggle_counter.set(counter); let window = window_from_node(self); - let task_source = window.dom_manipulation_task_source(); - let details = Trusted::new(self); - let runnable = box DetailsNotificationRunnable { - element: details, - toggle_number: counter - }; - let _ = task_source.queue(runnable, window.upcast()); - } - } -} - -pub struct DetailsNotificationRunnable { - element: Trusted<HTMLDetailsElement>, - toggle_number: u32 -} - -impl Runnable for DetailsNotificationRunnable { - fn name(&self) -> &'static str { "DetailsNotificationRunnable" } - - fn handler(self: Box<DetailsNotificationRunnable>) { - let target = self.element.root(); - if target.check_toggle_count(self.toggle_number) { - target.upcast::<EventTarget>().fire_event(atom!("toggle")); + let this = Trusted::new(self); + // FIXME(nox): Why are errors silenced here? + let _ = window.task_manager().dom_manipulation_task_source().queue( + task!(details_notification_task_steps: move || { + let this = this.root(); + if counter == this.toggle_counter.get() { + this.upcast::<EventTarget>().fire_event(atom!("toggle")); + } + }), + window.upcast(), + ); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage) } } } diff --git a/components/script/dom/htmldialogelement.rs b/components/script/dom/htmldialogelement.rs index 37b38ed82dc..135f7afe035 100644 --- a/components/script/dom/htmldialogelement.rs +++ b/components/script/dom/htmldialogelement.rs @@ -1,45 +1,50 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::HTMLDialogElementBinding; -use dom::bindings::codegen::Bindings::HTMLDialogElementBinding::HTMLDialogElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::Element; -use dom::eventtarget::EventTarget; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, window_from_node}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLDialogElementBinding::HTMLDialogElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{window_from_node, Node}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLDialogElement { htmlelement: HTMLElement, - return_value: DOMRefCell<DOMString>, + return_value: DomRefCell<DOMString>, } impl HTMLDialogElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLDialogElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLDialogElement { HTMLDialogElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), - return_value: DOMRefCell::new(DOMString::new()), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + return_value: DomRefCell::new(DOMString::new()), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLDialogElement> { - Node::reflect_node(box HTMLDialogElement::new_inherited(local_name, prefix, document), - document, - HTMLDialogElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLDialogElement> { + Node::reflect_node( + Box::new(HTMLDialogElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } @@ -68,7 +73,10 @@ impl HTMLDialogElementMethods for HTMLDialogElement { let win = window_from_node(self); // Step 1 & 2 - if element.remove_attribute(&ns!(), &local_name!("open")).is_none() { + if element + .remove_attribute(&ns!(), &local_name!("open")) + .is_none() + { return; } @@ -80,6 +88,8 @@ impl HTMLDialogElementMethods for HTMLDialogElement { // TODO: Step 4 implement pending dialog stack removal // Step 5 - win.dom_manipulation_task_source().queue_simple_event(target, atom!("close"), &win); + win.task_manager() + .dom_manipulation_task_source() + .queue_simple_event(target, atom!("close"), &win); } } diff --git a/components/script/dom/htmldirectoryelement.rs b/components/script/dom/htmldirectoryelement.rs index aa24adcfacf..ce2472be3d3 100644 --- a/components/script/dom/htmldirectoryelement.rs +++ b/components/script/dom/htmldirectoryelement.rs @@ -1,37 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLDirectoryElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLDirectoryElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLDirectoryElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLDirectoryElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLDirectoryElement { HTMLDirectoryElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLDirectoryElement> { - Node::reflect_node(box HTMLDirectoryElement::new_inherited(local_name, prefix, document), - document, - HTMLDirectoryElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLDirectoryElement> { + Node::reflect_node( + Box::new(HTMLDirectoryElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmldivelement.rs b/components/script/dom/htmldivelement.rs index e4d8bf669ef..0bd558210a0 100644 --- a/components/script/dom/htmldivelement.rs +++ b/components/script/dom/htmldivelement.rs @@ -1,37 +1,42 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLDivElementBinding::{self, HTMLDivElementMethods}; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::codegen::Bindings::HTMLDivElementBinding::HTMLDivElementMethods; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLDivElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLDivElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLDivElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLDivElement { HTMLDivElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLDivElement> { - Node::reflect_node(box HTMLDivElement::new_inherited(local_name, prefix, document), - document, - HTMLDivElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLDivElement> { + Node::reflect_node( + Box::new(HTMLDivElement::new_inherited(local_name, prefix, document)), + document, + ) } } diff --git a/components/script/dom/htmldlistelement.rs b/components/script/dom/htmldlistelement.rs index 6da79f5d5c8..356c30c8c94 100644 --- a/components/script/dom/htmldlistelement.rs +++ b/components/script/dom/htmldlistelement.rs @@ -1,35 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLDListElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLDListElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLDListElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLDListElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLDListElement { HTMLDListElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLDListElement> { - Node::reflect_node(box HTMLDListElement::new_inherited(local_name, prefix, document), - document, - HTMLDListElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLDListElement> { + Node::reflect_node( + Box::new(HTMLDListElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index ade0b7307d1..0b1cb499fe2 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -1,38 +1,43 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::activation::{ActivationSource, synthetic_click_activation}; -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; -use dom::bindings::codegen::Bindings::HTMLElementBinding; -use dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::{Error, ErrorResult}; -use dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference}; -use dom::bindings::str::DOMString; -use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; -use dom::document::{Document, FocusType}; -use dom::domstringmap::DOMStringMap; -use dom::element::{AttributeMutation, Element}; -use dom::eventtarget::EventTarget; -use dom::htmlbodyelement::HTMLBodyElement; -use dom::htmlframesetelement::HTMLFrameSetElement; -use dom::htmlhtmlelement::HTMLHtmlElement; -use dom::htmlinputelement::HTMLInputElement; -use dom::htmllabelelement::HTMLLabelElement; -use dom::node::{Node, SEQUENTIALLY_FOCUSABLE}; -use dom::node::{document_from_node, window_from_node}; -use dom::nodelist::NodeList; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::activation::Activatable; +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; +use crate::dom::document::{Document, FocusType}; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::domstringmap::DOMStringMap; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlbodyelement::HTMLBodyElement; +use crate::dom::htmlbrelement::HTMLBRElement; +use crate::dom::htmldetailselement::HTMLDetailsElement; +use crate::dom::htmlframesetelement::HTMLFrameSetElement; +use crate::dom::htmlhtmlelement::HTMLHtmlElement; +use crate::dom::htmlinputelement::{HTMLInputElement, InputType}; +use crate::dom::htmllabelelement::HTMLLabelElement; +use crate::dom::htmltextareaelement::HTMLTextAreaElement; +use crate::dom::node::{document_from_node, window_from_node}; +use crate::dom::node::{Node, ShadowIncluding}; +use crate::dom::text::Text; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use std::ascii::AsciiExt; -use std::borrow::ToOwned; +use html5ever::{LocalName, Prefix}; +use script_layout_interface::message::QueryMsg; +use std::collections::HashSet; use std::default::Default; use std::rc::Rc; use style::attr::AttrValue; @@ -41,85 +46,67 @@ use style::element_state::*; #[dom_struct] pub struct HTMLElement { element: Element, - style_decl: MutNullableJS<CSSStyleDeclaration>, - dataset: MutNullableJS<DOMStringMap>, + style_decl: MutNullableDom<CSSStyleDeclaration>, + dataset: MutNullableDom<DOMStringMap>, } impl HTMLElement { - pub fn new_inherited(tag_name: LocalName, prefix: Option<DOMString>, - document: &Document) -> HTMLElement { + pub fn new_inherited( + tag_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLElement { HTMLElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document) } - pub fn new_inherited_with_state(state: ElementState, tag_name: LocalName, - prefix: Option<DOMString>, document: &Document) - -> HTMLElement { + pub fn new_inherited_with_state( + state: ElementState, + tag_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLElement { HTMLElement { - element: - Element::new_inherited_with_state(state, tag_name, ns!(html), prefix, document), + element: Element::new_inherited_with_state( + state, + tag_name, + ns!(html), + prefix, + document, + ), style_decl: Default::default(), dataset: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> Root<HTMLElement> { - Node::reflect_node(box HTMLElement::new_inherited(local_name, prefix, document), - document, - HTMLElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLElement> { + Node::reflect_node( + Box::new(HTMLElement::new_inherited(local_name, prefix, document)), + document, + ) } fn is_body_or_frameset(&self) -> bool { let eventtarget = self.upcast::<EventTarget>(); eventtarget.is::<HTMLBodyElement>() || eventtarget.is::<HTMLFrameSetElement>() } - - fn update_sequentially_focusable_status(&self) { - let element = self.upcast::<Element>(); - let node = self.upcast::<Node>(); - if element.has_attribute(&local_name!("tabindex")) { - node.set_flag(SEQUENTIALLY_FOCUSABLE, true); - } else { - match node.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) - => node.set_flag(SEQUENTIALLY_FOCUSABLE, true), - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) => { - if element.has_attribute(&local_name!("href")) { - node.set_flag(SEQUENTIALLY_FOCUSABLE, true); - } - }, - _ => { - if let Some(attr) = element.get_attribute(&ns!(), &local_name!("draggable")) { - let value = attr.value(); - let is_true = match *value { - AttrValue::String(ref string) => string == "true", - _ => false, - }; - node.set_flag(SEQUENTIALLY_FOCUSABLE, is_true); - } else { - node.set_flag(SEQUENTIALLY_FOCUSABLE, false); - } - //TODO set SEQUENTIALLY_FOCUSABLE flag if editing host - //TODO set SEQUENTIALLY_FOCUSABLE flag if "sorting interface th elements" - }, - } - } - } } impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#the-style-attribute - fn Style(&self) -> Root<CSSStyleDeclaration> { + fn Style(&self) -> DomRoot<CSSStyleDeclaration> { self.style_decl.or_init(|| { let global = window_from_node(self); - CSSStyleDeclaration::new(&global, - CSSStyleOwner::Element(JS::from_ref(self.upcast())), - None, - CSSModificationAccess::ReadWrite) + CSSStyleDeclaration::new( + &global, + CSSStyleOwner::Element(Dom::from_ref(self.upcast())), + None, + CSSModificationAccess::ReadWrite, + ) }) } @@ -138,6 +125,11 @@ impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#dom-hidden make_bool_setter!(SetHidden, "hidden"); + // https://html.spec.whatwg.org/multipage/#the-dir-attribute + make_getter!(Dir, "dir"); + // https://html.spec.whatwg.org/multipage/#the-dir-attribute + make_setter!(SetDir, "dir"); + // https://html.spec.whatwg.org/multipage/#globaleventhandlers global_event_handlers!(NoOnload); @@ -145,38 +137,41 @@ impl HTMLElementMethods for HTMLElement { document_and_element_event_handlers!(); // https://html.spec.whatwg.org/multipage/#dom-dataset - fn Dataset(&self) -> Root<DOMStringMap> { + fn Dataset(&self) -> DomRoot<DOMStringMap> { self.dataset.or_init(|| DOMStringMap::new(self)) } - // https://html.spec.whatwg.org/multipage/#handler-onload - fn GetOnload(&self) -> Option<Rc<EventHandlerNonNull>> { + // https://html.spec.whatwg.org/multipage/#handler-onerror + fn GetOnerror(&self) -> Option<Rc<OnErrorEventHandlerNonNull>> { if self.is_body_or_frameset() { let document = document_from_node(self); if document.has_browsing_context() { - document.window().GetOnload() + document.window().GetOnerror() } else { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("load") + self.upcast::<EventTarget>() + .get_event_handler_common("error") } } - // https://html.spec.whatwg.org/multipage/#handler-onload - fn SetOnload(&self, listener: Option<Rc<EventHandlerNonNull>>) { + // https://html.spec.whatwg.org/multipage/#handler-onerror + fn SetOnerror(&self, listener: Option<Rc<OnErrorEventHandlerNonNull>>) { if self.is_body_or_frameset() { let document = document_from_node(self); if document.has_browsing_context() { - document.window().SetOnload(listener) + document.window().SetOnerror(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("load", listener) + // special setter for error + self.upcast::<EventTarget>() + .set_error_event_handler("error", listener) } } - // https://html.spec.whatwg.org/multipage/#handler-onresize - fn GetOnresize(&self) -> Option<Rc<EventHandlerNonNull>> { + // https://html.spec.whatwg.org/multipage/#handler-onload + fn GetOnload(&self) -> Option<Rc<EventHandlerNonNull>> { if self.is_body_or_frameset() { let document = document_from_node(self); if document.has_browsing_context() { @@ -185,19 +180,21 @@ impl HTMLElementMethods for HTMLElement { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("resize") + self.upcast::<EventTarget>() + .get_event_handler_common("load") } } - // https://html.spec.whatwg.org/multipage/#handler-onresize - fn SetOnresize(&self, listener: Option<Rc<EventHandlerNonNull>>) { + // https://html.spec.whatwg.org/multipage/#handler-onload + fn SetOnload(&self, listener: Option<Rc<EventHandlerNonNull>>) { if self.is_body_or_frameset() { let document = document_from_node(self); if document.has_browsing_context() { - document.window().SetOnresize(listener); + document.window().SetOnload(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("resize", listener) + self.upcast::<EventTarget>() + .set_event_handler_common("load", listener) } } @@ -211,7 +208,8 @@ impl HTMLElementMethods for HTMLElement { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("blur") + self.upcast::<EventTarget>() + .get_event_handler_common("blur") } } @@ -223,7 +221,8 @@ impl HTMLElementMethods for HTMLElement { document.window().SetOnblur(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("blur", listener) + self.upcast::<EventTarget>() + .set_event_handler_common("blur", listener) } } @@ -237,7 +236,8 @@ impl HTMLElementMethods for HTMLElement { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("focus") + self.upcast::<EventTarget>() + .get_event_handler_common("focus") } } @@ -249,7 +249,36 @@ impl HTMLElementMethods for HTMLElement { document.window().SetOnfocus(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("focus", listener) + self.upcast::<EventTarget>() + .set_event_handler_common("focus", listener) + } + } + + // https://html.spec.whatwg.org/multipage/#handler-onresize + fn GetOnresize(&self) -> Option<Rc<EventHandlerNonNull>> { + if self.is_body_or_frameset() { + let document = document_from_node(self); + if document.has_browsing_context() { + document.window().GetOnresize() + } else { + None + } + } else { + self.upcast::<EventTarget>() + .get_event_handler_common("resize") + } + } + + // https://html.spec.whatwg.org/multipage/#handler-onresize + fn SetOnresize(&self, listener: Option<Rc<EventHandlerNonNull>>) { + if self.is_body_or_frameset() { + let document = document_from_node(self); + if document.has_browsing_context() { + document.window().SetOnresize(listener) + } + } else { + self.upcast::<EventTarget>() + .set_event_handler_common("resize", listener) } } @@ -263,7 +292,8 @@ impl HTMLElementMethods for HTMLElement { None } } else { - self.upcast::<EventTarget>().get_event_handler_common("scroll") + self.upcast::<EventTarget>() + .get_event_handler_common("scroll") } } @@ -275,20 +305,61 @@ impl HTMLElementMethods for HTMLElement { document.window().SetOnscroll(listener) } } else { - self.upcast::<EventTarget>().set_event_handler_common("scroll", listener) + self.upcast::<EventTarget>() + .set_event_handler_common("scroll", listener) + } + } + + // https://html.spec.whatwg.org/multipage/#attr-itemtype + fn Itemtypes(&self) -> Option<Vec<DOMString>> { + let atoms = self + .element + .get_tokenlist_attribute(&local_name!("itemtype")); + + if atoms.is_empty() { + return None; + } + + let mut item_attr_values = HashSet::new(); + for attr_value in &atoms { + item_attr_values.insert(DOMString::from(String::from(attr_value.trim()))); + } + + Some(item_attr_values.into_iter().collect()) + } + + // https://html.spec.whatwg.org/multipage/#names:-the-itemprop-attribute + fn PropertyNames(&self) -> Option<Vec<DOMString>> { + let atoms = self + .element + .get_tokenlist_attribute(&local_name!("itemprop")); + + if atoms.is_empty() { + return None; } + + let mut item_attr_values = HashSet::new(); + for attr_value in &atoms { + item_attr_values.insert(DOMString::from(String::from(attr_value.trim()))); + } + + Some(item_attr_values.into_iter().collect()) } // https://html.spec.whatwg.org/multipage/#dom-click fn Click(&self) { - if !self.upcast::<Element>().disabled_state() { - synthetic_click_activation(self.upcast::<Element>(), - false, - false, - false, - false, - ActivationSource::FromClick) + let element = self.upcast::<Element>(); + if element.disabled_state() { + return; } + if element.click_in_progress() { + return; + } + element.set_click_in_progress(true); + + self.upcast::<Node>() + .fire_synthetic_mouse_event_not_trusted(DOMString::from("click")); + element.set_click_in_progress(false); } // https://html.spec.whatwg.org/multipage/#dom-focus @@ -296,9 +367,7 @@ impl HTMLElementMethods for HTMLElement { // TODO: Mark the element as locked for focus and run the focusing steps. // https://html.spec.whatwg.org/multipage/#focusing-steps let document = document_from_node(self); - document.begin_focus_transaction(); - document.request_focus(self.upcast()); - document.commit_focus_transaction(FocusType::Element); + document.request_focus(Some(self.upcast()), FocusType::Element); } // https://html.spec.whatwg.org/multipage/#dom-blur @@ -309,20 +378,18 @@ impl HTMLElementMethods for HTMLElement { } // https://html.spec.whatwg.org/multipage/#unfocusing-steps let document = document_from_node(self); - document.begin_focus_transaction(); - // If `request_focus` is not called, focus will be set to None. - document.commit_focus_transaction(FocusType::Element); + document.request_focus(None, FocusType::Element); } // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent - fn GetOffsetParent(&self) -> Option<Root<Element>> { + fn GetOffsetParent(&self) -> Option<DomRoot<Element>> { if self.is::<HTMLBodyElement>() || self.is::<HTMLHtmlElement>() { return None; } let node = self.upcast::<Node>(); let window = window_from_node(self); - let (element, _) = window.offset_parent_query(node.to_trusted_node_address()); + let (element, _) = window.offset_parent_query(node); element } @@ -335,7 +402,7 @@ impl HTMLElementMethods for HTMLElement { let node = self.upcast::<Node>(); let window = window_from_node(self); - let (_, rect) = window.offset_parent_query(node.to_trusted_node_address()); + let (_, rect) = window.offset_parent_query(node); rect.origin.y.to_nearest_px() } @@ -348,7 +415,7 @@ impl HTMLElementMethods for HTMLElement { let node = self.upcast::<Node>(); let window = window_from_node(self); - let (_, rect) = window.offset_parent_query(node.to_trusted_node_address()); + let (_, rect) = window.offset_parent_query(node); rect.origin.x.to_nearest_px() } @@ -357,7 +424,7 @@ impl HTMLElementMethods for HTMLElement { fn OffsetWidth(&self) -> i32 { let node = self.upcast::<Node>(); let window = window_from_node(self); - let (_, rect) = window.offset_parent_query(node.to_trusted_node_address()); + let (_, rect) = window.offset_parent_query(node); rect.size.width.to_nearest_px() } @@ -366,20 +433,144 @@ impl HTMLElementMethods for HTMLElement { fn OffsetHeight(&self) -> i32 { let node = self.upcast::<Node>(); let window = window_from_node(self); - let (_, rect) = window.offset_parent_query(node.to_trusted_node_address()); + let (_, rect) = window.offset_parent_query(node); rect.size.height.to_nearest_px() } + + // https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute + fn InnerText(&self) -> DOMString { + let node = self.upcast::<Node>(); + let window = window_from_node(node); + let element = self.upcast::<Element>(); + + // Step 1. + let element_not_rendered = !node.is_connected() || !element.has_css_layout_box(); + if element_not_rendered { + return node.GetTextContent().unwrap(); + } + + window.layout_reflow(QueryMsg::ElementInnerTextQuery( + node.to_trusted_node_address(), + )); + DOMString::from(window.layout().element_inner_text()) + } + + // https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute + fn SetInnerText(&self, input: DOMString) { + // Step 1. + let document = document_from_node(self); + + // Step 2. + let fragment = DocumentFragment::new(&document); + + // Step 3. The given value is already named 'input'. + + // Step 4. + let mut position = input.chars().peekable(); + + // Step 5. + let mut text = String::new(); + + // Step 6. + while let Some(ch) = position.next() { + match ch { + '\u{000A}' | '\u{000D}' => { + if ch == '\u{000D}' && position.peek() == Some(&'\u{000A}') { + // a \r\n pair should only generate one <br>, + // so just skip the \r. + position.next(); + } + + if !text.is_empty() { + append_text_node_to_fragment(&document, &fragment, text); + text = String::new(); + } + + let br = HTMLBRElement::new(local_name!("br"), None, &document); + fragment.upcast::<Node>().AppendChild(&br.upcast()).unwrap(); + }, + _ => { + text.push(ch); + }, + } + } + + if !text.is_empty() { + append_text_node_to_fragment(&document, &fragment, text); + } + + // Step 7. + Node::replace_all(Some(fragment.upcast()), self.upcast::<Node>()); + } + + // https://html.spec.whatwg.org/multipage/#dom-translate + fn Translate(&self) -> bool { + self.upcast::<Element>().is_translate_enabled() + } + + // https://html.spec.whatwg.org/multipage/#dom-translate + fn SetTranslate(&self, yesno: bool) { + self.upcast::<Element>().set_string_attribute( + // TODO change this to local_name! when html5ever updates + &LocalName::from("translate"), + match yesno { + true => DOMString::from("yes"), + false => DOMString::from("no"), + }, + ); + } + + // https://html.spec.whatwg.org/multipage/#dom-contenteditable + fn ContentEditable(&self) -> DOMString { + // TODO: https://github.com/servo/servo/issues/12776 + self.upcast::<Element>() + .get_attribute(&ns!(), &local_name!("contenteditable")) + .map(|attr| DOMString::from(&**attr.value())) + .unwrap_or_else(|| DOMString::from("inherit")) + } + + // https://html.spec.whatwg.org/multipage/#dom-contenteditable + fn SetContentEditable(&self, _: DOMString) { + // TODO: https://github.com/servo/servo/issues/12776 + warn!("The contentEditable attribute is not implemented yet"); + } + + // https://html.spec.whatwg.org/multipage/#dom-contenteditable + fn IsContentEditable(&self) -> bool { + // TODO: https://github.com/servo/servo/issues/12776 + false + } +} + +fn append_text_node_to_fragment(document: &Document, fragment: &DocumentFragment, text: String) { + let text = Text::new(DOMString::from(text), document); + fragment + .upcast::<Node>() + .AppendChild(&text.upcast()) + .unwrap(); } // https://html.spec.whatwg.org/multipage/#attr-data-* +static DATA_PREFIX: &str = "data-"; +static DATA_HYPHEN_SEPARATOR: char = '\x2d'; + +fn is_ascii_uppercase(c: char) -> bool { + 'A' <= c && c <= 'Z' +} + +fn is_ascii_lowercase(c: char) -> bool { + 'a' <= c && c <= 'w' +} + fn to_snake_case(name: DOMString) -> DOMString { - let mut attr_name = "data-".to_owned(); + let mut attr_name = String::with_capacity(name.len() + DATA_PREFIX.len()); + attr_name.push_str(DATA_PREFIX); for ch in name.chars() { - if ch.is_uppercase() { - attr_name.push('\x2d'); - attr_name.extend(ch.to_lowercase()); + if is_ascii_uppercase(ch) { + attr_name.push(DATA_HYPHEN_SEPARATOR); + attr_name.push(ch.to_ascii_lowercase()); } else { attr_name.push(ch); } @@ -387,30 +578,27 @@ fn to_snake_case(name: DOMString) -> DOMString { DOMString::from(attr_name) } - // https://html.spec.whatwg.org/multipage/#attr-data-* // if this attribute is in snake case with a data- prefix, // this function returns a name converted to camel case // without the data prefix. fn to_camel_case(name: &str) -> Option<DOMString> { - if !name.starts_with("data-") { + if !name.starts_with(DATA_PREFIX) { return None; } let name = &name[5..]; - let has_uppercase = name.chars().any(|curr_char| { - curr_char.is_ascii() && curr_char.is_uppercase() - }); + let has_uppercase = name.chars().any(|curr_char| is_ascii_uppercase(curr_char)); if has_uppercase { return None; } - let mut result = "".to_owned(); + let mut result = String::with_capacity(name.len().saturating_sub(DATA_PREFIX.len())); let mut name_chars = name.chars(); while let Some(curr_char) = name_chars.next() { //check for hyphen followed by character - if curr_char == '\x2d' { + if curr_char == DATA_HYPHEN_SEPARATOR { if let Some(next_char) = name_chars.next() { - if next_char.is_ascii() && next_char.is_lowercase() { + if is_ascii_lowercase(next_char) { result.push(next_char.to_ascii_uppercase()); } else { result.push(curr_char); @@ -428,44 +616,51 @@ fn to_camel_case(name: &str) -> Option<DOMString> { impl HTMLElement { pub fn set_custom_attr(&self, name: DOMString, value: DOMString) -> ErrorResult { - if name.chars() - .skip_while(|&ch| ch != '\u{2d}') - .nth(1).map_or(false, |ch| ch >= 'a' && ch <= 'z') { + if name + .chars() + .skip_while(|&ch| ch != '\u{2d}') + .nth(1) + .map_or(false, |ch| ch >= 'a' && ch <= 'z') + { return Err(Error::Syntax); } - self.upcast::<Element>().set_custom_attribute(to_snake_case(name), value) + self.upcast::<Element>() + .set_custom_attribute(to_snake_case(name), value) } pub fn get_custom_attr(&self, local_name: DOMString) -> Option<DOMString> { // FIXME(ajeffrey): Convert directly from DOMString to LocalName let local_name = LocalName::from(to_snake_case(local_name)); - self.upcast::<Element>().get_attribute(&ns!(), &local_name).map(|attr| { - DOMString::from(&**attr.value()) // FIXME(ajeffrey): Convert directly from AttrValue to DOMString - }) + self.upcast::<Element>() + .get_attribute(&ns!(), &local_name) + .map(|attr| { + DOMString::from(&**attr.value()) // FIXME(ajeffrey): Convert directly from AttrValue to DOMString + }) } pub fn delete_custom_attr(&self, local_name: DOMString) { // FIXME(ajeffrey): Convert directly from DOMString to LocalName let local_name = LocalName::from(to_snake_case(local_name)); - self.upcast::<Element>().remove_attribute(&ns!(), &local_name); + self.upcast::<Element>() + .remove_attribute(&ns!(), &local_name); } // https://html.spec.whatwg.org/multipage/#category-label pub fn is_labelable_element(&self) -> bool { // Note: HTMLKeygenElement is omitted because Servo doesn't currently implement it match self.upcast::<Node>().type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => - match type_id { - HTMLElementTypeId::HTMLInputElement => - self.downcast::<HTMLInputElement>().unwrap().type_() != atom!("hidden"), - HTMLElementTypeId::HTMLButtonElement | - HTMLElementTypeId::HTMLMeterElement | - HTMLElementTypeId::HTMLOutputElement | - HTMLElementTypeId::HTMLProgressElement | - HTMLElementTypeId::HTMLSelectElement | - HTMLElementTypeId::HTMLTextAreaElement => true, - _ => false, + NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id { + HTMLElementTypeId::HTMLInputElement => { + self.downcast::<HTMLInputElement>().unwrap().input_type() != InputType::Hidden }, + HTMLElementTypeId::HTMLButtonElement | + HTMLElementTypeId::HTMLMeterElement | + HTMLElementTypeId::HTMLOutputElement | + HTMLElementTypeId::HTMLProgressElement | + HTMLElementTypeId::HTMLSelectElement | + HTMLElementTypeId::HTMLTextAreaElement => true, + _ => false, + }, _ => false, } } @@ -479,72 +674,166 @@ impl HTMLElement { } match self.upcast::<Node>().type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => - match type_id { - HTMLElementTypeId::HTMLButtonElement | - HTMLElementTypeId::HTMLFieldSetElement | - HTMLElementTypeId::HTMLInputElement | - HTMLElementTypeId::HTMLObjectElement | - HTMLElementTypeId::HTMLOutputElement | - HTMLElementTypeId::HTMLSelectElement | - HTMLElementTypeId::HTMLTextAreaElement => true, - _ => false, - }, + NodeTypeId::Element(ElementTypeId::HTMLElement(type_id)) => match type_id { + HTMLElementTypeId::HTMLButtonElement | + HTMLElementTypeId::HTMLFieldSetElement | + HTMLElementTypeId::HTMLInputElement | + HTMLElementTypeId::HTMLObjectElement | + HTMLElementTypeId::HTMLOutputElement | + HTMLElementTypeId::HTMLSelectElement | + HTMLElementTypeId::HTMLTextAreaElement => true, + _ => false, + }, _ => false, } } pub fn supported_prop_names_custom_attr(&self) -> Vec<DOMString> { let element = self.upcast::<Element>(); - element.attrs().iter().filter_map(|attr| { - let raw_name = attr.local_name(); - to_camel_case(&raw_name) - }).collect() + element + .attrs() + .iter() + .filter_map(|attr| { + let raw_name = attr.local_name(); + to_camel_case(&raw_name) + }) + .collect() } // https://html.spec.whatwg.org/multipage/#dom-lfe-labels - pub fn labels(&self) -> Root<NodeList> { - debug_assert!(self.is_labelable_element()); + // This gets the nth label in tree order. + pub fn label_at(&self, index: u32) -> Option<DomRoot<Node>> { + let element = self.upcast::<Element>(); + + // Traverse entire tree for <label> elements that have + // this as their control. + // There is room for performance optimization, as we don't need + // the actual result of GetControl, only whether the result + // would match self. + // (Even more room for performance optimization: do what + // nodelist ChildrenList does and keep a mutation-aware cursor + // around; this may be hard since labels need to keep working + // even as they get detached into a subtree and reattached to + // a document.) + let root_element = element.root_element(); + let root_node = root_element.upcast::<Node>(); + root_node + .traverse_preorder(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<HTMLLabelElement>) + .filter(|elem| match elem.GetControl() { + Some(control) => &*control == self, + _ => false, + }) + .nth(index as usize) + .map(|n| DomRoot::from_ref(n.upcast::<Node>())) + } + // https://html.spec.whatwg.org/multipage/#dom-lfe-labels + // This counts the labels of the element, to support NodeList::Length + pub fn labels_count(&self) -> u32 { + // see label_at comments about performance let element = self.upcast::<Element>(); - let window = window_from_node(element); - - // Traverse ancestors for implicitly associated <label> elements - // https://html.spec.whatwg.org/multipage/#the-label-element:attr-label-for-4 - let ancestors = - self.upcast::<Node>() - .ancestors() - .filter_map(Root::downcast::<HTMLElement>) - // If we reach a labelable element, we have a guarantee no ancestors above it - // will be a label for this HTMLElement - .take_while(|elem| !elem.is_labelable_element()) - .filter_map(Root::downcast::<HTMLLabelElement>) - .filter(|elem| !elem.upcast::<Element>().has_attribute(&local_name!("for"))) - .filter(|elem| elem.first_labelable_descendant().r() == Some(self)) - .map(Root::upcast::<Node>); - - let id = element.Id(); - let id = match &id as &str { - "" => return NodeList::new_simple_list(&window, ancestors), - id => id, - }; - - // Traverse entire tree for <label> elements with `for` attribute matching `id` let root_element = element.root_element(); let root_node = root_element.upcast::<Node>(); - let children = root_node.traverse_preorder() - .filter_map(Root::downcast::<Element>) - .filter(|elem| elem.is::<HTMLLabelElement>()) - .filter(|elem| elem.get_string_attribute(&local_name!("for")) == id) - .map(Root::upcast::<Node>); + root_node + .traverse_preorder(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<HTMLLabelElement>) + .filter(|elem| match elem.GetControl() { + Some(control) => &*control == self, + _ => false, + }) + .count() as u32 + } + + // https://html.spec.whatwg.org/multipage/#the-directionality. + // returns Some if can infer direction by itself or from child nodes + // returns None if requires to go up to parent + pub fn directionality(&self) -> Option<String> { + let element_direction: &str = &self.Dir(); + + if element_direction == "ltr" { + return Some("ltr".to_owned()); + } + + if element_direction == "rtl" { + return Some("rtl".to_owned()); + } + + if let Some(input) = self.downcast::<HTMLInputElement>() { + if input.input_type() == InputType::Tel { + return Some("ltr".to_owned()); + } + } + + if element_direction == "auto" { + if let Some(directionality) = self + .downcast::<HTMLInputElement>() + .and_then(|input| input.auto_directionality()) + { + return Some(directionality); + } + + if let Some(area) = self.downcast::<HTMLTextAreaElement>() { + return Some(area.auto_directionality()); + } + } + + // TODO(NeverHappened): Implement condition + // If the element's dir attribute is in the auto state OR + // If the element is a bdi element and the dir attribute is not in a defined state + // (i.e. it is not present or has an invalid value) + // Requires bdi element implementation (https://html.spec.whatwg.org/multipage/#the-bdi-element) + + None + } + + // https://html.spec.whatwg.org/multipage/#the-summary-element:activation-behaviour + pub fn summary_activation_behavior(&self) { + // Step 1 + if !self.is_summary_for_its_parent_details() { + return; + } + + // Step 2 + let parent_details = self.upcast::<Node>().GetParentNode().unwrap(); + + // Step 3 + parent_details + .downcast::<HTMLDetailsElement>() + .unwrap() + .toggle(); + } + + // https://html.spec.whatwg.org/multipage/#summary-for-its-parent-details + fn is_summary_for_its_parent_details(&self) -> bool { + // Step 1 + let summary_node = self.upcast::<Node>(); + if !summary_node.has_parent() { + return false; + } + + // Step 2 + let parent = &summary_node.GetParentNode().unwrap(); - NodeList::new_simple_list(&window, children.chain(ancestors)) + // Step 3 + if !parent.is::<HTMLDetailsElement>() { + return false; + } + + // Step 4 & 5 + let first_summary_element = parent + .child_elements() + .find(|el| el.local_name() == &local_name!("summary")); + match first_summary_element { + Some(first_summary) => &*first_summary == self.upcast::<Element>(), + None => false, + } } } impl VirtualMethods for HTMLElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<Element>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<Element>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -553,20 +842,41 @@ impl VirtualMethods for HTMLElement { (name, AttributeMutation::Set(_)) if name.starts_with("on") => { let evtarget = self.upcast::<EventTarget>(); let source_line = 1; //TODO(#9604) get current JS execution line - evtarget.set_event_handler_uncompiled(window_from_node(self).get_url(), - source_line, - &name[2..], - // FIXME(ajeffrey): Convert directly from AttrValue to DOMString - DOMString::from(&**attr.value())); + evtarget.set_event_handler_uncompiled( + window_from_node(self).get_url(), + source_line, + &name[2..], + // FIXME(ajeffrey): Convert directly from AttrValue to DOMString + DOMString::from(&**attr.value()), + ); }, - _ => {} + _ => {}, } } - fn bind_to_tree(&self, tree_in_doc: bool) { - if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { + match name { + &local_name!("itemprop") => AttrValue::from_serialized_tokenlist(value.into()), + &local_name!("itemtype") => AttrValue::from_serialized_tokenlist(value.into()), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } - self.update_sequentially_focusable_status(); + } +} + +impl Activatable for HTMLElement { + fn as_element(&self) -> &Element { + self.upcast::<Element>() + } + + fn is_instance_activatable(&self) -> bool { + self.as_element().local_name() == &local_name!("summary") + } + + // Basically used to make the HTMLSummaryElement activatable (which has no IDL definition) + fn activation_behavior(&self, _event: &Event, _target: &EventTarget) { + self.summary_activation_behavior(); } } diff --git a/components/script/dom/htmlembedelement.rs b/components/script/dom/htmlembedelement.rs index 9d88e7d7c12..e21b8f5ce35 100644 --- a/components/script/dom/htmlembedelement.rs +++ b/components/script/dom/htmlembedelement.rs @@ -1,34 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLEmbedElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLEmbedElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLEmbedElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLEmbedElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLEmbedElement { HTMLEmbedElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLEmbedElement> { - Node::reflect_node(box HTMLEmbedElement::new_inherited(local_name, prefix, document), - document, - HTMLEmbedElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLEmbedElement> { + Node::reflect_node( + Box::new(HTMLEmbedElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs index 9fdc65a513c..cefee026751 100644 --- a/components/script/dom/htmlfieldsetelement.rs +++ b/components/script/dom/htmlfieldsetelement.rs @@ -1,59 +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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding; -use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods; -use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::htmlcollection::{CollectionFilter, HTMLCollection}; -use dom::htmlelement::HTMLElement; -use dom::htmlformelement::{FormControl, HTMLFormElement}; -use dom::htmllegendelement::HTMLLegendElement; -use dom::node::{Node, window_from_node}; -use dom::validitystate::ValidityState; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods; +use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; +use crate::dom::htmllegendelement::HTMLLegendElement; +use crate::dom::node::{window_from_node, Node, ShadowIncluding}; +use crate::dom::validation::Validatable; +use crate::dom::validitystate::ValidityState; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use std::default::Default; -use style::element_state::*; +use style::element_state::ElementState; #[dom_struct] pub struct HTMLFieldSetElement { htmlelement: HTMLElement, - form_owner: MutNullableJS<HTMLFormElement>, + form_owner: MutNullableDom<HTMLFormElement>, + validity_state: MutNullableDom<ValidityState>, } impl HTMLFieldSetElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLFieldSetElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLFieldSetElement { HTMLFieldSetElement { - htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, - local_name, prefix, document), + htmlelement: HTMLElement::new_inherited_with_state( + ElementState::IN_ENABLED_STATE, + local_name, + prefix, + document, + ), form_owner: Default::default(), + validity_state: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLFieldSetElement> { - Node::reflect_node(box HTMLFieldSetElement::new_inherited(local_name, prefix, document), - document, - HTMLFieldSetElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLFieldSetElement> { + Node::reflect_node( + Box::new(HTMLFieldSetElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } impl HTMLFieldSetElementMethods for HTMLFieldSetElement { // https://html.spec.whatwg.org/multipage/#dom-fieldset-elements - fn Elements(&self) -> Root<HTMLCollection> { - #[derive(JSTraceable, HeapSizeOf)] + fn Elements(&self) -> DomRoot<HTMLCollection> { + #[derive(JSTraceable, MallocSizeOf)] struct ElementsFilter; impl CollectionFilter for ElementsFilter { fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool { @@ -61,32 +73,62 @@ impl HTMLFieldSetElementMethods for HTMLFieldSetElement { .map_or(false, HTMLElement::is_listed_element) } } - let filter = box ElementsFilter; + let filter = Box::new(ElementsFilter); let window = window_from_node(self); HTMLCollection::create(&window, self.upcast(), filter) } - // https://html.spec.whatwg.org/multipage/#dom-cva-validity - fn Validity(&self) -> Root<ValidityState> { - let window = window_from_node(self); - ValidityState::new(&window, self.upcast()) - } - // https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled make_bool_getter!(Disabled, "disabled"); // https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled make_bool_setter!(SetDisabled, "disabled"); + // https://html.spec.whatwg.org/multipage/#dom-fe-name + make_atomic_setter!(SetName, "name"); + + // https://html.spec.whatwg.org/multipage/#dom-fe-name + make_getter!(Name, "name"); + // https://html.spec.whatwg.org/multipage/#dom-fae-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner() } + + // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate + fn WillValidate(&self) -> bool { + self.is_instance_validatable() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validity + fn Validity(&self) -> DomRoot<ValidityState> { + self.validity_state() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity + fn CheckValidity(&self) -> bool { + self.check_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity + fn ReportValidity(&self) -> bool { + self.report_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage + fn ValidationMessage(&self) -> DOMString { + self.validation_message() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity + fn SetCustomValidity(&self, error: DOMString) { + self.validity_state().set_custom_error_message(error); + } } impl VirtualMethods for HTMLFieldSetElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -117,39 +159,40 @@ impl VirtualMethods for HTMLFieldSetElement { } }); let fields = children.flat_map(|child| { - child.traverse_preorder().filter(|descendant| { - match descendant.type_id() { - NodeTypeId::Element( - ElementTypeId::HTMLElement( - HTMLElementTypeId::HTMLButtonElement)) | - NodeTypeId::Element( - ElementTypeId::HTMLElement( - HTMLElementTypeId::HTMLInputElement)) | - NodeTypeId::Element( - ElementTypeId::HTMLElement( - HTMLElementTypeId::HTMLSelectElement)) | - NodeTypeId::Element( - ElementTypeId::HTMLElement( - HTMLElementTypeId::HTMLTextAreaElement)) => { - true - }, + child + .traverse_preorder(ShadowIncluding::No) + .filter(|descendant| match descendant.type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLButtonElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLSelectElement, + )) | + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement, + )) => true, _ => false, - } - }) + }) }); if disabled_state { for field in fields { let el = field.downcast::<Element>().unwrap(); el.set_disabled_state(true); el.set_enabled_state(false); + el.update_sequentially_focusable_status(); } } else { for field in fields { let el = field.downcast::<Element>().unwrap(); el.check_disabled_attribute(); el.check_ancestors_disabled_state_for_form_control(); + el.update_sequentially_focusable_status(); } } + el.update_sequentially_focusable_status(); }, &local_name!("form") => { self.form_attribute_mutated(mutation); @@ -160,7 +203,7 @@ impl VirtualMethods for HTMLFieldSetElement { } impl FormControl for HTMLFieldSetElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } @@ -172,3 +215,19 @@ impl FormControl for HTMLFieldSetElement { self.upcast::<Element>() } } + +impl Validatable for HTMLFieldSetElement { + fn as_element(&self) -> &Element { + self.upcast() + } + + fn validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), self.upcast())) + } + + fn is_instance_validatable(&self) -> bool { + // fieldset is not a submittable element (https://html.spec.whatwg.org/multipage/#category-submit) + false + } +} diff --git a/components/script/dom/htmlfontelement.rs b/components/script/dom/htmlfontelement.rs index 4e6b96a9c7e..f9b1575577e 100644 --- a/components/script/dom/htmlfontelement.rs +++ b/components/script/dom/htmlfontelement.rs @@ -1,44 +1,50 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLFontElementBinding::HTMLFontElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{Element, LayoutElementHelpers}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use crate::dom::virtualmethods::VirtualMethods; use cssparser::RGBA; -use dom::bindings::codegen::Bindings::HTMLFontElementBinding; -use dom::bindings::codegen::Bindings::HTMLFontElementBinding::HTMLFontElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{Element, RawLayoutElementHelpers}; -use dom::htmlelement::HTMLElement; -use dom::node::Node; -use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use servo_atoms::Atom; use style::attr::AttrValue; -use style::str::{HTML_SPACE_CHARACTERS, read_numbers}; +use style::str::{read_numbers, HTML_SPACE_CHARACTERS}; #[dom_struct] pub struct HTMLFontElement { htmlelement: HTMLElement, } - impl HTMLFontElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLFontElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLFontElement { HTMLFontElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLFontElement> { - Node::reflect_node(box HTMLFontElement::new_inherited(local_name, prefix, document), - document, - HTMLFontElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLFontElement> { + Node::reflect_node( + Box::new(HTMLFontElement::new_inherited(local_name, prefix, document)), + document, + ) } } @@ -66,8 +72,19 @@ impl HTMLFontElementMethods for HTMLFontElement { } impl VirtualMethods for HTMLFontElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) + } + + fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool { + if attr.local_name() == &local_name!("color") { + return true; + } + + // FIXME: Should also return true for `size` and `face` changes! + self.super_type() + .unwrap() + .attribute_affects_presentational_hints(attr) } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { @@ -75,44 +92,39 @@ impl VirtualMethods for HTMLFontElement { &local_name!("face") => AttrValue::from_atomic(value.into()), &local_name!("color") => AttrValue::from_legacy_color(value.into()), &local_name!("size") => parse_size(&value), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } } pub trait HTMLFontElementLayoutHelpers { - fn get_color(&self) -> Option<RGBA>; - fn get_face(&self) -> Option<Atom>; - fn get_size(&self) -> Option<u32>; + fn get_color(self) -> Option<RGBA>; + fn get_face(self) -> Option<Atom>; + fn get_size(self) -> Option<u32>; } -impl HTMLFontElementLayoutHelpers for LayoutJS<HTMLFontElement> { - #[allow(unsafe_code)] - fn get_color(&self) -> Option<RGBA> { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("color")) - .and_then(AttrValue::as_color) - .cloned() - } +impl HTMLFontElementLayoutHelpers for LayoutDom<'_, HTMLFontElement> { + fn get_color(self) -> Option<RGBA> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("color")) + .and_then(AttrValue::as_color) + .cloned() } - #[allow(unsafe_code)] - fn get_face(&self) -> Option<Atom> { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("face")) - .map(AttrValue::as_atom) - .cloned() - } + fn get_face(self) -> Option<Atom> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("face")) + .map(AttrValue::as_atom) + .cloned() } - #[allow(unsafe_code)] - fn get_size(&self) -> Option<u32> { - let size = unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("size")) - }; + fn get_size(self) -> Option<u32> { + let size = self + .upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("size")); match size { Some(&AttrValue::UInt(_, s)) => Some(s), _ => None, @@ -120,7 +132,7 @@ impl HTMLFontElementLayoutHelpers for LayoutJS<HTMLFontElement> { } } -/// https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size +/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size> fn parse_size(mut input: &str) -> AttrValue { let original_input = input; // Steps 1 & 2 are not relevant @@ -140,13 +152,13 @@ fn parse_size(mut input: &str) -> AttrValue { // Step 5 Some(&'+') => { - let _ = input_chars.next(); // consume the '+' + let _ = input_chars.next(); // consume the '+' ParseMode::RelativePlus - } + }, Some(&'-') => { - let _ = input_chars.next(); // consume the '-' + let _ = input_chars.next(); // consume the '-' ParseMode::RelativeMinus - } + }, Some(_) => ParseMode::Absolute, }; diff --git a/components/script/dom/htmlformcontrolscollection.rs b/components/script/dom/htmlformcontrolscollection.rs index a90e2dd48ee..f687870b058 100644 --- a/components/script/dom/htmlformcontrolscollection.rs +++ b/components/script/dom/htmlformcontrolscollection.rs @@ -1,44 +1,58 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; -use dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding; -use dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods; -use dom::bindings::codegen::UnionTypes::RadioNodeListOrElement; -use dom::bindings::js::Root; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::element::Element; -use dom::htmlcollection::{CollectionFilter, HTMLCollection}; -use dom::node::Node; -use dom::radionodelist::RadioNodeList; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; +use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; +use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::element::Element; +use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; +use crate::dom::htmlformelement::HTMLFormElement; +use crate::dom::node::Node; +use crate::dom::radionodelist::RadioNodeList; +use crate::dom::window::Window; use dom_struct::dom_struct; -use std::iter; +use servo_atoms::Atom; #[dom_struct] pub struct HTMLFormControlsCollection { collection: HTMLCollection, + form: Dom<HTMLFormElement>, } impl HTMLFormControlsCollection { - fn new_inherited(root: &Node, filter: Box<CollectionFilter + 'static>) -> HTMLFormControlsCollection { + fn new_inherited( + form: &HTMLFormElement, + filter: Box<dyn CollectionFilter + 'static>, + ) -> HTMLFormControlsCollection { + let root_of_form = form + .upcast::<Node>() + .GetRootNode(&GetRootNodeOptions::empty()); HTMLFormControlsCollection { - collection: HTMLCollection::new_inherited(root, filter) + collection: HTMLCollection::new_inherited(&*root_of_form, filter), + form: Dom::from_ref(form), } } - pub fn new(window: &Window, root: &Node, filter: Box<CollectionFilter + 'static>) - -> Root<HTMLFormControlsCollection> - { - reflect_dom_object(box HTMLFormControlsCollection::new_inherited(root, filter), - window, - HTMLFormControlsCollectionBinding::Wrap) + pub fn new( + window: &Window, + form: &HTMLFormElement, + filter: Box<dyn CollectionFilter + 'static>, + ) -> DomRoot<HTMLFormControlsCollection> { + reflect_dom_object( + Box::new(HTMLFormControlsCollection::new_inherited(form, filter)), + window, + ) } // FIXME: This shouldn't need to be implemented here since HTMLCollection (the parent of // HTMLFormControlsCollection) implements Length + #[allow(non_snake_case)] pub fn Length(&self) -> u32 { self.collection.Length() } @@ -48,13 +62,20 @@ impl HTMLFormControlsCollectionMethods for HTMLFormControlsCollection { // https://html.spec.whatwg.org/multipage/#dom-htmlformcontrolscollection-nameditem fn NamedItem(&self, name: DOMString) -> Option<RadioNodeListOrElement> { // Step 1 - if name.is_empty() { return None; } + if name.is_empty() { + return None; + } + + let name = Atom::from(name); let mut filter_map = self.collection.elements_iter().filter_map(|elem| { - if elem.get_string_attribute(&local_name!("name")) == name - || elem.get_string_attribute(&local_name!("id")) == name { + if elem.get_name().map_or(false, |n| n == name) || + elem.get_id().map_or(false, |i| i == name) + { Some(elem) - } else { None } + } else { + None + } }); if let Some(elem) = filter_map.next() { @@ -64,15 +85,19 @@ impl HTMLFormControlsCollectionMethods for HTMLFormControlsCollection { Some(RadioNodeListOrElement::Element(elem)) } else { // Step 4-5 - let once = iter::once(Root::upcast::<Node>(elem)); - let list = once.chain(peekable.map(Root::upcast)); let global = self.global(); let window = global.as_window(); - Some(RadioNodeListOrElement::RadioNodeList(RadioNodeList::new_simple_list(window, list))) + // There is only one way to get an HTMLCollection, + // specifically HTMLFormElement::Elements(), + // and the collection filter excludes image inputs. + Some(RadioNodeListOrElement::RadioNodeList( + RadioNodeList::new_controls_except_image_inputs(window, &*self.form, &name), + )) } // Step 3 - } else { None } - + } else { + None + } } // https://html.spec.whatwg.org/multipage/#dom-htmlformcontrolscollection-nameditem @@ -90,7 +115,7 @@ impl HTMLFormControlsCollectionMethods for HTMLFormControlsCollection { // https://github.com/servo/servo/issues/5875 // // https://dom.spec.whatwg.org/#dom-htmlcollection-item - fn IndexedGetter(&self, index: u32) -> Option<Root<Element>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> { self.collection.IndexedGetter(index) } } diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index afcfd9c6690..d06fd85a426 100755..100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -1,96 +1,184 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods; -use dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods; -use dom::bindings::codegen::Bindings::HTMLFormElementBinding; -use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods; -use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; -use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; -use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; -use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::blob::Blob; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::eventtarget::EventTarget; -use dom::file::File; -use dom::globalscope::GlobalScope; -use dom::htmlbuttonelement::HTMLButtonElement; -use dom::htmlcollection::CollectionFilter; -use dom::htmldatalistelement::HTMLDataListElement; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlformcontrolscollection::HTMLFormControlsCollection; -use dom::htmlimageelement::HTMLImageElement; -use dom::htmlinputelement::HTMLInputElement; -use dom::htmllabelelement::HTMLLabelElement; -use dom::htmllegendelement::HTMLLegendElement; -use dom::htmlobjectelement::HTMLObjectElement; -use dom::htmloutputelement::HTMLOutputElement; -use dom::htmlselectelement::HTMLSelectElement; -use dom::htmltextareaelement::HTMLTextAreaElement; -use dom::node::{Node, PARSER_ASSOCIATED_FORM_OWNER, UnbindContext, VecPreOrderInsertionHelper}; -use dom::node::{document_from_node, window_from_node}; -use dom::validitystate::ValidationFlags; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::body::Extractable; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrBinding::AttrMethods; +use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods; +use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::blob::Blob; +use crate::dom::document::Document; +use crate::dom::domtokenlist::DOMTokenList; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::file::File; +use crate::dom::formdata::FormData; +use crate::dom::formdataevent::FormDataEvent; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlanchorelement::{get_element_noopener, get_element_target}; +use crate::dom::htmlbuttonelement::HTMLButtonElement; +use crate::dom::htmlcollection::CollectionFilter; +use crate::dom::htmldatalistelement::HTMLDataListElement; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlformcontrolscollection::HTMLFormControlsCollection; +use crate::dom::htmlimageelement::HTMLImageElement; +use crate::dom::htmlinputelement::{HTMLInputElement, InputType}; +use crate::dom::htmllabelelement::HTMLLabelElement; +use crate::dom::htmllegendelement::HTMLLegendElement; +use crate::dom::htmlobjectelement::HTMLObjectElement; +use crate::dom::htmloutputelement::HTMLOutputElement; +use crate::dom::htmlselectelement::HTMLSelectElement; +use crate::dom::htmltextareaelement::HTMLTextAreaElement; +use crate::dom::node::{document_from_node, window_from_node}; +use crate::dom::node::{Node, NodeFlags}; +use crate::dom::node::{UnbindContext, VecPreOrderInsertionHelper}; +use crate::dom::nodelist::{NodeList, RadioListMode}; +use crate::dom::radionodelist::RadioNodeList; +use crate::dom::submitevent::SubmitEvent; +use crate::dom::validitystate::ValidationFlags; +use crate::dom::virtualmethods::VirtualMethods; +use crate::dom::window::Window; +use crate::task_source::TaskSource; use dom_struct::dom_struct; -use encoding::EncodingRef; -use encoding::all::UTF_8; -use encoding::label::encoding_from_whatwg_label; -use html5ever_atoms::LocalName; -use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType}; -use hyper::method::Method; -use msg::constellation_msg::PipelineId; -use script_thread::{MainThreadScriptMsg, Runnable}; -use script_traits::LoadData; +use encoding_rs::{Encoding, UTF_8}; +use headers::{ContentType, HeaderMapExt}; +use html5ever::{LocalName, Prefix}; +use hyper::Method; +use mime::{self, Mime}; +use net_traits::http_percent_encode; +use net_traits::request::Referrer; +use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin}; +use servo_atoms::Atom; use servo_rand::random; use std::borrow::ToOwned; use std::cell::Cell; -use std::sync::mpsc::Sender; use style::attr::AttrValue; use style::str::split_html_space_chars; -use task_source::TaskSource; -#[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)] +use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement; +use std::collections::HashMap; +use time::{now, Duration, Tm}; + +use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods}; + +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub struct GenerationId(u32); #[dom_struct] pub struct HTMLFormElement { htmlelement: HTMLElement, marked_for_reset: Cell<bool>, - elements: MutNullableJS<HTMLFormControlsCollection>, + /// https://html.spec.whatwg.org/multipage/#constructing-entry-list + constructing_entry_list: Cell<bool>, + elements: DomOnceCell<HTMLFormControlsCollection>, generation_id: Cell<GenerationId>, - controls: DOMRefCell<Vec<JS<Element>>>, + controls: DomRefCell<Vec<Dom<Element>>>, + past_names_map: DomRefCell<HashMap<Atom, (Dom<Element>, Tm)>>, + firing_submission_events: Cell<bool>, + rel_list: MutNullableDom<DOMTokenList>, } impl HTMLFormElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLFormElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLFormElement { HTMLFormElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), marked_for_reset: Cell::new(false), + constructing_entry_list: Cell::new(false), elements: Default::default(), generation_id: Cell::new(GenerationId(0)), - controls: DOMRefCell::new(Vec::new()), + controls: DomRefCell::new(Vec::new()), + past_names_map: DomRefCell::new(HashMap::new()), + firing_submission_events: Cell::new(false), + rel_list: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLFormElement> { - Node::reflect_node(box HTMLFormElement::new_inherited(local_name, prefix, document), - document, - HTMLFormElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLFormElement> { + Node::reflect_node( + Box::new(HTMLFormElement::new_inherited(local_name, prefix, document)), + document, + ) + } + + fn filter_for_radio_list(mode: RadioListMode, child: &Element, name: &Atom) -> bool { + if let Some(child) = child.downcast::<Element>() { + match mode { + RadioListMode::ControlsExceptImageInputs => { + if child + .downcast::<HTMLElement>() + .map_or(false, |c| c.is_listed_element()) + { + if child.get_id().map_or(false, |i| i == *name) || + child.get_name().map_or(false, |n| n == *name) + { + if let Some(inp) = child.downcast::<HTMLInputElement>() { + // input, only return it if it's not image-button state + return inp.input_type() != InputType::Image; + } else { + // control, but not an input + return true; + } + } + } + return false; + }, + RadioListMode::Images => { + return child.is::<HTMLImageElement>() && + (child.get_id().map_or(false, |i| i == *name) || + child.get_name().map_or(false, |n| n == *name)); + }, + } + } + false + } + + pub fn nth_for_radio_list( + &self, + index: u32, + mode: RadioListMode, + name: &Atom, + ) -> Option<DomRoot<Node>> { + self.controls + .borrow() + .iter() + .filter(|n| HTMLFormElement::filter_for_radio_list(mode, &*n, name)) + .nth(index as usize) + .and_then(|n| Some(DomRoot::from_ref(n.upcast::<Node>()))) + } + + pub fn count_for_radio_list(&self, mode: RadioListMode, name: &Atom) -> u32 { + self.controls + .borrow() + .iter() + .filter(|n| HTMLFormElement::filter_for_radio_list(mode, &**n, name)) + .count() as u32 } } @@ -102,7 +190,7 @@ impl HTMLFormElementMethods for HTMLFormElement { make_setter!(SetAcceptCharset, "accept-charset"); // https://html.spec.whatwg.org/multipage/#dom-fs-action - make_string_or_document_url_getter!(Action, "action"); + make_form_action_getter!(Action, "action"); // https://html.spec.whatwg.org/multipage/#dom-fs-action make_setter!(SetAction, "action"); @@ -114,10 +202,12 @@ impl HTMLFormElementMethods for HTMLFormElement { make_setter!(SetAutocomplete, "autocomplete"); // https://html.spec.whatwg.org/multipage/#dom-fs-enctype - make_enumerated_getter!(Enctype, - "enctype", - "application/x-www-form-urlencoded", - "text/plain" | "multipart/form-data"); + make_enumerated_getter!( + Enctype, + "enctype", + "application/x-www-form-urlencoded", + "text/plain" | "multipart/form-data" + ); // https://html.spec.whatwg.org/multipage/#dom-fs-enctype make_setter!(SetEnctype, "enctype"); @@ -156,63 +246,123 @@ impl HTMLFormElementMethods for HTMLFormElement { // https://html.spec.whatwg.org/multipage/#dom-fs-target make_setter!(SetTarget, "target"); + // https://html.spec.whatwg.org/multipage/#dom-a-rel + make_getter!(Rel, "rel"); + // https://html.spec.whatwg.org/multipage/#the-form-element:concept-form-submit fn Submit(&self) { self.submit(SubmittedFrom::FromForm, FormSubmitter::FormElement(self)); } + // https://html.spec.whatwg.org/multipage/#dom-form-requestsubmit + fn RequestSubmit(&self, submitter: Option<&HTMLElement>) -> Fallible<()> { + let submitter: FormSubmitter = match submitter { + Some(submitter_element) => { + // Step 1.1 + let error_not_a_submit_button = + Err(Error::Type("submitter must be a submit button".to_string())); + + let element = match submitter_element.upcast::<Node>().type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element, + _ => { + return error_not_a_submit_button; + }, + }; + + let submit_button = match element { + HTMLElementTypeId::HTMLInputElement => FormSubmitter::InputElement( + &submitter_element + .downcast::<HTMLInputElement>() + .expect("Failed to downcast submitter elem to HTMLInputElement."), + ), + HTMLElementTypeId::HTMLButtonElement => FormSubmitter::ButtonElement( + &submitter_element + .downcast::<HTMLButtonElement>() + .expect("Failed to downcast submitter elem to HTMLButtonElement."), + ), + _ => { + return error_not_a_submit_button; + }, + }; + + if !submit_button.is_submit_button() { + return error_not_a_submit_button; + } + + let submitters_owner = submit_button.form_owner(); + + // Step 1.2 + let owner = match submitters_owner { + Some(owner) => owner, + None => { + return Err(Error::NotFound); + }, + }; + + if *owner != *self { + return Err(Error::NotFound); + } + + submit_button + }, + None => { + // Step 2 + FormSubmitter::FormElement(&self) + }, + }; + // Step 3 + self.submit(SubmittedFrom::NotFromForm, submitter); + Ok(()) + } + // https://html.spec.whatwg.org/multipage/#dom-form-reset fn Reset(&self) { self.reset(ResetFrom::FromForm); } // https://html.spec.whatwg.org/multipage/#dom-form-elements - fn Elements(&self) -> Root<HTMLFormControlsCollection> { - if let Some(elements) = self.elements.get() { - return elements; - } - - #[derive(JSTraceable, HeapSizeOf)] + fn Elements(&self) -> DomRoot<HTMLFormControlsCollection> { + #[derive(JSTraceable, MallocSizeOf)] struct ElementsFilter { - form: Root<HTMLFormElement> + form: DomRoot<HTMLFormElement>, } impl CollectionFilter for ElementsFilter { fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool { let form_owner = match elem.upcast::<Node>().type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => { - match t { - HTMLElementTypeId::HTMLButtonElement => { - elem.downcast::<HTMLButtonElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLFieldSetElement => { - elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLInputElement => { - let input_elem = elem.downcast::<HTMLInputElement>().unwrap(); - if input_elem.type_() == atom!("image") { - return false; - } - input_elem.form_owner() - } - HTMLElementTypeId::HTMLObjectElement => { - elem.downcast::<HTMLObjectElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLOutputElement => { - elem.downcast::<HTMLOutputElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLSelectElement => { - elem.downcast::<HTMLSelectElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLTextAreaElement => { - elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner() - } - _ => { - debug_assert!(!elem.downcast::<HTMLElement>().unwrap().is_listed_element() || - elem.local_name() == &local_name!("keygen")); + NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t { + HTMLElementTypeId::HTMLButtonElement => { + elem.downcast::<HTMLButtonElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLFieldSetElement => { + elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLInputElement => { + let input_elem = elem.downcast::<HTMLInputElement>().unwrap(); + if input_elem.input_type() == InputType::Image { return false; } - } - } + input_elem.form_owner() + }, + HTMLElementTypeId::HTMLObjectElement => { + elem.downcast::<HTMLObjectElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLOutputElement => { + elem.downcast::<HTMLOutputElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLSelectElement => { + elem.downcast::<HTMLSelectElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLTextAreaElement => { + elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner() + }, + _ => { + debug_assert!( + !elem.downcast::<HTMLElement>().unwrap().is_listed_element() || + elem.local_name() == &local_name!("keygen") + ); + return false; + }, + }, _ => return false, }; @@ -222,11 +372,13 @@ impl HTMLFormElementMethods for HTMLFormElement { } } } - let filter = box ElementsFilter { form: Root::from_ref(self) }; - let window = window_from_node(self); - let elements = HTMLFormControlsCollection::new(&window, self.upcast(), filter); - self.elements.set(Some(&elements)); - elements + DomRoot::from_ref(self.elements.init_once(|| { + let filter = Box::new(ElementsFilter { + form: DomRoot::from_ref(self), + }); + let window = window_from_node(self); + HTMLFormControlsCollection::new(&window, self, filter) + })) } // https://html.spec.whatwg.org/multipage/#dom-form-length @@ -235,35 +387,263 @@ impl HTMLFormElementMethods for HTMLFormElement { } // https://html.spec.whatwg.org/multipage/#dom-form-item - fn IndexedGetter(&self, index: u32) -> Option<Root<Element>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> { let elements = self.Elements(); elements.IndexedGetter(index) } + + // https://html.spec.whatwg.org/multipage/#the-form-element%3Adetermine-the-value-of-a-named-property + fn NamedGetter(&self, name: DOMString) -> Option<RadioNodeListOrElement> { + let window = window_from_node(self); + + let name = Atom::from(name); + + // Step 1 + let mut candidates = RadioNodeList::new_controls_except_image_inputs(&window, self, &name); + let mut candidates_length = candidates.Length(); + + // Step 2 + if candidates_length == 0 { + candidates = RadioNodeList::new_images(&window, self, &name); + candidates_length = candidates.Length(); + } + + let mut past_names_map = self.past_names_map.borrow_mut(); + + // Step 3 + if candidates_length == 0 { + if past_names_map.contains_key(&name) { + return Some(RadioNodeListOrElement::Element(DomRoot::from_ref( + &*past_names_map.get(&name).unwrap().0, + ))); + } + return None; + } + + // Step 4 + if candidates_length > 1 { + return Some(RadioNodeListOrElement::RadioNodeList(candidates)); + } + + // Step 5 + // candidates_length is 1, so we can unwrap item 0 + let element_node = candidates.upcast::<NodeList>().Item(0).unwrap(); + past_names_map.insert( + name, + ( + Dom::from_ref(&*element_node.downcast::<Element>().unwrap()), + now(), + ), + ); + + // Step 6 + return Some(RadioNodeListOrElement::Element(DomRoot::from_ref( + &*element_node.downcast::<Element>().unwrap(), + ))); + } + + // https://html.spec.whatwg.org/multipage/#dom-a-rel + fn SetRel(&self, rel: DOMString) { + self.upcast::<Element>() + .set_tokenlist_attribute(&local_name!("rel"), rel); + } + + // https://html.spec.whatwg.org/multipage/#dom-a-rellist + fn RelList(&self) -> DomRoot<DOMTokenList> { + self.rel_list.or_init(|| { + DOMTokenList::new( + self.upcast(), + &local_name!("rel"), + Some(vec![ + Atom::from("noopener"), + Atom::from("noreferrer"), + Atom::from("opener"), + ]), + ) + }) + } + + // https://html.spec.whatwg.org/multipage/#the-form-element:supported-property-names + #[allow(non_snake_case)] + fn SupportedPropertyNames(&self) -> Vec<DOMString> { + // Step 1 + #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] + enum SourcedNameSource { + Id, + Name, + Past(Duration), + } + + impl SourcedNameSource { + fn is_past(&self) -> bool { + match self { + SourcedNameSource::Past(..) => true, + _ => false, + } + } + } + + struct SourcedName { + name: Atom, + element: DomRoot<Element>, + source: SourcedNameSource, + } + + let mut sourced_names_vec: Vec<SourcedName> = Vec::new(); + + let controls = self.controls.borrow(); + + // Step 2 + for child in controls.iter() { + if child + .downcast::<HTMLElement>() + .map_or(false, |c| c.is_listed_element()) + { + if let Some(id_atom) = child.get_id() { + let entry = SourcedName { + name: id_atom, + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Id, + }; + sourced_names_vec.push(entry); + } + if let Some(name_atom) = child.get_name() { + let entry = SourcedName { + name: name_atom, + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Name, + }; + sourced_names_vec.push(entry); + } + } + } + + // Step 3 + for child in controls.iter() { + if child.is::<HTMLImageElement>() { + if let Some(id_atom) = child.get_id() { + let entry = SourcedName { + name: id_atom, + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Id, + }; + sourced_names_vec.push(entry); + } + if let Some(name_atom) = child.get_name() { + let entry = SourcedName { + name: name_atom, + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Name, + }; + sourced_names_vec.push(entry); + } + } + } + + // Step 4 + let past_names_map = self.past_names_map.borrow(); + for (key, val) in past_names_map.iter() { + let entry = SourcedName { + name: key.clone(), + element: DomRoot::from_ref(&*val.0), + source: SourcedNameSource::Past(now() - val.1), // calculate difference now()-val.1 to find age + }; + sourced_names_vec.push(entry); + } + + // Step 5 + // TODO need to sort as per spec. + // if a.CompareDocumentPosition(b) returns 0 that means a=b in which case + // the remaining part where sorting is to be done by putting entries whose source is id first, + // then entries whose source is name, and finally entries whose source is past, + // and sorting entries with the same element and source by their age, oldest first. + + // if a.CompareDocumentPosition(b) has set NodeConstants::DOCUMENT_POSITION_FOLLOWING + // (this can be checked by bitwise operations) then b would follow a in tree order and + // Ordering::Less should be returned in the closure else Ordering::Greater + + sourced_names_vec.sort_by(|a, b| { + if a.element + .upcast::<Node>() + .CompareDocumentPosition(b.element.upcast::<Node>()) == + 0 + { + if a.source.is_past() && b.source.is_past() { + b.source.cmp(&a.source) + } else { + a.source.cmp(&b.source) + } + } else { + if a.element + .upcast::<Node>() + .CompareDocumentPosition(b.element.upcast::<Node>()) & + NodeConstants::DOCUMENT_POSITION_FOLLOWING == + NodeConstants::DOCUMENT_POSITION_FOLLOWING + { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + } + }); + + // Step 6 + sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty()); + + // Step 7-8 + let mut names_vec: Vec<DOMString> = Vec::new(); + for elem in sourced_names_vec.iter() { + if names_vec + .iter() + .find(|name| &**name == &*elem.name) + .is_none() + { + names_vec.push(DOMString::from(&*elem.name)); + } + } + + return names_vec; + } + + /// https://html.spec.whatwg.org/multipage/#dom-form-checkvalidity + fn CheckValidity(&self) -> bool { + self.static_validation().is_ok() + } + + /// https://html.spec.whatwg.org/multipage/#dom-form-reportvalidity + fn ReportValidity(&self) -> bool { + self.interactive_validation().is_ok() + } } -#[derive(Copy, Clone, HeapSizeOf, PartialEq)] +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub enum SubmittedFrom { FromForm, - NotFromForm + NotFromForm, } -#[derive(Copy, Clone, HeapSizeOf)] +#[derive(Clone, Copy, MallocSizeOf)] pub enum ResetFrom { FromForm, - NotFromForm + NotFromForm, } - impl HTMLFormElement { // https://html.spec.whatwg.org/multipage/#picking-an-encoding-for-the-form - fn pick_encoding(&self) -> EncodingRef { + fn pick_encoding(&self) -> &'static Encoding { // Step 2 - if self.upcast::<Element>().has_attribute(&local_name!("accept-charset")) { + if self + .upcast::<Element>() + .has_attribute(&local_name!("accept-charset")) + { // Substep 1 - let input = self.upcast::<Element>().get_string_attribute(&local_name!("accept-charset")); + let input = self + .upcast::<Element>() + .get_string_attribute(&local_name!("accept-charset")); // Substep 2, 3, 4 - let mut candidate_encodings = split_html_space_chars(&*input).filter_map(encoding_from_whatwg_label); + let mut candidate_encodings = + split_html_space_chars(&*input).filter_map(|c| Encoding::for_label(c.as_bytes())); // Substep 5, 6 return candidate_encodings.next().unwrap_or(UTF_8); @@ -282,7 +662,7 @@ impl HTMLFormElement { let encoding = self.pick_encoding(); // Step 3 - let charset = &*encoding.whatwg_name().unwrap(); + let charset = encoding.name(); for entry in form_data.iter_mut() { // Step 4, 5 @@ -299,251 +679,441 @@ impl HTMLFormElement { /// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit) pub fn submit(&self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter) { // Step 1 + if self.upcast::<Element>().cannot_navigate() { + return; + } + + // Step 2 + if self.constructing_entry_list.get() { + return; + } + // Step 3 let doc = document_from_node(self); let base = doc.base_url(); - // TODO: Handle browsing contexts (Step 2, 3) - // Step 4 - if submit_method_flag == SubmittedFrom::NotFromForm && - !submitter.no_validate(self) - { - if self.interactive_validation().is_err() { - // TODO: Implement event handlers on all form control elements - self.upcast::<EventTarget>().fire_event(atom!("invalid")); + // TODO: Handle browsing contexts (Step 4, 5) + // Step 6 + if submit_method_flag == SubmittedFrom::NotFromForm { + // Step 6.1 + if self.firing_submission_events.get() { return; } - } - // Step 5 - if submit_method_flag == SubmittedFrom::NotFromForm { - let event = self.upcast::<EventTarget>() - .fire_bubbling_cancelable_event(atom!("submit")); + // Step 6.2 + self.firing_submission_events.set(true); + // Step 6.3 + if !submitter.no_validate(self) { + if self.interactive_validation().is_err() { + self.firing_submission_events.set(false); + return; + } + } + // Step 6.4 + // spec calls this "submitterButton" but it doesn't have to be a button, + // just not be the form itself + let submitter_button = match submitter { + FormSubmitter::FormElement(f) => { + if f == self { + None + } else { + Some(f.upcast::<HTMLElement>()) + } + }, + FormSubmitter::InputElement(i) => Some(i.upcast::<HTMLElement>()), + FormSubmitter::ButtonElement(b) => Some(b.upcast::<HTMLElement>()), + }; + + // Step 6.5 + let event = SubmitEvent::new( + &self.global(), + atom!("submit"), + true, + true, + submitter_button.map(|s| DomRoot::from_ref(s)), + ); + let event = event.upcast::<Event>(); + event.fire(self.upcast::<EventTarget>()); + + // Step 6.6 + self.firing_submission_events.set(false); + // Step 6.7 if event.DefaultPrevented() { return; } + // Step 6.8 + if self.upcast::<Element>().cannot_navigate() { + return; + } } - // Step 6 - let mut form_data = self.get_form_dataset(Some(submitter)); // Step 7 let encoding = self.pick_encoding(); // Step 8 - let mut action = submitter.action(); + let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding)) { + Some(form_data) => form_data, + None => return, + }; // Step 9 + if self.upcast::<Element>().cannot_navigate() { + return; + } + + // Step 10 + let mut action = submitter.action(); + + // Step 11 if action.is_empty() { action = DOMString::from(base.as_str()); } - // Step 10-11 + // Step 12-13 let action_components = match base.join(&action) { Ok(url) => url, - Err(_) => return + Err(_) => return, }; - // Step 12-15 + // Step 14-16 let scheme = action_components.scheme().to_owned(); let enctype = submitter.enctype(); let method = submitter.method(); - let _target = submitter.target(); - // TODO: Handle browsing contexts, partially loaded documents (step 16-17) - let mut load_data = LoadData::new(action_components, doc.get_referrer_policy(), Some(doc.url())); + // Step 17 + let target_attribute_value = + if submitter.is_submit_button() && submitter.target() != DOMString::new() { + Some(submitter.target()) + } else { + let form_owner = submitter.form_owner(); + let form = form_owner.as_ref().map(|form| &**form).unwrap_or(self); + get_element_target(form.upcast::<Element>()) + }; // Step 18 + let noopener = + get_element_noopener(self.upcast::<Element>(), target_attribute_value.clone()); + + // Step 19 + let source = doc.browsing_context().unwrap(); + let (maybe_chosen, _new) = source + .choose_browsing_context(target_attribute_value.unwrap_or(DOMString::new()), noopener); + + // Step 20 + let chosen = match maybe_chosen { + Some(proxy) => proxy, + None => return, + }; + let target_document = match chosen.document() { + Some(doc) => doc, + None => return, + }; + // Step 21 + let target_window = target_document.window(); + let mut load_data = LoadData::new( + LoadOrigin::Script(doc.origin().immutable().clone()), + action_components, + None, + target_window.upcast::<GlobalScope>().get_referrer(), + target_document.get_referrer_policy(), + Some(target_window.upcast::<GlobalScope>().is_secure_context()), + ); + + // Step 22 match (&*scheme, method) { (_, FormMethod::FormDialog) => { // TODO: Submit dialog // https://html.spec.whatwg.org/multipage/#submit-dialog - } + }, // https://html.spec.whatwg.org/multipage/#submit-mutate-action - ("http", FormMethod::FormGet) | ("https", FormMethod::FormGet) | ("data", FormMethod::FormGet) => { - load_data.headers.set(ContentType::form_url_encoded()); - self.mutate_action_url(&mut form_data, load_data, encoding); - } + ("http", FormMethod::FormGet) | + ("https", FormMethod::FormGet) | + ("data", FormMethod::FormGet) => { + load_data + .headers + .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED)); + self.mutate_action_url(&mut form_data, load_data, encoding, &target_window); + }, // https://html.spec.whatwg.org/multipage/#submit-body ("http", FormMethod::FormPost) | ("https", FormMethod::FormPost) => { - load_data.method = Method::Post; - self.submit_entity_body(&mut form_data, load_data, enctype, encoding); - } + load_data.method = Method::POST; + self.submit_entity_body( + &mut form_data, + load_data, + enctype, + encoding, + &target_window, + ); + }, // https://html.spec.whatwg.org/multipage/#submit-get-action - ("file", _) | ("about", _) | ("data", FormMethod::FormPost) | - ("ftp", _) | ("javascript", _) => { - self.plan_to_navigate(load_data); - } + ("file", _) | + ("about", _) | + ("data", FormMethod::FormPost) | + ("ftp", _) | + ("javascript", _) => { + self.plan_to_navigate(load_data, &target_window); + }, ("mailto", FormMethod::FormPost) => { // TODO: Mail as body // https://html.spec.whatwg.org/multipage/#submit-mailto-body - } + }, ("mailto", FormMethod::FormGet) => { // TODO: Mail with headers // https://html.spec.whatwg.org/multipage/#submit-mailto-headers - } + }, _ => return, } } // https://html.spec.whatwg.org/multipage/#submit-mutate-action - fn mutate_action_url(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData, encoding: EncodingRef) { - let charset = &*encoding.whatwg_name().unwrap(); - - load_data.url - .as_mut_url() - .query_pairs_mut().clear() - .encoding_override(Some(self.pick_encoding())) - .extend_pairs(form_data.into_iter() - .map(|field| (field.name.clone(), field.replace_value(charset)))); - - self.plan_to_navigate(load_data); + fn mutate_action_url( + &self, + form_data: &mut Vec<FormDatum>, + mut load_data: LoadData, + encoding: &'static Encoding, + target: &Window, + ) { + let charset = encoding.name(); + + self.set_url_query_pairs( + &mut load_data.url, + form_data + .iter() + .map(|field| (&*field.name, field.replace_value(charset))), + ); + + self.plan_to_navigate(load_data, target); } // https://html.spec.whatwg.org/multipage/#submit-body - fn submit_entity_body(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData, - enctype: FormEncType, encoding: EncodingRef) { + fn submit_entity_body( + &self, + form_data: &mut Vec<FormDatum>, + mut load_data: LoadData, + enctype: FormEncType, + encoding: &'static Encoding, + target: &Window, + ) { let boundary = generate_boundary(); let bytes = match enctype { FormEncType::UrlEncoded => { - let charset = &*encoding.whatwg_name().unwrap(); - load_data.headers.set(ContentType::form_url_encoded()); - - load_data.url - .as_mut_url() - .query_pairs_mut().clear() - .encoding_override(Some(self.pick_encoding())) - .extend_pairs(form_data.into_iter() - .map(|field| (field.name.clone(), field.replace_value(charset)))); - - load_data.url.query().unwrap_or("").to_string().into_bytes() - } + let charset = encoding.name(); + load_data + .headers + .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED)); + + let mut url = load_data.url.clone(); + self.set_url_query_pairs( + &mut url, + form_data + .iter() + .map(|field| (&*field.name, field.replace_value(charset))), + ); + + url.query().unwrap_or("").to_string().into_bytes() + }, FormEncType::FormDataEncoded => { - let mime = mime!(Multipart / FormData; Boundary =(&boundary)); - load_data.headers.set(ContentType(mime)); + let mime: Mime = format!("multipart/form-data; boundary={}", boundary) + .parse() + .unwrap(); + load_data.headers.typed_insert(ContentType::from(mime)); encode_multipart_form_data(form_data, boundary, encoding) - } + }, FormEncType::TextPlainEncoded => { - load_data.headers.set(ContentType(mime!(Text / Plain))); + load_data + .headers + .typed_insert(ContentType::from(mime::TEXT_PLAIN)); self.encode_plaintext(form_data).into_bytes() - } + }, }; - load_data.data = Some(bytes); - self.plan_to_navigate(load_data); + let global = self.global(); + + let request_body = bytes + .extract(&global) + .expect("Couldn't extract body.") + .into_net_request_body() + .0; + load_data.data = Some(request_body); + + self.plan_to_navigate(load_data, target); } - /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation) - fn plan_to_navigate(&self, load_data: LoadData) { - let window = window_from_node(self); + fn set_url_query_pairs<'a>( + &self, + url: &mut servo_url::ServoUrl, + pairs: impl Iterator<Item = (&'a str, String)>, + ) { + let encoding = self.pick_encoding(); + url.as_mut_url() + .query_pairs_mut() + .encoding_override(Some(&|s| encoding.encode(s).0)) + .clear() + .extend_pairs(pairs); + } + /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation) + fn plan_to_navigate(&self, mut load_data: LoadData, target: &Window) { // Step 1 - // Each planned navigation runnable is tagged with a generation ID, and - // before the runnable is handled, it first checks whether the HTMLFormElement's + // Each planned navigation task is tagged with a generation ID, and + // before the task is handled, it first checks whether the HTMLFormElement's // generation ID is the same as its own generation ID. - let GenerationId(prev_id) = self.generation_id.get(); - self.generation_id.set(GenerationId(prev_id + 1)); + let generation_id = GenerationId(self.generation_id.get().0 + 1); + self.generation_id.set(generation_id); // Step 2 - let nav = box PlannedNavigation { - load_data: load_data, - pipeline_id: window.upcast::<GlobalScope>().pipeline_id(), - script_chan: window.main_thread_script_chan().clone(), - generation_id: self.generation_id.get(), - form: Trusted::new(self) + let elem = self.upcast::<Element>(); + let referrer = match elem.get_attribute(&ns!(), &local_name!("rel")) { + Some(ref link_types) if link_types.Value().contains("noreferrer") => { + Referrer::NoReferrer + }, + _ => target.upcast::<GlobalScope>().get_referrer(), }; - // Step 3 - window.dom_manipulation_task_source().queue(nav, window.upcast()).unwrap(); + let referrer_policy = target.Document().get_referrer_policy(); + let pipeline_id = target.upcast::<GlobalScope>().pipeline_id(); + load_data.creator_pipeline_id = Some(pipeline_id); + load_data.referrer = referrer; + load_data.referrer_policy = referrer_policy; + + // Step 4. + let this = Trusted::new(self); + let window = Trusted::new(target); + let task = task!(navigate_to_form_planned_navigation: move || { + if generation_id != this.root().generation_id.get() { + return; + } + window + .root() + .load_url( + HistoryEntryReplacement::Disabled, + false, + load_data, + ); + }); + + // Step 3. + target + .task_manager() + .dom_manipulation_task_source() + .queue(task, target.upcast()) + .unwrap(); } /// Interactively validate the constraints of form elements - /// https://html.spec.whatwg.org/multipage/#interactively-validate-the-constraints + /// <https://html.spec.whatwg.org/multipage/#interactively-validate-the-constraints> fn interactive_validation(&self) -> Result<(), ()> { - // Step 1-3 - let _unhandled_invalid_controls = match self.static_validation() { + // Step 1-2 + let unhandled_invalid_controls = match self.static_validation() { Ok(()) => return Ok(()), - Err(err) => err + Err(err) => err, }; - // TODO: Report the problems with the constraints of at least one of - // the elements given in unhandled invalid controls to the user + + // Step 3 + let mut first = true; + + for elem in unhandled_invalid_controls { + if let Some(validatable) = elem.as_maybe_validatable() { + println!("Validation error: {}", validatable.validation_message()); + } + if first { + if let Some(html_elem) = elem.downcast::<HTMLElement>() { + html_elem.Focus(); + first = false; + } + } + } + // Step 4 Err(()) } /// Statitically validate the constraints of form elements - /// https://html.spec.whatwg.org/multipage/#statically-validate-the-constraints - fn static_validation(&self) -> Result<(), Vec<FormSubmittableElement>> { - let node = self.upcast::<Node>(); - // FIXME(#3553): This is an incorrect way of getting controls owned by the - // form, refactor this when html5ever's form owner PR lands + /// <https://html.spec.whatwg.org/multipage/#statically-validate-the-constraints> + fn static_validation(&self) -> Result<(), Vec<DomRoot<Element>>> { + let controls = self.controls.borrow(); // Step 1-3 - let invalid_controls = node.traverse_preorder().filter_map(|field| { - if let Some(el) = field.downcast::<Element>() { - if el.disabled_state() { - None - } else { + let invalid_controls = controls + .iter() + .filter_map(|field| { + if let Some(el) = field.downcast::<Element>() { let validatable = match el.as_maybe_validatable() { Some(v) => v, - None => return None + None => return None, }; if !validatable.is_instance_validatable() { None - } else if validatable.validate(ValidationFlags::empty()) { + } else if validatable.validate(ValidationFlags::all()).is_empty() { None } else { - Some(FormSubmittableElement::from_element(&el)) + Some(DomRoot::from_ref(el)) } + } else { + None } - } else { - None - } - }).collect::<Vec<FormSubmittableElement>>(); + }) + .collect::<Vec<DomRoot<Element>>>(); // Step 4 - if invalid_controls.is_empty() { return Ok(()); } + if invalid_controls.is_empty() { + return Ok(()); + } // Step 5-6 - let unhandled_invalid_controls = invalid_controls.into_iter().filter_map(|field| { - let event = field.as_event_target() - .fire_cancelable_event(atom!("invalid")); - if !event.DefaultPrevented() { return Some(field); } - None - }).collect::<Vec<FormSubmittableElement>>(); + let unhandled_invalid_controls = invalid_controls + .into_iter() + .filter_map(|field| { + let event = field + .upcast::<EventTarget>() + .fire_cancelable_event(atom!("invalid")); + if !event.DefaultPrevented() { + return Some(field); + } + None + }) + .collect::<Vec<DomRoot<Element>>>(); // Step 7 Err(unhandled_invalid_controls) } - /// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set - /// Steps range from 1 to 3 - fn get_unclean_dataset(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> { + /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set> + /// terminology note: "form data set" = "entry list" + /// Steps range from 3 to 5 + /// 5.x substeps are mostly handled inside element-specific methods + fn get_unclean_dataset( + &self, + submitter: Option<FormSubmitter>, + encoding: Option<&'static Encoding>, + ) -> Vec<FormDatum> { let controls = self.controls.borrow(); let mut data_set = Vec::new(); for child in controls.iter() { - // Step 3.1: The field element is disabled. + // Step 5.1: The field element is disabled. if child.disabled_state() { continue; } let child = child.upcast::<Node>(); - // Step 3.1: The field element has a datalist element ancestor. - if child.ancestors() - .any(|a| Root::downcast::<HTMLDataListElement>(a).is_some()) { + // Step 5.1: The field element has a datalist element ancestor. + if child + .ancestors() + .any(|a| DomRoot::downcast::<HTMLDataListElement>(a).is_some()) + { continue; } if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() { match element { HTMLElementTypeId::HTMLInputElement => { let input = child.downcast::<HTMLInputElement>().unwrap(); - - data_set.append(&mut input.form_datums(submitter)); - } + data_set.append(&mut input.form_datums(submitter, encoding)); + }, HTMLElementTypeId::HTMLButtonElement => { let button = child.downcast::<HTMLButtonElement>().unwrap(); if let Some(datum) = button.form_datum(submitter) { data_set.push(datum); } - } + }, HTMLElementTypeId::HTMLObjectElement => { // Unimplemented () - } + }, HTMLElementTypeId::HTMLSelectElement => { let select = child.downcast::<HTMLSelectElement>().unwrap(); select.push_form_data(&mut data_set); - } + }, HTMLElementTypeId::HTMLTextAreaElement => { let textarea = child.downcast::<HTMLTextAreaElement>().unwrap(); let name = textarea.Name(); @@ -551,21 +1121,45 @@ impl HTMLFormElement { data_set.push(FormDatum { ty: textarea.Type(), name: name, - value: FormDatumValue::String(textarea.Value()) + value: FormDatumValue::String(textarea.Value()), }); } - } - _ => () + }, + _ => (), } } + + // Step: 5.13. Add an entry if element has dirname attribute + // An element can only have a dirname attribute if it is a textarea element + // or an input element whose type attribute is in either the Text state or the Search state + let child_element = child.downcast::<Element>().unwrap(); + let input_matches = + child_element + .downcast::<HTMLInputElement>() + .map_or(false, |input| { + input.input_type() == InputType::Text || + input.input_type() == InputType::Search + }); + let textarea_matches = child_element.is::<HTMLTextAreaElement>(); + let dirname = child_element.get_string_attribute(&local_name!("dirname")); + if (input_matches || textarea_matches) && !dirname.is_empty() { + let dir = DOMString::from(child_element.directionality()); + data_set.push(FormDatum { + ty: DOMString::from("string"), + name: dirname, + value: FormDatumValue::String(dir), + }); + } } data_set - // TODO: Handle `dirnames` (needs directionality support) - // https://html.spec.whatwg.org/multipage/#the-directionality } - /// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set - pub fn get_form_dataset(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> { + /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set> + pub fn get_form_dataset( + &self, + submitter: Option<FormSubmitter>, + encoding: Option<&'static Encoding>, + ) -> Option<Vec<FormDatum>> { fn clean_crlf(s: &str) -> DOMString { // Step 4 let mut buf = "".to_owned(); @@ -586,7 +1180,7 @@ impl HTMLFormElement { buf.push('\n'); buf.push(ch); }, - _ => buf.push(ch) + _ => buf.push(ch), }; prev = ch; } @@ -597,9 +1191,16 @@ impl HTMLFormElement { DOMString::from(buf) } - // Step 1-3 - let mut ret = self.get_unclean_dataset(submitter); - // Step 4 + // Step 1 + if self.constructing_entry_list.get() { + return None; + } + + // Step 2 + self.constructing_entry_list.set(true); + + // Step 3-6 + let mut ret = self.get_unclean_dataset(submitter, encoding); for datum in &mut ret { match &*datum.ty { "file" | "textarea" => (), // TODO @@ -607,13 +1208,33 @@ impl HTMLFormElement { datum.name = clean_crlf(&datum.name); datum.value = FormDatumValue::String(clean_crlf(match datum.value { FormDatumValue::String(ref s) => s, - FormDatumValue::File(_) => unreachable!() + FormDatumValue::File(_) => unreachable!(), })); - } + }, } - }; - // Step 5 - ret + } + + let window = window_from_node(self); + + // Step 6 + let form_data = FormData::new(Some(ret), &window.global()); + + // Step 7 + let event = FormDataEvent::new( + &window.global(), + atom!("formdata"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + &form_data, + ); + + event.upcast::<Event>().fire(self.upcast::<EventTarget>()); + + // Step 8 + self.constructing_entry_list.set(false); + + // Step 9 + Some(form_data.datums()) } pub fn reset(&self, _reset_method_flag: ResetFrom) { @@ -624,7 +1245,8 @@ impl HTMLFormElement { self.marked_for_reset.set(true); } - let event = self.upcast::<EventTarget>() + let event = self + .upcast::<EventTarget>() .fire_bubbling_cancelable_event(atom!("reset")); if event.DefaultPrevented() { return; @@ -635,24 +1257,32 @@ impl HTMLFormElement { let child = child.upcast::<Node>(); match child.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement, + )) => { child.downcast::<HTMLInputElement>().unwrap().reset(); - } + }, // TODO HTMLKeygenElement unimplemented //NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLKeygenElement)) => { // // Unimplemented // {} //} - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLSelectElement, + )) => { child.downcast::<HTMLSelectElement>().unwrap().reset(); - } - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { + }, + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement, + )) => { child.downcast::<HTMLTextAreaElement>().unwrap().reset(); - } - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { - // Unimplemented - } - _ => {} + }, + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLOutputElement, + )) => { + child.downcast::<HTMLOutputElement>().unwrap().reset(); + }, + _ => {}, } } self.marked_for_reset.set(false); @@ -660,7 +1290,7 @@ impl HTMLFormElement { fn add_control<T: ?Sized + FormControl>(&self, control: &T) { let root = self.upcast::<Element>().root_element(); - let root = root.r().upcast::<Node>(); + let root = root.upcast::<Node>(); let mut controls = self.controls.borrow_mut(); controls.insert_pre_order(control.to_element(), root); @@ -669,28 +1299,37 @@ impl HTMLFormElement { fn remove_control<T: ?Sized + FormControl>(&self, control: &T) { let control = control.to_element(); let mut controls = self.controls.borrow_mut(); - controls.iter().position(|c| c.r() == control) - .map(|idx| controls.remove(idx)); + controls + .iter() + .position(|c| &**c == control) + .map(|idx| controls.remove(idx)); + + // https://html.spec.whatwg.org/multipage#forms.html#the-form-element:past-names-map-5 + // "If an element listed in a form element's past names map + // changes form owner, then its entries must be removed + // from that map." + let mut past_names_map = self.past_names_map.borrow_mut(); + past_names_map.retain(|_k, v| v.0 != control); } } -#[derive(JSTraceable, HeapSizeOf, Clone)] +#[derive(Clone, JSTraceable, MallocSizeOf)] pub enum FormDatumValue { #[allow(dead_code)] - File(Root<File>), - String(DOMString) + File(DomRoot<File>), + String(DOMString), } -#[derive(HeapSizeOf, JSTraceable, Clone)] +#[derive(Clone, JSTraceable, MallocSizeOf)] pub struct FormDatum { pub ty: DOMString, pub name: DOMString, - pub value: FormDatumValue + pub value: FormDatumValue, } impl FormDatum { pub fn replace_value(&self, charset: &str) -> String { - if self.name == "_charset_" && self.ty == "hidden" { + if self.name.to_ascii_lowercase() == "_charset_" && self.ty == "hidden" { return charset.to_string(); } @@ -701,168 +1340,148 @@ impl FormDatum { } } -#[derive(Copy, Clone, HeapSizeOf)] +#[derive(Clone, Copy, MallocSizeOf)] pub enum FormEncType { TextPlainEncoded, UrlEncoded, - FormDataEncoded + FormDataEncoded, } -#[derive(Copy, Clone, HeapSizeOf)] +#[derive(Clone, Copy, MallocSizeOf)] pub enum FormMethod { FormGet, FormPost, - FormDialog + FormDialog, } -#[derive(HeapSizeOf)] -#[allow(dead_code)] -pub enum FormSubmittableElement { - ButtonElement(Root<HTMLButtonElement>), - InputElement(Root<HTMLInputElement>), - // TODO: HTMLKeygenElement unimplemented - // KeygenElement(&'a HTMLKeygenElement), - ObjectElement(Root<HTMLObjectElement>), - SelectElement(Root<HTMLSelectElement>), - TextAreaElement(Root<HTMLTextAreaElement>), -} - -impl FormSubmittableElement { - fn as_event_target(&self) -> &EventTarget { - match *self { - FormSubmittableElement::ButtonElement(ref button) => button.upcast(), - FormSubmittableElement::InputElement(ref input) => input.upcast(), - FormSubmittableElement::ObjectElement(ref object) => object.upcast(), - FormSubmittableElement::SelectElement(ref select) => select.upcast(), - FormSubmittableElement::TextAreaElement(ref textarea) => textarea.upcast() - } - } - - fn from_element(element: &Element) -> FormSubmittableElement { - if let Some(input) = element.downcast::<HTMLInputElement>() { - FormSubmittableElement::InputElement(Root::from_ref(&input)) - } - else if let Some(input) = element.downcast::<HTMLButtonElement>() { - FormSubmittableElement::ButtonElement(Root::from_ref(&input)) - } - else if let Some(input) = element.downcast::<HTMLObjectElement>() { - FormSubmittableElement::ObjectElement(Root::from_ref(&input)) - } - else if let Some(input) = element.downcast::<HTMLSelectElement>() { - FormSubmittableElement::SelectElement(Root::from_ref(&input)) - } - else if let Some(input) = element.downcast::<HTMLTextAreaElement>() { - FormSubmittableElement::TextAreaElement(Root::from_ref(&input)) - } else { - unreachable!() - } - } -} - -#[derive(Copy, Clone, HeapSizeOf)] +/// <https://html.spec.whatwg.org/multipage/#form-associated-element> +#[derive(Clone, Copy, MallocSizeOf)] pub enum FormSubmitter<'a> { FormElement(&'a HTMLFormElement), InputElement(&'a HTMLInputElement), - ButtonElement(&'a HTMLButtonElement) - // TODO: image submit, etc etc + ButtonElement(&'a HTMLButtonElement), + // TODO: implement other types of form associated elements + // (including custom elements) that can be passed as submitter. } impl<'a> FormSubmitter<'a> { fn action(&self) -> DOMString { match *self { FormSubmitter::FormElement(form) => form.Action(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_attribute(&local_name!("formaction"), - |i| i.FormAction(), - |f| f.Action()) - }, - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_attribute(&local_name!("formaction"), - |i| i.FormAction(), - |f| f.Action()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_attribute( + &local_name!("formaction"), + |i| i.FormAction(), + |f| f.Action(), + ), + FormSubmitter::ButtonElement(button_element) => button_element.get_form_attribute( + &local_name!("formaction"), + |i| i.FormAction(), + |f| f.Action(), + ), } } fn enctype(&self) -> FormEncType { let attr = match *self { FormSubmitter::FormElement(form) => form.Enctype(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_attribute(&local_name!("formenctype"), - |i| i.FormEnctype(), - |f| f.Enctype()) - }, - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_attribute(&local_name!("formenctype"), - |i| i.FormEnctype(), - |f| f.Enctype()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_attribute( + &local_name!("formenctype"), + |i| i.FormEnctype(), + |f| f.Enctype(), + ), + FormSubmitter::ButtonElement(button_element) => button_element.get_form_attribute( + &local_name!("formenctype"), + |i| i.FormEnctype(), + |f| f.Enctype(), + ), }; match &*attr { "multipart/form-data" => FormEncType::FormDataEncoded, "text/plain" => FormEncType::TextPlainEncoded, // https://html.spec.whatwg.org/multipage/#attr-fs-enctype // urlencoded is the default - _ => FormEncType::UrlEncoded + _ => FormEncType::UrlEncoded, } } fn method(&self) -> FormMethod { let attr = match *self { FormSubmitter::FormElement(form) => form.Method(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_attribute(&local_name!("formmethod"), - |i| i.FormMethod(), - |f| f.Method()) - }, - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_attribute(&local_name!("formmethod"), - |i| i.FormMethod(), - |f| f.Method()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_attribute( + &local_name!("formmethod"), + |i| i.FormMethod(), + |f| f.Method(), + ), + FormSubmitter::ButtonElement(button_element) => button_element.get_form_attribute( + &local_name!("formmethod"), + |i| i.FormMethod(), + |f| f.Method(), + ), }; match &*attr { "dialog" => FormMethod::FormDialog, "post" => FormMethod::FormPost, - _ => FormMethod::FormGet + _ => FormMethod::FormGet, } } fn target(&self) -> DOMString { match *self { FormSubmitter::FormElement(form) => form.Target(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_attribute(&local_name!("formtarget"), - |i| i.FormTarget(), - |f| f.Target()) - }, - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_attribute(&local_name!("formtarget"), - |i| i.FormTarget(), - |f| f.Target()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_attribute( + &local_name!("formtarget"), + |i| i.FormTarget(), + |f| f.Target(), + ), + FormSubmitter::ButtonElement(button_element) => button_element.get_form_attribute( + &local_name!("formtarget"), + |i| i.FormTarget(), + |f| f.Target(), + ), } } fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool { match *self { FormSubmitter::FormElement(form) => form.NoValidate(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_boolean_attribute(&local_name!("formnovalidate"), - |i| i.FormNoValidate(), - |f| f.NoValidate()) - } - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_boolean_attribute(&local_name!("formnovalidate"), - |i| i.FormNoValidate(), - |f| f.NoValidate()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_boolean_attribute( + &local_name!("formnovalidate"), + |i| i.FormNoValidate(), + |f| f.NoValidate(), + ), + FormSubmitter::ButtonElement(button_element) => button_element + .get_form_boolean_attribute( + &local_name!("formnovalidate"), + |i| i.FormNoValidate(), + |f| f.NoValidate(), + ), + } + } + + // https://html.spec.whatwg.org/multipage/#concept-submit-button + fn is_submit_button(&self) -> bool { + match *self { + // https://html.spec.whatwg.org/multipage/#image-button-state-(type=image) + // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit) + FormSubmitter::InputElement(input_element) => input_element.is_submit_button(), + // https://html.spec.whatwg.org/multipage/#attr-button-type-submit-state + FormSubmitter::ButtonElement(button_element) => button_element.is_submit_button(), + _ => false, + } + } + + // https://html.spec.whatwg.org/multipage/#form-owner + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { + match *self { + FormSubmitter::ButtonElement(button_el) => button_el.form_owner(), + FormSubmitter::InputElement(input_el) => input_el.form_owner(), + _ => None, } } } pub trait FormControl: DomObject { - fn form_owner(&self) -> Option<Root<HTMLFormElement>>; + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>; fn set_form_owner(&self, form: Option<&HTMLFormElement>); @@ -879,7 +1498,7 @@ pub trait FormControl: DomObject { fn set_form_owner_from_parser(&self, form: &HTMLFormElement) { let elem = self.to_element(); let node = elem.upcast::<Node>(); - node.set_flag(PARSER_ASSOCIATED_FORM_OWNER, true); + node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true); form.add_control(self); self.set_form_owner(Some(form)); } @@ -890,9 +1509,10 @@ pub trait FormControl: DomObject { let node = elem.upcast::<Node>(); let old_owner = self.form_owner(); let has_form_id = elem.has_attribute(&local_name!("form")); - let nearest_form_ancestor = node.ancestors() - .filter_map(Root::downcast::<HTMLFormElement>) - .next(); + let nearest_form_ancestor = node + .ancestors() + .filter_map(DomRoot::downcast::<HTMLFormElement>) + .next(); // Step 1 if old_owner.is_some() && !(self.is_listed() && has_form_id) { @@ -905,7 +1525,8 @@ pub trait FormControl: DomObject { // Step 3 let doc = document_from_node(node); let form_id = elem.get_string_attribute(&local_name!("form")); - doc.GetElementById(form_id).and_then(Root::downcast::<HTMLFormElement>) + doc.GetElementById(form_id) + .and_then(DomRoot::downcast::<HTMLFormElement>) } else { // Step 4 nearest_form_ancestor @@ -915,11 +1536,10 @@ pub trait FormControl: DomObject { if let Some(o) = old_owner { o.remove_control(self); } - let new_owner = new_owner.as_ref().map(|o| { - o.add_control(self); - o.r() - }); - self.set_form_owner(new_owner); + if let Some(ref new_owner) = new_owner { + new_owner.add_control(self); + } + self.set_form_owner(new_owner.as_deref()); } } @@ -943,7 +1563,7 @@ pub trait FormControl: DomObject { let form_id = elem.get_string_attribute(&local_name!("form")); let node = elem.upcast::<Node>(); - if self.is_listed() && !form_id.is_empty() && node.is_in_doc() { + if self.is_listed() && !form_id.is_empty() && node.is_connected() { let doc = document_from_node(node); doc.register_form_id_listener(form_id, self); } @@ -968,8 +1588,8 @@ pub trait FormControl: DomObject { // Part of step 12. // '..suppress the running of the reset the form owner algorithm // when the parser subsequently attempts to insert the element..' - let must_skip_reset = node.get_flag(PARSER_ASSOCIATED_FORM_OWNER); - node.set_flag(PARSER_ASSOCIATED_FORM_OWNER, false); + let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER); + node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false); if !must_skip_reset { self.form_attribute_mutated(AttributeMutation::Set(None)); @@ -980,9 +1600,9 @@ pub trait FormControl: DomObject { fn unbind_form_control_from_tree(&self) { let elem = self.to_element(); let has_form_attr = elem.has_attribute(&local_name!("form")); - let same_subtree = self.form_owner().map_or(true, |form| { - elem.is_in_same_home_subtree(&*form) - }); + let same_subtree = self + .form_owner() + .map_or(true, |form| elem.is_in_same_home_subtree(&*form)); self.unregister_if_necessary(); @@ -996,13 +1616,16 @@ pub trait FormControl: DomObject { } } - fn get_form_attribute<InputFn, OwnerFn>(&self, - attr: &LocalName, - input: InputFn, - owner: OwnerFn) - -> DOMString - where InputFn: Fn(&Self) -> DOMString, - OwnerFn: Fn(&HTMLFormElement) -> DOMString, Self: Sized + fn get_form_attribute<InputFn, OwnerFn>( + &self, + attr: &LocalName, + input: InputFn, + owner: OwnerFn, + ) -> DOMString + where + InputFn: Fn(&Self) -> DOMString, + OwnerFn: Fn(&HTMLFormElement) -> DOMString, + Self: Sized, { if self.to_element().has_attribute(attr) { input(self) @@ -1011,13 +1634,16 @@ pub trait FormControl: DomObject { } } - fn get_form_boolean_attribute<InputFn, OwnerFn>(&self, - attr: &LocalName, - input: InputFn, - owner: OwnerFn) - -> bool - where InputFn: Fn(&Self) -> bool, - OwnerFn: Fn(&HTMLFormElement) -> bool, Self: Sized + fn get_form_boolean_attribute<InputFn, OwnerFn>( + &self, + attr: &LocalName, + input: InputFn, + owner: OwnerFn, + ) -> bool + where + InputFn: Fn(&Self) -> bool, + OwnerFn: Fn(&HTMLFormElement) -> bool, + Self: Sized, { if self.to_element().has_attribute(attr) { input(self) @@ -1032,15 +1658,8 @@ pub trait FormControl: DomObject { } impl VirtualMethods for HTMLFormElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) - } - - fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { - match name { - &local_name!("name") => AttrValue::from_atomic(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), - } + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn unbind_from_tree(&self, context: &UnbindContext) { @@ -1049,97 +1668,93 @@ impl VirtualMethods for HTMLFormElement { // Collect the controls to reset because reset_form_owner // will mutably borrow self.controls rooted_vec!(let mut to_reset); - to_reset.extend(self.controls.borrow().iter() - .filter(|c| !c.is_in_same_home_subtree(self)) - .map(|c| c.clone())); + to_reset.extend( + self.controls + .borrow() + .iter() + .filter(|c| !c.is_in_same_home_subtree(self)) + .map(|c| c.clone()), + ); for control in to_reset.iter() { - control.as_maybe_form_control() - .expect("Element must be a form control") - .reset_form_owner(); + control + .as_maybe_form_control() + .expect("Element must be a form control") + .reset_form_owner(); + } + } + + fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { + match name { + &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } } pub trait FormControlElementHelpers { - fn as_maybe_form_control<'a>(&'a self) -> Option<&'a FormControl>; + fn as_maybe_form_control<'a>(&'a self) -> Option<&'a dyn FormControl>; } impl FormControlElementHelpers for Element { - fn as_maybe_form_control<'a>(&'a self) -> Option<&'a FormControl> { + fn as_maybe_form_control<'a>(&'a self) -> Option<&'a dyn FormControl> { let node = self.upcast::<Node>(); match node.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) => { - Some(self.downcast::<HTMLButtonElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFieldSetElement)) => { - Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement)) => { - Some(self.downcast::<HTMLImageElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { - Some(self.downcast::<HTMLInputElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => { - Some(self.downcast::<HTMLLabelElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLegendElement)) => { - Some(self.downcast::<HTMLLegendElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { - Some(self.downcast::<HTMLObjectElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { - Some(self.downcast::<HTMLOutputElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { - Some(self.downcast::<HTMLSelectElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { - Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &FormControl) - }, - _ => { - None - } - } - } -} - -struct PlannedNavigation { - load_data: LoadData, - pipeline_id: PipelineId, - script_chan: Sender<MainThreadScriptMsg>, - generation_id: GenerationId, - form: Trusted<HTMLFormElement> -} - -impl Runnable for PlannedNavigation { - fn name(&self) -> &'static str { "PlannedNavigation" } - - fn handler(self: Box<PlannedNavigation>) { - if self.generation_id == self.form.root().generation_id.get() { - let script_chan = self.script_chan.clone(); - script_chan.send(MainThreadScriptMsg::Navigate(self.pipeline_id, self.load_data, false)).unwrap(); + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLButtonElement, + )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLFieldSetElement, + )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLImageElement, + )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement, + )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLLabelElement, + )) => Some(self.downcast::<HTMLLabelElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLLegendElement, + )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLObjectElement, + )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLOutputElement, + )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLSelectElement, + )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement, + )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl), + _ => None, } } } - // https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm -pub fn encode_multipart_form_data(form_data: &mut Vec<FormDatum>, - boundary: String, encoding: EncodingRef) -> Vec<u8> { +pub fn encode_multipart_form_data( + form_data: &mut Vec<FormDatum>, + boundary: String, + encoding: &'static Encoding, +) -> Vec<u8> { // Step 1 let mut result = vec![]; // Step 2 - let charset = &*encoding.whatwg_name().unwrap_or("UTF-8"); + let charset = encoding.name(); // Step 3 for entry in form_data.iter_mut() { // 3.1 - if entry.name == "_charset_" && entry.ty == "hidden" { + if entry.name.to_ascii_lowercase() == "_charset_" && entry.ty == "hidden" { entry.value = FormDatumValue::String(DOMString::from(charset.clone())); } // TODO: 3.2 @@ -1150,38 +1765,53 @@ pub fn encode_multipart_form_data(form_data: &mut Vec<FormDatum>, // what spec says (that it should start with a '\r\n'). let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes(); result.append(&mut boundary_bytes); - let mut content_disposition = ContentDisposition { - disposition: DispositionType::Ext("form-data".to_owned()), - parameters: vec![DispositionParam::Ext("name".to_owned(), String::from(entry.name.clone()))] - }; + // TODO(eijebong): Everthing related to content-disposition it to redo once typed headers + // are capable of it. match entry.value { FormDatumValue::String(ref s) => { - let mut bytes = format!("Content-Disposition: {}\r\n\r\n{}", - content_disposition, s).into_bytes(); + let content_disposition = format!("form-data; name=\"{}\"", entry.name); + let mut bytes = + format!("Content-Disposition: {}\r\n\r\n{}", content_disposition, s) + .into_bytes(); result.append(&mut bytes); - } + }, FormDatumValue::File(ref f) => { - content_disposition.parameters.push( - DispositionParam::Filename(Charset::Ext(String::from(charset.clone())), - None, - f.name().clone().into())); + let extra = if charset.to_lowercase() == "utf-8" { + format!( + "filename=\"{}\"", + String::from_utf8(f.name().as_bytes().into()).unwrap() + ) + } else { + format!( + "filename*=\"{}\"''{}", + charset, + http_percent_encode(f.name().as_bytes()) + ) + }; + + let content_disposition = format!("form-data; name=\"{}\"; {}", entry.name, extra); // https://tools.ietf.org/html/rfc7578#section-4.4 - let content_type = ContentType(f.upcast::<Blob>().Type() - .parse().unwrap_or(mime!(Text / Plain))); - let mut type_bytes = format!("Content-Disposition: {}\r\ncontent-type: {}\r\n\r\n", - content_disposition, - content_type).into_bytes(); + let content_type: Mime = f + .upcast::<Blob>() + .Type() + .parse() + .unwrap_or(mime::TEXT_PLAIN); + let mut type_bytes = format!( + "Content-Disposition: {}\r\ncontent-type: {}\r\n\r\n", + content_disposition, content_type + ) + .into_bytes(); result.append(&mut type_bytes); let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]); result.append(&mut bytes); - } + }, } } - let mut boundary_bytes = format!("\r\n--{}--", boundary).into_bytes(); + let mut boundary_bytes = format!("\r\n--{}--\r\n", boundary).into_bytes(); result.append(&mut boundary_bytes); result diff --git a/components/script/dom/htmlframeelement.rs b/components/script/dom/htmlframeelement.rs index e36bfc95310..fa3ad51393e 100644 --- a/components/script/dom/htmlframeelement.rs +++ b/components/script/dom/htmlframeelement.rs @@ -1,34 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLFrameElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLFrameElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLFrameElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLFrameElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLFrameElement { HTMLFrameElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLFrameElement> { - Node::reflect_node(box HTMLFrameElement::new_inherited(local_name, prefix, document), - document, - HTMLFrameElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLFrameElement> { + Node::reflect_node( + Box::new(HTMLFrameElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmlframesetelement.rs b/components/script/dom/htmlframesetelement.rs index d6fba77cb08..dc071b47074 100644 --- a/components/script/dom/htmlframesetelement.rs +++ b/components/script/dom/htmlframesetelement.rs @@ -1,41 +1,47 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventHandlerBinding::{EventHandlerNonNull, OnBeforeUnloadEventHandlerNonNull}; -use dom::bindings::codegen::Bindings::HTMLFrameSetElementBinding; -use dom::bindings::codegen::Bindings::HTMLFrameSetElementBinding::HTMLFrameSetElementMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, document_from_node}; +use crate::dom::bindings::codegen::Bindings::HTMLFrameSetElementBinding::HTMLFrameSetElementMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{document_from_node, Node}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLFrameSetElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLFrameSetElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLFrameSetElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLFrameSetElement { HTMLFrameSetElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLFrameSetElement> { - Node::reflect_node(box HTMLFrameSetElement::new_inherited(local_name, prefix, document), - document, - HTMLFrameSetElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLFrameSetElement> { + let n = Node::reflect_node( + Box::new(HTMLFrameSetElement::new_inherited( + local_name, prefix, document, + )), + document, + ); + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } } diff --git a/components/script/dom/htmlheadelement.rs b/components/script/dom/htmlheadelement.rs index 7e05ac3d45c..1bd6e5e1148 100644 --- a/components/script/dom/htmlheadelement.rs +++ b/components/script/dom/htmlheadelement.rs @@ -1,62 +1,72 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::HTMLHeadElementBinding; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{Root, RootedReference}; -use dom::bindings::str::DOMString; -use dom::document::{Document, determine_policy_for_token}; -use dom::element::Element; -use dom::htmlelement::HTMLElement; -use dom::htmlmetaelement::HTMLMetaElement; -use dom::node::{Node, document_from_node}; -use dom::userscripts::load_script; -use dom::virtualmethods::VirtualMethods; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::{determine_policy_for_token, Document}; +use crate::dom::element::Element; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlmetaelement::HTMLMetaElement; +use crate::dom::node::{document_from_node, BindContext, Node, ShadowIncluding}; +use crate::dom::userscripts::load_script; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLHeadElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLHeadElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLHeadElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLHeadElement { HTMLHeadElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLHeadElement> { - Node::reflect_node(box HTMLHeadElement::new_inherited(local_name, prefix, document), - document, - HTMLHeadElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLHeadElement> { + let n = Node::reflect_node( + Box::new(HTMLHeadElement::new_inherited(local_name, prefix, document)), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } - /// https://html.spec.whatwg.org/multipage/#meta-referrer + /// <https://html.spec.whatwg.org/multipage/#meta-referrer> pub fn set_document_referrer(&self) { let doc = document_from_node(self); - if doc.GetHead().r() != Some(self) { + if doc.GetHead().as_deref() != Some(self) { return; } let node = self.upcast::<Node>(); - let candidates = node.traverse_preorder() - .filter_map(Root::downcast::<Element>) - .filter(|elem| elem.is::<HTMLMetaElement>()) - .filter(|elem| elem.get_string_attribute(&local_name!("name")) == "referrer") - .filter(|elem| elem.get_attribute(&ns!(), &local_name!("content")).is_some()); + let candidates = node + .traverse_preorder(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<Element>) + .filter(|elem| elem.is::<HTMLMetaElement>()) + .filter(|elem| elem.get_name() == Some(atom!("referrer"))) + .filter(|elem| { + elem.get_attribute(&ns!(), &local_name!("content")) + .is_some() + }); for meta in candidates { - if let Some(content) = meta.get_attribute(&ns!(), &local_name!("content")).r() { + if let Some(ref content) = meta.get_attribute(&ns!(), &local_name!("content")) { let content = content.value(); let content_val = content.trim(); if !content_val.is_empty() { @@ -69,12 +79,12 @@ impl HTMLHeadElement { } impl VirtualMethods for HTMLHeadElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } load_script(self); } diff --git a/components/script/dom/htmlheadingelement.rs b/components/script/dom/htmlheadingelement.rs index f820b8ba90f..659cf14f4d2 100644 --- a/components/script/dom/htmlheadingelement.rs +++ b/components/script/dom/htmlheadingelement.rs @@ -1,17 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLHeadingElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] pub enum HeadingLevel { Heading1, Heading2, @@ -28,24 +26,30 @@ pub struct HTMLHeadingElement { } impl HTMLHeadingElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document, - level: HeadingLevel) -> HTMLHeadingElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + level: HeadingLevel, + ) -> HTMLHeadingElement { HTMLHeadingElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), level: level, } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document, - level: HeadingLevel) -> Root<HTMLHeadingElement> { - Node::reflect_node(box HTMLHeadingElement::new_inherited(local_name, prefix, document, level), - document, - HTMLHeadingElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + level: HeadingLevel, + ) -> DomRoot<HTMLHeadingElement> { + Node::reflect_node( + Box::new(HTMLHeadingElement::new_inherited( + local_name, prefix, document, level, + )), + document, + ) } } diff --git a/components/script/dom/htmlhrelement.rs b/components/script/dom/htmlhrelement.rs index f1d5774dbe9..69fa8887bb1 100644 --- a/components/script/dom/htmlhrelement.rs +++ b/components/script/dom/htmlhrelement.rs @@ -1,19 +1,19 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::HTMLHRElementBinding::HTMLHRElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{Element, LayoutElementHelpers}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use crate::dom::virtualmethods::VirtualMethods; use cssparser::RGBA; -use dom::bindings::codegen::Bindings::HTMLHRElementBinding::{self, HTMLHRElementMethods}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{Element, RawLayoutElementHelpers}; -use dom::htmlelement::HTMLElement; -use dom::node::Node; -use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; #[dom_struct] @@ -22,19 +22,26 @@ pub struct HTMLHRElement { } impl HTMLHRElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLHRElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLHRElement { HTMLHRElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLHRElement> { - Node::reflect_node(box HTMLHRElement::new_inherited(local_name, prefix, document), - document, - HTMLHRElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLHRElement> { + Node::reflect_node( + Box::new(HTMLHRElement::new_inherited(local_name, prefix, document)), + document, + ) } } @@ -59,37 +66,30 @@ impl HTMLHRElementMethods for HTMLHRElement { } pub trait HTMLHRLayoutHelpers { - fn get_color(&self) -> Option<RGBA>; - fn get_width(&self) -> LengthOrPercentageOrAuto; + fn get_color(self) -> Option<RGBA>; + fn get_width(self) -> LengthOrPercentageOrAuto; } -impl HTMLHRLayoutHelpers for LayoutJS<HTMLHRElement> { - #[allow(unsafe_code)] - fn get_color(&self) -> Option<RGBA> { - unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("color")) - .and_then(AttrValue::as_color) - .cloned() - } +impl HTMLHRLayoutHelpers for LayoutDom<'_, HTMLHRElement> { + fn get_color(self) -> Option<RGBA> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("color")) + .and_then(AttrValue::as_color) + .cloned() } - #[allow(unsafe_code)] - fn get_width(&self) -> LengthOrPercentageOrAuto { - unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("width")) - .map(AttrValue::as_dimension) - .cloned() - .unwrap_or(LengthOrPercentageOrAuto::Auto) - } + fn get_width(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("width")) + .map(AttrValue::as_dimension) + .cloned() + .unwrap_or(LengthOrPercentageOrAuto::Auto) } } - impl VirtualMethods for HTMLHRElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { @@ -97,7 +97,10 @@ impl VirtualMethods for HTMLHRElement { &local_name!("align") => AttrValue::from_dimension(value.into()), &local_name!("color") => AttrValue::from_legacy_color(value.into()), &local_name!("width") => AttrValue::from_dimension(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } } diff --git a/components/script/dom/htmlhtmlelement.rs b/components/script/dom/htmlhtmlelement.rs index 924b55650ee..da77d3464d9 100644 --- a/components/script/dom/htmlhtmlelement.rs +++ b/components/script/dom/htmlhtmlelement.rs @@ -1,34 +1,44 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLHtmlElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLHtmlElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } +#[allow(non_snake_case)] impl HTMLHtmlElement { - fn new_inherited(localName: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLHtmlElement { + fn new_inherited( + localName: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLHtmlElement { HTMLHtmlElement { - htmlelement: HTMLElement::new_inherited(localName, prefix, document) + htmlelement: HTMLElement::new_inherited(localName, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(localName: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLHtmlElement> { - Node::reflect_node(box HTMLHtmlElement::new_inherited(localName, prefix, document), - document, - HTMLHtmlElementBinding::Wrap) + pub fn new( + localName: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLHtmlElement> { + let n = Node::reflect_node( + Box::new(HTMLHtmlElement::new_inherited(localName, prefix, document)), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } } diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 599f2f69c36..6f5264999be 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -1,75 +1,68 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use document_loader::{LoadBlocker, LoadType}; -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementErrorEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementIconChangeEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementLocationChangeEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementOpenTabEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementOpenWindowEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementSecurityChangeDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementVisibilityChangeEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserShowModalPromptEventDetail; -use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding; -use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::conversions::ToJSValConvertible; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, MutNullableJS, Root}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::browsingcontext::BrowsingContext; -use dom::customevent::CustomEvent; -use dom::document::Document; -use dom::domtokenlist::DOMTokenList; -use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, NodeDamage, UnbindContext, document_from_node, window_from_node}; -use dom::virtualmethods::VirtualMethods; -use dom::window::{ReflowReason, Window}; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::document_loader::{LoadBlocker, LoadType}; +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::document::Document; +use crate::dom::domtokenlist::DOMTokenList; +use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{ + document_from_node, window_from_node, BindContext, Node, NodeDamage, UnbindContext, +}; +use crate::dom::virtualmethods::VirtualMethods; +use crate::dom::window::ReflowReason; +use crate::dom::windowproxy::WindowProxy; +use crate::script_thread::ScriptThread; +use crate::task_source::TaskSource; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use ipc_channel::ipc; -use js::jsapi::{JSAutoCompartment, JSContext, MutableHandleValue}; -use js::jsval::{NullValue, UndefinedValue}; -use msg::constellation_msg::{FrameType, FrameId, PipelineId, TraversalDirection}; -use net_traits::response::HttpsState; -use script_layout_interface::message::ReflowQueryType; -use script_thread::{ScriptThread, Runnable}; -use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, LoadData}; -use script_traits::{MozBrowserEvent, NewLayoutInfo, ScriptMsg as ConstellationMsg}; +use msg::constellation_msg::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId}; +use profile_traits::ipc as ProfiledIpc; +use script_layout_interface::message::ReflowGoal; use script_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed}; +use script_traits::{ + HistoryEntryReplacement, IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, + LoadOrigin, UpdatePipelineIdReason, WindowSizeData, +}; +use script_traits::{NewLayoutInfo, ScriptMsg}; use servo_atoms::Atom; -use servo_config::prefs::PREFS; -use servo_config::servo_version; use servo_url::ServoUrl; use std::cell::Cell; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; -use style::context::ReflowGoal; -use task_source::TaskSource; bitflags! { - #[derive(JSTraceable, HeapSizeOf)] - flags SandboxAllowance: u8 { - const ALLOW_NOTHING = 0x00, - const ALLOW_SAME_ORIGIN = 0x01, - const ALLOW_TOP_NAVIGATION = 0x02, - const ALLOW_FORMS = 0x04, - const ALLOW_SCRIPTS = 0x08, - const ALLOW_POINTER_LOCK = 0x10, - const ALLOW_POPUPS = 0x20 + #[derive(JSTraceable, MallocSizeOf)] + struct SandboxAllowance: u8 { + const ALLOW_NOTHING = 0x00; + const ALLOW_SAME_ORIGIN = 0x01; + const ALLOW_TOP_NAVIGATION = 0x02; + const ALLOW_FORMS = 0x04; + const ALLOW_SCRIPTS = 0x08; + const ALLOW_POINTER_LOCK = 0x10; + const ALLOW_POPUPS = 0x20; } } #[derive(PartialEq)] +pub enum NavigationType { + InitialAboutBlank, + Regular, +} + +#[derive(PartialEq)] enum ProcessingMode { FirstTime, NotFirstTime, @@ -78,11 +71,14 @@ enum ProcessingMode { #[dom_struct] pub struct HTMLIFrameElement { htmlelement: HTMLElement, - frame_id: FrameId, + top_level_browsing_context_id: Cell<Option<TopLevelBrowsingContextId>>, + browsing_context_id: Cell<Option<BrowsingContextId>>, pipeline_id: Cell<Option<PipelineId>>, - sandbox: MutNullableJS<DOMTokenList>, + pending_pipeline_id: Cell<Option<PipelineId>>, + about_blank_pipeline_id: Cell<Option<PipelineId>>, + sandbox: MutNullableDom<DOMTokenList>, sandbox_allowance: Cell<Option<SandboxAllowance>>, - load_blocker: DOMRefCell<Option<LoadBlocker>>, + load_blocker: DomRefCell<Option<LoadBlocker>>, visibility: Cell<bool>, } @@ -95,172 +91,348 @@ impl HTMLIFrameElement { /// step 1. fn get_url(&self) -> ServoUrl { let element = self.upcast::<Element>(); - element.get_attribute(&ns!(), &local_name!("src")).and_then(|src| { - let url = src.value(); - if url.is_empty() { - None - } else { - document_from_node(self).base_url().join(&url).ok() - } - }).unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap()) - } - - pub fn generate_new_pipeline_id(&self) -> (Option<PipelineId>, PipelineId) { - let old_pipeline_id = self.pipeline_id.get(); - let new_pipeline_id = PipelineId::new(); - self.pipeline_id.set(Some(new_pipeline_id)); - debug!("Frame {} created pipeline {}.", self.frame_id, new_pipeline_id); - (old_pipeline_id, new_pipeline_id) + element + .get_attribute(&ns!(), &local_name!("src")) + .and_then(|src| { + let url = src.value(); + if url.is_empty() { + None + } else { + document_from_node(self).base_url().join(&url).ok() + } + }) + .unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap()) } - pub fn navigate_or_reload_child_browsing_context(&self, load_data: Option<LoadData>, replace: bool) { + pub fn navigate_or_reload_child_browsing_context( + &self, + mut load_data: LoadData, + nav_type: NavigationType, + replace: HistoryEntryReplacement, + ) { let sandboxed = if self.is_sandboxed() { IFrameSandboxed } else { IFrameUnsandboxed }; - let document = document_from_node(self); + let browsing_context_id = match self.browsing_context_id() { + None => return warn!("Navigating unattached iframe."), + Some(id) => id, + }; - let mut load_blocker = self.load_blocker.borrow_mut(); - // Any oustanding load is finished from the point of view of the blocked - // document; the new navigation will continue blocking it. - LoadBlocker::terminate(&mut load_blocker); + let top_level_browsing_context_id = match self.top_level_browsing_context_id() { + None => return warn!("Navigating unattached iframe."), + Some(id) => id, + }; + + let document = document_from_node(self); - //TODO(#9592): Deal with the case where an iframe is being reloaded so url is None. - // The iframe should always have access to the nested context's active - // document URL through the browsing context. - if let Some(ref load_data) = load_data { - *load_blocker = Some(LoadBlocker::new(&*document, LoadType::Subframe(load_data.url.clone()))); + { + let mut load_blocker = self.load_blocker.borrow_mut(); + // Any oustanding load is finished from the point of view of the blocked + // document; the new navigation will continue blocking it. + LoadBlocker::terminate(&mut load_blocker); + } + + if load_data.url.scheme() == "javascript" { + let window_proxy = self.GetContentWindow(); + if let Some(window_proxy) = window_proxy { + // Important re security. See https://github.com/servo/servo/issues/23373 + // TODO: check according to https://w3c.github.io/webappsec-csp/#should-block-navigation-request + if ScriptThread::check_load_origin(&load_data.load_origin, &document.url().origin()) + { + ScriptThread::eval_js_url(&window_proxy.global(), &mut load_data); + } + } } + match load_data.js_eval_result { + Some(JsEvalResult::NoContent) => (), + _ => { + let mut load_blocker = self.load_blocker.borrow_mut(); + *load_blocker = Some(LoadBlocker::new( + &*document, + LoadType::Subframe(load_data.url.clone()), + )); + }, + }; + let window = window_from_node(self); - let (old_pipeline_id, new_pipeline_id) = self.generate_new_pipeline_id(); - let private_iframe = self.privatebrowsing(); - let frame_type = if self.Mozbrowser() { FrameType::MozBrowserIFrame } else { FrameType::IFrame }; + let old_pipeline_id = self.pipeline_id(); + let new_pipeline_id = PipelineId::new(); + self.pending_pipeline_id.set(Some(new_pipeline_id)); let global_scope = window.upcast::<GlobalScope>(); let load_info = IFrameLoadInfo { parent_pipeline_id: global_scope.pipeline_id(), - frame_id: self.frame_id, + browsing_context_id: browsing_context_id, + top_level_browsing_context_id: top_level_browsing_context_id, new_pipeline_id: new_pipeline_id, - is_private: private_iframe, - frame_type: frame_type, + is_private: false, // FIXME + inherited_secure_context: load_data.inherited_secure_context, replace: replace, }; - if load_data.as_ref().map_or(false, |d| d.url.as_str() == "about:blank") { - let (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap(); - - global_scope - .constellation_chan() - .send(ConstellationMsg::ScriptLoadedAboutBlankInIFrame(load_info, pipeline_sender)) - .unwrap(); - - let new_layout_info = NewLayoutInfo { - parent_info: Some((global_scope.pipeline_id(), frame_type)), - new_pipeline_id: new_pipeline_id, - frame_id: self.frame_id, - load_data: load_data.unwrap(), - pipeline_port: pipeline_receiver, - content_process_shutdown_chan: None, - window_size: None, - layout_threads: PREFS.get("layout.threads").as_u64().expect("count") as usize, - }; - - ScriptThread::process_attach_layout(new_layout_info, document.origin().clone()); - } else { - let load_info = IFrameLoadInfoWithData { - info: load_info, - load_data: load_data, - old_pipeline_id: old_pipeline_id, - sandbox: sandboxed, - }; - global_scope - .constellation_chan() - .send(ConstellationMsg::ScriptLoadedURLInIFrame(load_info)) - .unwrap(); - } + let window_size = WindowSizeData { + initial_viewport: window + .inner_window_dimensions_query(browsing_context_id) + .unwrap_or_default(), + device_pixel_ratio: window.device_pixel_ratio(), + }; - if PREFS.is_mozbrowser_enabled() { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadstart - self.dispatch_mozbrowser_event(MozBrowserEvent::LoadStart); + match nav_type { + NavigationType::InitialAboutBlank => { + let (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap(); + + self.about_blank_pipeline_id.set(Some(new_pipeline_id)); + + let load_info = IFrameLoadInfoWithData { + info: load_info, + load_data: load_data.clone(), + old_pipeline_id: old_pipeline_id, + sandbox: sandboxed, + window_size, + }; + global_scope + .script_to_constellation_chan() + .send(ScriptMsg::ScriptNewIFrame(load_info, pipeline_sender)) + .unwrap(); + + let new_layout_info = NewLayoutInfo { + parent_info: Some(global_scope.pipeline_id()), + new_pipeline_id: new_pipeline_id, + browsing_context_id: browsing_context_id, + top_level_browsing_context_id: top_level_browsing_context_id, + opener: None, + load_data: load_data, + pipeline_port: pipeline_receiver, + window_size, + }; + + self.pipeline_id.set(Some(new_pipeline_id)); + ScriptThread::process_attach_layout(new_layout_info, document.origin().clone()); + }, + NavigationType::Regular => { + let load_info = IFrameLoadInfoWithData { + info: load_info, + load_data: load_data, + old_pipeline_id: old_pipeline_id, + sandbox: sandboxed, + window_size, + }; + global_scope + .script_to_constellation_chan() + .send(ScriptMsg::ScriptLoadedURLInIFrame(load_info)) + .unwrap(); + }, } } - /// https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes + /// <https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes> fn process_the_iframe_attributes(&self, mode: ProcessingMode) { - // TODO: srcdoc - - // https://github.com/whatwg/html/issues/490 - if mode == ProcessingMode::FirstTime && !self.upcast::<Element>().has_attribute(&local_name!("src")) { + if self + .upcast::<Element>() + .has_attribute(&local_name!("srcdoc")) + { + let url = ServoUrl::parse("about:srcdoc").unwrap(); + let document = document_from_node(self); let window = window_from_node(self); - let event_loop = window.dom_manipulation_task_source(); - let _ = event_loop.queue(box IFrameLoadEventSteps::new(self), - window.upcast()); + let pipeline_id = Some(window.upcast::<GlobalScope>().pipeline_id()); + let mut load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + url, + pipeline_id, + window.upcast::<GlobalScope>().get_referrer(), + document.get_referrer_policy(), + Some(window.upcast::<GlobalScope>().is_secure_context()), + ); + let element = self.upcast::<Element>(); + load_data.srcdoc = String::from(element.get_string_attribute(&local_name!("srcdoc"))); + self.navigate_or_reload_child_browsing_context( + load_data, + NavigationType::Regular, + HistoryEntryReplacement::Disabled, + ); return; } - let url = self.get_url(); + let window = window_from_node(self); - // TODO: check ancestor browsing contexts for same URL + // https://html.spec.whatwg.org/multipage/#attr-iframe-name + // Note: the spec says to set the name 'when the nested browsing context is created'. + // The current implementation sets the name on the window, + // when the iframe attributes are first processed. + if mode == ProcessingMode::FirstTime { + if let Some(window) = self.GetContentWindow() { + window.set_name( + self.upcast::<Element>() + .get_name() + .map_or(DOMString::from(""), |n| DOMString::from(&*n)), + ); + } + } - let document = document_from_node(self); - self.navigate_or_reload_child_browsing_context( - Some(LoadData::new(url, document.get_referrer_policy(), Some(document.url()))), false); - } + // https://github.com/whatwg/html/issues/490 + if mode == ProcessingMode::FirstTime && + !self.upcast::<Element>().has_attribute(&local_name!("src")) + { + let this = Trusted::new(self); + let pipeline_id = self.pipeline_id().unwrap(); + // FIXME(nox): Why are errors silenced here? + let _ = window.task_manager().dom_manipulation_task_source().queue( + task!(iframe_load_event_steps: move || { + this.root().iframe_load_event_steps(pipeline_id); + }), + window.upcast(), + ); + return; + } - #[allow(unsafe_code)] - pub fn dispatch_mozbrowser_event(&self, event: MozBrowserEvent) { - assert!(PREFS.is_mozbrowser_enabled()); + let url = self.get_url(); - if self.Mozbrowser() { - let window = window_from_node(self); - let custom_event = build_mozbrowser_custom_event(&window, event); - custom_event.upcast::<Event>().fire(self.upcast()); + // TODO(#25748): + // By spec, we return early if there's an ancestor browsing context + // "whose active document's url, ignoring fragments, is equal". + // However, asking about ancestor browsing contexts is more nuanced than + // it sounds and not implemented here. + // Within a single origin, we can do it by walking window proxies, + // and this check covers only that single-origin case, protecting + // against simple typo self-includes but nothing more elaborate. + let mut ancestor = window.GetParent(); + while let Some(a) = ancestor { + if let Some(ancestor_url) = a.document().map(|d| d.url()) { + if ancestor_url.scheme() == url.scheme() && + ancestor_url.username() == url.username() && + ancestor_url.password() == url.password() && + ancestor_url.host() == url.host() && + ancestor_url.port() == url.port() && + ancestor_url.path() == url.path() && + ancestor_url.query() == url.query() + { + return; + } + } + ancestor = a.parent().map(|p| DomRoot::from_ref(p)); } + + let creator_pipeline_id = if url.as_str() == "about:blank" { + Some(window.upcast::<GlobalScope>().pipeline_id()) + } else { + None + }; + + let document = document_from_node(self); + let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + url, + creator_pipeline_id, + window.upcast::<GlobalScope>().get_referrer(), + document.get_referrer_policy(), + Some(window.upcast::<GlobalScope>().is_secure_context()), + ); + + let pipeline_id = self.pipeline_id(); + // If the initial `about:blank` page is the current page, load with replacement enabled, + // see https://html.spec.whatwg.org/multipage/#the-iframe-element:about:blank-3 + let is_about_blank = + pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get(); + let replace = if is_about_blank { + HistoryEntryReplacement::Enabled + } else { + HistoryEntryReplacement::Disabled + }; + self.navigate_or_reload_child_browsing_context(load_data, NavigationType::Regular, replace); } fn create_nested_browsing_context(&self) { // Synchronously create a new context and navigate it to about:blank. let url = ServoUrl::parse("about:blank").unwrap(); let document = document_from_node(self); - let load_data = LoadData::new(url, - document.get_referrer_policy(), - Some(document.url().clone())); - self.navigate_or_reload_child_browsing_context(Some(load_data), false); + let window = window_from_node(self); + let pipeline_id = Some(window.upcast::<GlobalScope>().pipeline_id()); + let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + url, + pipeline_id, + window.upcast::<GlobalScope>().get_referrer(), + document.get_referrer_policy(), + Some(window.upcast::<GlobalScope>().is_secure_context()), + ); + let browsing_context_id = BrowsingContextId::new(); + let top_level_browsing_context_id = window.window_proxy().top_level_browsing_context_id(); + self.pipeline_id.set(None); + self.pending_pipeline_id.set(None); + self.top_level_browsing_context_id + .set(Some(top_level_browsing_context_id)); + self.browsing_context_id.set(Some(browsing_context_id)); + self.navigate_or_reload_child_browsing_context( + load_data, + NavigationType::InitialAboutBlank, + HistoryEntryReplacement::Disabled, + ); } - pub fn update_pipeline_id(&self, new_pipeline_id: PipelineId) { + fn destroy_nested_browsing_context(&self) { + self.pipeline_id.set(None); + self.pending_pipeline_id.set(None); + self.about_blank_pipeline_id.set(None); + self.top_level_browsing_context_id.set(None); + self.browsing_context_id.set(None); + } + + pub fn update_pipeline_id(&self, new_pipeline_id: PipelineId, reason: UpdatePipelineIdReason) { + if self.pending_pipeline_id.get() != Some(new_pipeline_id) && + reason == UpdatePipelineIdReason::Navigation + { + return; + } + self.pipeline_id.set(Some(new_pipeline_id)); - let mut blocker = self.load_blocker.borrow_mut(); - LoadBlocker::terminate(&mut blocker); + // Only terminate the load blocker if the pipeline id was updated due to a traversal. + // The load blocker will be terminated for a navigation in iframe_load_event_steps. + if reason == UpdatePipelineIdReason::Traversal { + let mut blocker = self.load_blocker.borrow_mut(); + LoadBlocker::terminate(&mut blocker); + } self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + let window = window_from_node(self); + window.reflow(ReflowGoal::Full, ReflowReason::FramedContentChanged); } - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLIFrameElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLIFrameElement { HTMLIFrameElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), - frame_id: FrameId::new(), + browsing_context_id: Cell::new(None), + top_level_browsing_context_id: Cell::new(None), pipeline_id: Cell::new(None), + pending_pipeline_id: Cell::new(None), + about_blank_pipeline_id: Cell::new(None), sandbox: Default::default(), sandbox_allowance: Cell::new(None), - load_blocker: DOMRefCell::new(None), + load_blocker: DomRefCell::new(None), visibility: Cell::new(true), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLIFrameElement> { - Node::reflect_node(box HTMLIFrameElement::new_inherited(local_name, prefix, document), - document, - HTMLIFrameElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLIFrameElement> { + Node::reflect_node( + Box::new(HTMLIFrameElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } #[inline] @@ -269,26 +441,18 @@ impl HTMLIFrameElement { } #[inline] - pub fn frame_id(&self) -> FrameId { - self.frame_id + pub fn browsing_context_id(&self) -> Option<BrowsingContextId> { + self.browsing_context_id.get() + } + + #[inline] + pub fn top_level_browsing_context_id(&self) -> Option<TopLevelBrowsingContextId> { + self.top_level_browsing_context_id.get() } pub fn change_visibility_status(&self, visibility: bool) { if self.visibility.get() != visibility { self.visibility.set(visibility); - - // Visibility changes are only exposed to Mozbrowser iframes - if self.Mozbrowser() { - self.dispatch_mozbrowser_event(MozBrowserEvent::VisibilityChange(visibility)); - } - } - } - - pub fn set_visible(&self, visible: bool) { - if let Some(pipeline_id) = self.pipeline_id.get() { - let window = window_from_node(self); - let msg = ConstellationMsg::SetVisible(pipeline_id, visible); - window.upcast::<GlobalScope>().constellation_chan().send(msg).unwrap(); } } @@ -296,7 +460,9 @@ impl HTMLIFrameElement { pub fn iframe_load_event_steps(&self, loaded_pipeline: PipelineId) { // TODO(#9592): assert that the load blocker is present at all times when we // can guarantee that it's created for the case of iframe.reload(). - if Some(loaded_pipeline) != self.pipeline_id() { return; } + if Some(loaded_pipeline) != self.pending_pipeline_id.get() { + return; + } // TODO A cross-origin child document would not be easily accessible // from this script thread. It's unclear how to implement @@ -313,211 +479,101 @@ impl HTMLIFrameElement { // TODO Step 5 - unset child document `mut iframe load` flag let window = window_from_node(self); - window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::IFrameLoadEvent); - } - - /// Check whether the iframe has the mozprivatebrowsing attribute set - pub fn privatebrowsing(&self) -> bool { - if self.Mozbrowser() { - let element = self.upcast::<Element>(); - element.has_attribute(&LocalName::from("mozprivatebrowsing")) - } else { - false - } + window.reflow(ReflowGoal::Full, ReflowReason::IFrameLoadEvent); } } pub trait HTMLIFrameElementLayoutMethods { fn pipeline_id(self) -> Option<PipelineId>; - fn get_width(&self) -> LengthOrPercentageOrAuto; - fn get_height(&self) -> LengthOrPercentageOrAuto; + fn browsing_context_id(self) -> Option<BrowsingContextId>; + fn get_width(self) -> LengthOrPercentageOrAuto; + fn get_height(self) -> LengthOrPercentageOrAuto; } -impl HTMLIFrameElementLayoutMethods for LayoutJS<HTMLIFrameElement> { +impl HTMLIFrameElementLayoutMethods for LayoutDom<'_, HTMLIFrameElement> { #[inline] #[allow(unsafe_code)] fn pipeline_id(self) -> Option<PipelineId> { - unsafe { - (*self.unsafe_get()).pipeline_id.get() - } - } - - #[allow(unsafe_code)] - fn get_width(&self) -> LengthOrPercentageOrAuto { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("width")) - .map(AttrValue::as_dimension) - .cloned() - .unwrap_or(LengthOrPercentageOrAuto::Auto) - } + unsafe { (*self.unsafe_get()).pipeline_id.get() } } + #[inline] #[allow(unsafe_code)] - fn get_height(&self) -> LengthOrPercentageOrAuto { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("height")) - .map(AttrValue::as_dimension) - .cloned() - .unwrap_or(LengthOrPercentageOrAuto::Auto) - } + fn browsing_context_id(self) -> Option<BrowsingContextId> { + unsafe { (*self.unsafe_get()).browsing_context_id.get() } } -} - -#[allow(unsafe_code)] -pub fn build_mozbrowser_custom_event(window: &Window, event: MozBrowserEvent) -> Root<CustomEvent> { - // TODO(gw): Support mozbrowser event types that have detail which is not a string. - // See https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API - // for a list of mozbrowser events. - let cx = window.get_cx(); - let _ac = JSAutoCompartment::new(cx, window.reflector().get_jsobject().get()); - rooted!(in(cx) let mut detail = UndefinedValue()); - let event_name = Atom::from(event.name()); - unsafe { build_mozbrowser_event_detail(event, cx, detail.handle_mut()); } - CustomEvent::new(window.upcast(), - event_name, - true, - true, - detail.handle()) -} -#[allow(unsafe_code)] -unsafe fn build_mozbrowser_event_detail(event: MozBrowserEvent, - cx: *mut JSContext, - rval: MutableHandleValue) { - match event { - MozBrowserEvent::AsyncScroll | MozBrowserEvent::Close | MozBrowserEvent::ContextMenu | - MozBrowserEvent::LoadEnd | MozBrowserEvent::LoadStart | - MozBrowserEvent::Connected | MozBrowserEvent::OpenSearch | - MozBrowserEvent::UsernameAndPasswordRequired => { - rval.set(NullValue()); - } - MozBrowserEvent::Error(error_type, description, report) => { - BrowserElementErrorEventDetail { - type_: Some(DOMString::from(error_type.name())), - description: Some(DOMString::from(description)), - report: Some(DOMString::from(report)), - version: Some(DOMString::from_string(servo_version())), - }.to_jsval(cx, rval); - }, - MozBrowserEvent::SecurityChange(https_state) => { - BrowserElementSecurityChangeDetail { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsersecuritychange - state: Some(DOMString::from(match https_state { - HttpsState::Modern => "secure", - HttpsState::Deprecated => "broken", - HttpsState::None => "insecure", - }.to_owned())), - // FIXME - Not supported yet: - trackingContent: None, - mixedContent: None, - trackingState: None, - extendedValidation: None, - mixedState: None, - }.to_jsval(cx, rval); - } - MozBrowserEvent::TitleChange(ref string) => { - string.to_jsval(cx, rval); - } - MozBrowserEvent::LocationChange(url, can_go_back, can_go_forward) => { - BrowserElementLocationChangeEventDetail { - url: Some(DOMString::from(url)), - canGoBack: Some(can_go_back), - canGoForward: Some(can_go_forward), - }.to_jsval(cx, rval); - } - MozBrowserEvent::OpenTab(url) => { - BrowserElementOpenTabEventDetail { - url: Some(DOMString::from(url)), - }.to_jsval(cx, rval); - } - MozBrowserEvent::OpenWindow(url, target, features) => { - BrowserElementOpenWindowEventDetail { - url: Some(DOMString::from(url)), - target: target.map(DOMString::from), - features: features.map(DOMString::from), - }.to_jsval(cx, rval); - } - MozBrowserEvent::IconChange(rel, href, sizes) => { - BrowserElementIconChangeEventDetail { - rel: Some(DOMString::from(rel)), - href: Some(DOMString::from(href)), - sizes: Some(DOMString::from(sizes)), - }.to_jsval(cx, rval); - } - MozBrowserEvent::ShowModalPrompt(prompt_type, title, message, return_value) => { - BrowserShowModalPromptEventDetail { - promptType: Some(DOMString::from(prompt_type)), - title: Some(DOMString::from(title)), - message: Some(DOMString::from(message)), - returnValue: Some(DOMString::from(return_value)), - }.to_jsval(cx, rval) - } - MozBrowserEvent::VisibilityChange(visibility) => { - BrowserElementVisibilityChangeEventDetail { - visible: Some(visibility), - }.to_jsval(cx, rval); - } + fn get_width(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("width")) + .map(AttrValue::as_dimension) + .cloned() + .unwrap_or(LengthOrPercentageOrAuto::Auto) } -} -pub fn Navigate(iframe: &HTMLIFrameElement, direction: TraversalDirection) -> ErrorResult { - if iframe.Mozbrowser() { - if iframe.upcast::<Node>().is_in_doc_with_browsing_context() { - let window = window_from_node(iframe); - let msg = ConstellationMsg::TraverseHistory(iframe.pipeline_id(), direction); - window.upcast::<GlobalScope>().constellation_chan().send(msg).unwrap(); - } - - Ok(()) - } else { - debug!(concat!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top", - "level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)")); - Err(Error::NotSupported) + fn get_height(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("height")) + .map(AttrValue::as_dimension) + .cloned() + .unwrap_or(LengthOrPercentageOrAuto::Auto) } } impl HTMLIFrameElementMethods for HTMLIFrameElement { // https://html.spec.whatwg.org/multipage/#dom-iframe-src - fn Src(&self) -> DOMString { - self.upcast::<Element>().get_string_attribute(&local_name!("src")) - } + make_url_getter!(Src, "src"); // https://html.spec.whatwg.org/multipage/#dom-iframe-src - fn SetSrc(&self, src: DOMString) { - self.upcast::<Element>().set_url_attribute(&local_name!("src"), src) - } + make_url_setter!(SetSrc, "src"); + + // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc + make_getter!(Srcdoc, "srcdoc"); + + // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc + make_setter!(SetSrcdoc, "srcdoc"); // https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox - fn Sandbox(&self) -> Root<DOMTokenList> { - self.sandbox.or_init(|| DOMTokenList::new(self.upcast::<Element>(), &local_name!("sandbox"))) + fn Sandbox(&self) -> DomRoot<DOMTokenList> { + self.sandbox.or_init(|| { + DOMTokenList::new( + self.upcast::<Element>(), + &local_name!("sandbox"), + Some(vec![ + Atom::from("allow-same-origin"), + Atom::from("allow-forms"), + Atom::from("allow-pointer-lock"), + Atom::from("allow-popups"), + Atom::from("allow-scripts"), + Atom::from("allow-top-navigation"), + ]), + ) + }) } // https://html.spec.whatwg.org/multipage/#dom-iframe-contentwindow - fn GetContentWindow(&self) -> Option<Root<BrowsingContext>> { - self.pipeline_id.get().and_then(|_| ScriptThread::find_browsing_context(self.frame_id)) + fn GetContentWindow(&self) -> Option<DomRoot<WindowProxy>> { + self.browsing_context_id + .get() + .and_then(|browsing_context_id| ScriptThread::find_window_proxy(browsing_context_id)) } // https://html.spec.whatwg.org/multipage/#dom-iframe-contentdocument // https://html.spec.whatwg.org/multipage/#concept-bcc-content-document - fn GetContentDocument(&self) -> Option<Root<Document>> { + fn GetContentDocument(&self) -> Option<DomRoot<Document>> { // Step 1. - let pipeline_id = match self.pipeline_id.get() { - None => return None, - Some(pipeline_id) => pipeline_id, - }; + let pipeline_id = self.pipeline_id.get()?; + // Step 2-3. // Note that this lookup will fail if the document is dissimilar-origin, // so we should return None in that case. - let document = match ScriptThread::find_document(pipeline_id) { - None => return None, - Some(document) => document, - }; + let document = ScriptThread::find_document(pipeline_id)?; + // Step 4. - let current = GlobalScope::current().as_window().Document(); + let current = GlobalScope::current() + .expect("No current global object") + .as_window() + .Document(); if !current.origin().same_origin_domain(document.origin()) { return None; } @@ -525,83 +581,11 @@ impl HTMLIFrameElementMethods for HTMLIFrameElement { Some(document) } - // Experimental mozbrowser implementation is based on the webidl - // present in the gecko source tree, and the documentation here: - // https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-mozbrowser - fn Mozbrowser(&self) -> bool { - if window_from_node(self).is_mozbrowser() { - let element = self.upcast::<Element>(); - element.has_attribute(&local_name!("mozbrowser")) - } else { - false - } - } - - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-mozbrowser - fn SetMozbrowser(&self, value: bool) { - let element = self.upcast::<Element>(); - element.set_bool_attribute(&local_name!("mozbrowser"), value); - } - // https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen make_bool_getter!(AllowFullscreen, "allowfullscreen"); // https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen make_bool_setter!(SetAllowFullscreen, "allowfullscreen"); - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/goBack - fn GoBack(&self) -> ErrorResult { - Navigate(self, TraversalDirection::Back(1)) - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/goForward - fn GoForward(&self) -> ErrorResult { - Navigate(self, TraversalDirection::Forward(1)) - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/reload - fn Reload(&self, _hard_reload: bool) -> ErrorResult { - if self.Mozbrowser() { - if self.upcast::<Node>().is_in_doc_with_browsing_context() { - self.navigate_or_reload_child_browsing_context(None, true); - } - Ok(()) - } else { - debug!(concat!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top", - "level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)")); - Err(Error::NotSupported) - } - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/setVisible - fn SetVisible(&self, visible: bool) -> ErrorResult { - if self.Mozbrowser() { - self.set_visible(visible); - Ok(()) - } else { - debug!(concat!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top", - "level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)")); - Err(Error::NotSupported) - } - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/getVisible - fn GetVisible(&self) -> Fallible<bool> { - if self.Mozbrowser() { - Ok(self.visibility.get()) - } else { - debug!(concat!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top", - "level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)")); - Err(Error::NotSupported) - } - } - - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/stop - fn Stop(&self) -> ErrorResult { - Err(Error::NotSupported) - } - // https://html.spec.whatwg.org/multipage/#dom-dim-width make_getter!(Width, "width"); // https://html.spec.whatwg.org/multipage/#dom-dim-width @@ -617,46 +601,58 @@ impl HTMLIFrameElementMethods for HTMLIFrameElement { // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:attr-iframe-frameborder make_setter!(SetFrameBorder, "frameborder"); - // check-tidy: no specs after this line - fn SetMozprivatebrowsing(&self, value: bool) { - let element = self.upcast::<Element>(); - element.set_bool_attribute(&LocalName::from("mozprivatebrowsing"), value); - } + // https://html.spec.whatwg.org/multipage/#dom-iframe-name + // A child browsing context checks the name of its iframe only at the time + // it is created; subsequent name sets have no special effect. + make_atomic_setter!(SetName, "name"); - fn Mozprivatebrowsing(&self) -> bool { - if window_from_node(self).is_mozbrowser() { - let element = self.upcast::<Element>(); - element.has_attribute(&LocalName::from("mozprivatebrowsing")) - } else { - false - } - } + // https://html.spec.whatwg.org/multipage/#dom-iframe-name + // This is specified as reflecting the name content attribute of the + // element, not the name of the child browsing context. + make_getter!(Name, "name"); } impl VirtualMethods for HTMLIFrameElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); match attr.local_name() { &local_name!("sandbox") => { - self.sandbox_allowance.set(mutation.new_value(attr).map(|value| { - let mut modes = ALLOW_NOTHING; - for token in value.as_tokens() { - modes |= match &*token.to_ascii_lowercase() { - "allow-same-origin" => ALLOW_SAME_ORIGIN, - "allow-forms" => ALLOW_FORMS, - "allow-pointer-lock" => ALLOW_POINTER_LOCK, - "allow-popups" => ALLOW_POPUPS, - "allow-scripts" => ALLOW_SCRIPTS, - "allow-top-navigation" => ALLOW_TOP_NAVIGATION, - _ => ALLOW_NOTHING - }; - } - modes - })); + self.sandbox_allowance + .set(mutation.new_value(attr).map(|value| { + let mut modes = SandboxAllowance::ALLOW_NOTHING; + for token in value.as_tokens() { + modes |= match &*token.to_ascii_lowercase() { + "allow-same-origin" => SandboxAllowance::ALLOW_SAME_ORIGIN, + "allow-forms" => SandboxAllowance::ALLOW_FORMS, + "allow-pointer-lock" => SandboxAllowance::ALLOW_POINTER_LOCK, + "allow-popups" => SandboxAllowance::ALLOW_POPUPS, + "allow-scripts" => SandboxAllowance::ALLOW_SCRIPTS, + "allow-top-navigation" => SandboxAllowance::ALLOW_TOP_NAVIGATION, + _ => SandboxAllowance::ALLOW_NOTHING, + }; + } + modes + })); + }, + &local_name!("srcdoc") => { + // https://html.spec.whatwg.org/multipage/#the-iframe-element:the-iframe-element-9 + // "Whenever an iframe element with a non-null nested browsing context has its + // srcdoc attribute set, changed, or removed, the user agent must process the + // iframe attributes." + // but we can't check that directly, since the child browsing context + // may be in a different script thread. Instead, we check to see if the parent + // is in a document tree and has a browsing context, which is what causes + // the child browsing context to be created. + + // trigger the processing of iframe attributes whenever "srcdoc" attribute is set, changed or removed + if self.upcast::<Node>().is_connected_with_browsing_context() { + debug!("iframe srcdoc modified while in browsing context."); + self.process_the_iframe_attributes(ProcessingMode::NotFirstTime); + } }, &local_name!("src") => { // https://html.spec.whatwg.org/multipage/#the-iframe-element @@ -664,11 +660,11 @@ impl VirtualMethods for HTMLIFrameElement { // but with no srcdoc attribute specified has its src attribute set, changed, or removed, // the user agent must process the iframe attributes," // but we can't check that directly, since the child browsing context - // may be in a different script thread. Instread, we check to see if the parent + // may be in a different script thread. Instead, we check to see if the parent // is in a document tree and has a browsing context, which is what causes // the child browsing context to be created. - if self.upcast::<Node>().is_in_doc_with_browsing_context() { - debug!("iframe {} src set while in browsing context.", self.frame_id); + if self.upcast::<Node>().is_connected_with_browsing_context() { + debug!("iframe src set while in browsing context."); self.process_the_iframe_attributes(ProcessingMode::NotFirstTime); } }, @@ -681,27 +677,35 @@ impl VirtualMethods for HTMLIFrameElement { &local_name!("sandbox") => AttrValue::from_serialized_tokenlist(value.into()), &local_name!("width") => AttrValue::from_dimension(value.into()), &local_name!("height") => AttrValue::from_dimension(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); - } - - // https://html.spec.whatwg.org/multipage/#the-iframe-element - // "When an iframe element is inserted into a document that has - // a browsing context, the user agent must create a new - // browsing context, set the element's nested browsing context - // to the newly-created browsing context, and then process the - // iframe attributes for the "first time"." - if self.upcast::<Node>().is_in_doc_with_browsing_context() { - debug!("iframe {} bound to browsing context.", self.frame_id); - debug_assert!(tree_in_doc, "is_in_doc_with_bc, but not tree_in_doc"); - self.create_nested_browsing_context(); - self.process_the_iframe_attributes(ProcessingMode::FirstTime); - } + s.bind_to_tree(context); + } + + let tree_connected = context.tree_connected; + let iframe = Trusted::new(self); + document_from_node(self).add_delayed_task(task!(IFrameDelayedInitialize: move || { + let this = iframe.root(); + // https://html.spec.whatwg.org/multipage/#the-iframe-element + // "When an iframe element is inserted into a document that has + // a browsing context, the user agent must create a new + // browsing context, set the element's nested browsing context + // to the newly-created browsing context, and then process the + // iframe attributes for the "first time"." + if this.upcast::<Node>().is_connected_with_browsing_context() { + debug!("iframe bound to browsing context."); + debug_assert!(tree_connected, "is_connected_with_bc, but not tree_connected"); + this.create_nested_browsing_context(); + this.process_the_iframe_attributes(ProcessingMode::FirstTime); + } + })); } fn unbind_from_tree(&self, context: &UnbindContext) { @@ -711,24 +715,41 @@ impl VirtualMethods for HTMLIFrameElement { LoadBlocker::terminate(&mut blocker); // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded - debug!("Unbinding frame {}.", self.frame_id); let window = window_from_node(self); - let (sender, receiver) = ipc::channel().unwrap(); + let (sender, receiver) = + ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); // Ask the constellation to remove the iframe, and tell us the // pipeline ids of the closed pipelines. - let msg = ConstellationMsg::RemoveIFrame(self.frame_id, sender); - window.upcast::<GlobalScope>().constellation_chan().send(msg).unwrap(); + let browsing_context_id = match self.browsing_context_id() { + None => return warn!("Unbinding already unbound iframe."), + Some(id) => id, + }; + debug!("Unbinding frame {}.", browsing_context_id); + + let msg = ScriptMsg::RemoveIFrame(browsing_context_id, sender); + window + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(msg) + .unwrap(); let exited_pipeline_ids = receiver.recv().unwrap(); // The spec for discarding is synchronous, // so we need to discard the browsing contexts now, rather than // when the `PipelineExit` message arrives. for exited_pipeline_id in exited_pipeline_ids { + // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded if let Some(exited_document) = ScriptThread::find_document(exited_pipeline_id) { - exited_document.window().browsing_context().discard(); + debug!( + "Discarding browsing context for pipeline {}", + exited_pipeline_id + ); + let exited_window = exited_document.window(); + exited_window.discard_browsing_context(); for exited_iframe in exited_document.iter_iframes() { - exited_iframe.pipeline_id.set(None); + debug!("Discarding nested browsing context"); + exited_iframe.destroy_nested_browsing_context(); } } } @@ -738,27 +759,6 @@ impl VirtualMethods for HTMLIFrameElement { // the load doesn't think that it's a navigation, but instead // a new iframe. Without this, the constellation gets very // confused. - self.pipeline_id.set(None); - } -} - -struct IFrameLoadEventSteps { - frame_element: Trusted<HTMLIFrameElement>, - pipeline_id: PipelineId, -} - -impl IFrameLoadEventSteps { - fn new(frame_element: &HTMLIFrameElement) -> IFrameLoadEventSteps { - IFrameLoadEventSteps { - frame_element: Trusted::new(frame_element), - pipeline_id: frame_element.pipeline_id().unwrap(), - } - } -} - -impl Runnable for IFrameLoadEventSteps { - fn handler(self: Box<IFrameLoadEventSteps>) { - let this = self.frame_element.root(); - this.iframe_load_event_steps(self.pipeline_id); + self.destroy_nested_browsing_context(); } } diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 389cd87b064..893080edd07 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -1,61 +1,127 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::document_loader::{LoadBlocker, LoadType}; +use crate::dom::activation::Activatable; +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::{DomRefCell, RefMut}; +use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectBinding::DOMRectMethods; +use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods; +use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::document::{determine_policy_for_token, Document}; +use crate::dom::element::{cors_setting_for_element, referrer_policy_for_element}; +use crate::dom::element::{reflect_cross_origin_attribute, set_cross_origin_attribute}; +use crate::dom::element::{ + AttributeMutation, CustomElementCreationMode, Element, ElementCreator, LayoutElementHelpers, +}; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlareaelement::HTMLAreaElement; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; +use crate::dom::htmlmapelement::HTMLMapElement; +use crate::dom::htmlpictureelement::HTMLPictureElement; +use crate::dom::htmlsourceelement::HTMLSourceElement; +use crate::dom::mouseevent::MouseEvent; +use crate::dom::node::UnbindContext; +use crate::dom::node::{ + document_from_node, window_from_node, BindContext, Node, NodeDamage, ShadowIncluding, +}; +use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::values::UNSIGNED_LONG_MAX; +use crate::dom::virtualmethods::VirtualMethods; +use crate::dom::window::Window; +use crate::fetch::create_a_potential_cors_request; +use crate::image_listener::{generate_cache_listener_for_element, ImageCacheListener}; +use crate::microtask::{Microtask, MicrotaskRunnable}; +use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; +use crate::script_thread::ScriptThread; +use crate::task_source::TaskSource; use app_units::{Au, AU_PER_PX}; -use document_loader::{LoadType, LoadBlocker}; -use dom::activation::Activatable; -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectBinding::DOMRectMethods; -use dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::HTMLImageElementBinding; -use dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods; -use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, MutNullableJS, Root}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; -use dom::element::{reflect_cross_origin_attribute, set_cross_origin_attribute}; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::htmlareaelement::HTMLAreaElement; -use dom::htmlelement::HTMLElement; -use dom::htmlformelement::{FormControl, HTMLFormElement}; -use dom::htmlmapelement::HTMLMapElement; -use dom::mouseevent::MouseEvent; -use dom::node::{Node, NodeDamage, document_from_node, window_from_node}; -use dom::values::UNSIGNED_LONG_MAX; -use dom::virtualmethods::VirtualMethods; -use dom::window::Window; +use cssparser::{Parser, ParserInput}; use dom_struct::dom_struct; -use euclid::point::Point2D; -use html5ever_atoms::LocalName; +use euclid::Point2D; +use html5ever::{LocalName, Prefix, QualName}; use ipc_channel::ipc; +use ipc_channel::ipc::IpcSender; use ipc_channel::router::ROUTER; -use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg}; +use mime::{self, Mime}; +use msg::constellation_msg::PipelineId; use net_traits::image::base::{Image, ImageMetadata}; -use net_traits::image_cache::{CanRequestImages, ImageCache, ImageOrMetadataAvailable}; -use net_traits::image_cache::{ImageResponder, ImageResponse, ImageState, PendingImageId}; -use net_traits::image_cache::UsePlaceholder; -use net_traits::request::{RequestInit, Type as RequestType}; -use network_listener::{NetworkListener, PreInvoke}; +use net_traits::image_cache::{ + CorsStatus, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, + PendingImageId, PendingImageResponse, UsePlaceholder, +}; +use net_traits::request::{CorsSettings, Destination, Initiator, Referrer, RequestBuilder}; +use net_traits::{FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError}; +use net_traits::{ReferrerPolicy, ResourceFetchTiming, ResourceTimingType}; use num_traits::ToPrimitive; -use script_thread::Runnable; +use servo_url::origin::ImmutableOrigin; +use servo_url::origin::MutableOrigin; use servo_url::ServoUrl; use std::cell::Cell; +use std::char; +use std::collections::HashSet; use std::default::Default; use std::i32; +use std::mem; use std::sync::{Arc, Mutex}; -use style::attr::{AttrValue, LengthOrPercentageOrAuto}; -use task_source::TaskSource; +use style::attr::{ + parse_double, parse_length, parse_unsigned_integer, AttrValue, LengthOrPercentageOrAuto, +}; +use style::context::QuirksMode; +use style::media_queries::MediaList; +use style::parser::ParserContext; +use style::str::is_ascii_digit; +use style::stylesheets::{CssRuleType, Origin}; +use style::values::specified::length::{Length, NoCalcLength}; +use style::values::specified::{source_size_list::SourceSizeList, AbsoluteLength}; +use style_traits::ParsingMode; + +enum ParseState { + InDescriptor, + InParens, + AfterDescriptor, +} + +pub struct SourceSet { + image_sources: Vec<ImageSource>, + source_size: SourceSizeList, +} + +impl SourceSet { + fn new() -> SourceSet { + SourceSet { + image_sources: Vec::new(), + source_size: SourceSizeList::empty(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ImageSource { + pub url: String, + pub descriptor: Descriptor, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Descriptor { + pub wid: Option<u32>, + pub den: Option<f64>, +} -#[derive(JSTraceable, HeapSizeOf)] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf)] #[allow(dead_code)] enum State { Unavailable, @@ -63,57 +129,57 @@ enum State { CompletelyAvailable, Broken, } -#[derive(JSTraceable, HeapSizeOf)] -#[must_root] + +#[derive(Clone, Copy, JSTraceable, MallocSizeOf)] +enum ImageRequestPhase { + Pending, + Current, +} +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] struct ImageRequest { state: State, parsed_url: Option<ServoUrl>, - source_url: Option<DOMString>, + source_url: Option<USVString>, blocker: Option<LoadBlocker>, - #[ignore_heap_size_of = "Arc"] + #[ignore_malloc_size_of = "Arc"] image: Option<Arc<Image>>, metadata: Option<ImageMetadata>, + final_url: Option<ServoUrl>, + current_pixel_density: Option<f64>, } #[dom_struct] pub struct HTMLImageElement { htmlelement: HTMLElement, - current_request: DOMRefCell<ImageRequest>, - pending_request: DOMRefCell<ImageRequest>, - form_owner: MutNullableJS<HTMLFormElement>, + image_request: Cell<ImageRequestPhase>, + current_request: DomRefCell<ImageRequest>, + pending_request: DomRefCell<ImageRequest>, + form_owner: MutNullableDom<HTMLFormElement>, generation: Cell<u32>, + #[ignore_malloc_size_of = "SourceSet"] + source_set: DomRefCell<SourceSet>, + last_selected_source: DomRefCell<Option<USVString>>, } impl HTMLImageElement { pub fn get_url(&self) -> Option<ServoUrl> { self.current_request.borrow().parsed_url.clone() } -} - -struct ImageResponseHandlerRunnable { - element: Trusted<HTMLImageElement>, - image: ImageResponse, - generation: u32, -} - -impl ImageResponseHandlerRunnable { - fn new(element: Trusted<HTMLImageElement>, image: ImageResponse, generation: u32) - -> ImageResponseHandlerRunnable { - ImageResponseHandlerRunnable { - element: element, - image: image, - generation: generation, + // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument + pub 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 { + return Ok(false); + } } - } -} -impl Runnable for ImageResponseHandlerRunnable { - fn name(&self) -> &'static str { "ImageResponseHandlerRunnable" } - - fn handler(self: Box<Self>) { - let element = self.element.root(); - // Ignore any image response for a previous request that has been discarded. - if element.generation.get() == self.generation { - element.process_image_response(self.image); + match self.current_request.borrow().state { + // If image's current request's state is broken, then throw an "InvalidStateError" DOMException. + State::Broken => Err(Error::InvalidState), + State::CompletelyAvailable => Ok(true), + // If image is not fully decodable, then return bad. + State::PartiallyAvailable | State::Unavailable => Ok(false), } } } @@ -121,11 +187,18 @@ impl Runnable for ImageResponseHandlerRunnable { /// The context required for asynchronously loading an external image. struct ImageContext { /// Reference to the script thread image cache. - image_cache: Arc<ImageCache>, + image_cache: Arc<dyn ImageCache>, /// Indicates whether the request failed, and why status: Result<(), NetworkError>, /// The cache ID for this request. id: PendingImageId, + /// Used to mark abort + aborted: bool, + /// The document associated with this request + doc: Trusted<Document>, + /// timing data for this resource + resource_timing: ResourceFetchTiming, + url: ServoUrl, } impl FetchResponseListener for ImageContext { @@ -133,112 +206,151 @@ impl FetchResponseListener for ImageContext { fn process_request_eof(&mut self) {} fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { - self.image_cache.notify_pending_response( - self.id, - FetchResponseMsg::ProcessResponse(metadata.clone())); - - let metadata = metadata.ok().map(|meta| { - match meta { - FetchMetadata::Unfiltered(m) => m, - FetchMetadata::Filtered { unsafe_, .. } => unsafe_ - } + debug!("got {:?} for {:?}", metadata.as_ref().map(|_| ()), self.url); + self.image_cache + .notify_pending_response(self.id, FetchResponseMsg::ProcessResponse(metadata.clone())); + + let metadata = metadata.ok().map(|meta| match meta { + FetchMetadata::Unfiltered(m) => m, + FetchMetadata::Filtered { unsafe_, .. } => unsafe_, }); - let status_code = metadata.as_ref().and_then(|m| { - m.status.as_ref().map(|&(code, _)| code) - }).unwrap_or(0); + // Step 14.5 of https://html.spec.whatwg.org/multipage/#img-environment-changes + if let Some(metadata) = metadata.as_ref() { + if let Some(ref content_type) = metadata.content_type { + let mime: Mime = content_type.clone().into_inner().into(); + if mime.type_() == mime::MULTIPART && mime.subtype().as_str() == "x-mixed-replace" { + self.aborted = true; + } + } + } + + let status_code = metadata + .as_ref() + .and_then(|m| m.status.as_ref().map(|&(code, _)| code)) + .unwrap_or(0); self.status = match status_code { - 0 => Err(NetworkError::Internal("No http status code received".to_owned())), - 200...299 => Ok(()), // HTTP ok status codes - _ => Err(NetworkError::Internal(format!("HTTP error code {}", status_code))) + 0 => Err(NetworkError::Internal( + "No http status code received".to_owned(), + )), + 200..=299 => Ok(()), // HTTP ok status codes + _ => Err(NetworkError::Internal(format!( + "HTTP error code {}", + status_code + ))), }; } fn process_response_chunk(&mut self, payload: Vec<u8>) { if self.status.is_ok() { - self.image_cache.notify_pending_response( - self.id, - FetchResponseMsg::ProcessResponseChunk(payload)); + self.image_cache + .notify_pending_response(self.id, FetchResponseMsg::ProcessResponseChunk(payload)); } } - fn process_response_eof(&mut self, response: Result<(), NetworkError>) { - self.image_cache.notify_pending_response( - self.id, - FetchResponseMsg::ProcessResponseEOF(response)); + fn process_response_eof(&mut self, response: Result<ResourceFetchTiming, NetworkError>) { + self.image_cache + .notify_pending_response(self.id, FetchResponseMsg::ProcessResponseEOF(response)); } -} - -impl PreInvoke for ImageContext {} -impl HTMLImageElement { - /// Update the current image with a valid URL. - fn update_image_with_url(&self, img_url: ServoUrl, src: DOMString) { - { - let mut current_request = self.current_request.borrow_mut(); - current_request.parsed_url = Some(img_url.clone()); - current_request.source_url = Some(src); - - LoadBlocker::terminate(&mut current_request.blocker); - let document = document_from_node(self); - current_request.blocker = - Some(LoadBlocker::new(&*document, LoadType::Image(img_url.clone()))); - } + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } - fn add_cache_listener_for_element(image_cache: Arc<ImageCache>, - id: PendingImageId, - elem: &HTMLImageElement) { - let trusted_node = Trusted::new(elem); - let (responder_sender, responder_receiver) = ipc::channel().unwrap(); + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } - let window = window_from_node(elem); - let task_source = window.networking_task_source(); - let wrapper = window.get_runnable_wrapper(); - let generation = elem.generation.get(); - ROUTER.add_route(responder_receiver.to_opaque(), box move |message| { - debug!("Got image {:?}", message); - // Return the image via a message to the script thread, which marks - // the element as dirty and triggers a reflow. - let runnable = ImageResponseHandlerRunnable::new( - trusted_node.clone(), message.to().unwrap(), generation); - let _ = task_source.queue_with_wrapper(box runnable, &wrapper); - }); + fn submit_resource_timing(&mut self) { + network_listener::submit_timing(self) + } +} - image_cache.add_listener(id, ImageResponder::new(responder_sender, id)); - } +impl ResourceTimingListener for ImageContext { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + ( + InitiatorType::LocalName("img".to_string()), + self.url.clone(), + ) + } - let window = window_from_node(self); - let image_cache = window.image_cache(); - let response = - image_cache.find_image_or_metadata(img_url.clone().into(), - UsePlaceholder::Yes, - CanRequestImages::Yes); - match response { - Ok(ImageOrMetadataAvailable::ImageAvailable(image)) => { - self.process_image_response(ImageResponse::Loaded(image)); - } + fn resource_timing_global(&self) -> DomRoot<GlobalScope> { + self.doc.root().global() + } +} - Ok(ImageOrMetadataAvailable::MetadataAvailable(m)) => { - self.process_image_response(ImageResponse::MetadataLoaded(m)); - } +impl PreInvoke for ImageContext { + fn should_invoke(&self) -> bool { + !self.aborted + } +} - Err(ImageState::Pending(id)) => { - add_cache_listener_for_element(image_cache.clone(), id, self); - } +#[derive(PartialEq)] +pub(crate) enum FromPictureOrSrcSet { + Yes, + No, +} - Err(ImageState::LoadError) => { - self.process_image_response(ImageResponse::None); - } +// https://html.spec.whatwg.org/multipage/#update-the-image-data steps 17-20 +// This function is also used to prefetch an image in `script::dom::servoparser::prefetch`. +pub(crate) fn image_fetch_request( + img_url: ServoUrl, + origin: ImmutableOrigin, + referrer: Referrer, + pipeline_id: PipelineId, + cors_setting: Option<CorsSettings>, + referrer_policy: Option<ReferrerPolicy>, + from_picture_or_srcset: FromPictureOrSrcSet, +) -> RequestBuilder { + let mut request = + create_a_potential_cors_request(img_url, Destination::Image, cors_setting, None, referrer) + .origin(origin) + .pipeline_id(Some(pipeline_id)) + .referrer_policy(referrer_policy); + if from_picture_or_srcset == FromPictureOrSrcSet::Yes { + request = request.initiator(Initiator::ImageSet); + } + request +} - Err(ImageState::NotRequested(id)) => { - add_cache_listener_for_element(image_cache, id, self); - self.request_image(img_url, id); - } - } +#[allow(non_snake_case)] +impl HTMLImageElement { + /// Update the current image with a valid URL. + fn fetch_image(&self, img_url: &ServoUrl) { + let window = window_from_node(self); + let image_cache = window.image_cache(); + let sender = generate_cache_listener_for_element(self); + let cache_result = image_cache.track_image( + img_url.clone(), + window.origin().immutable().clone(), + cors_setting_for_element(self.upcast()), + sender, + UsePlaceholder::Yes, + ); + + match cache_result { + ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { + image, + url, + is_placeholder, + }) => { + if is_placeholder { + self.process_image_response(ImageResponse::PlaceholderLoaded(image, url)) + } else { + self.process_image_response(ImageResponse::Loaded(image, url)) + } + }, + ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m)) => { + self.process_image_response(ImageResponse::MetadataLoaded(m)) + }, + ImageCacheResult::Pending(_) => (), + ImageCacheResult::ReadyForRequest(id) => self.fetch_request(img_url, id), + ImageCacheResult::LoadError => self.process_image_response(ImageResponse::None), + }; } - fn request_image(&self, img_url: ServoUrl, id: PendingImageId) { + fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) { let document = document_from_node(self); let window = window_from_node(self); @@ -246,62 +358,120 @@ impl HTMLImageElement { image_cache: window.image_cache(), status: Ok(()), id: id, + aborted: false, + doc: Trusted::new(&document), + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), + url: img_url.clone(), })); let (action_sender, action_receiver) = ipc::channel().unwrap(); + let (task_source, canceller) = document + .window() + .task_manager() + .networking_task_source_with_canceller(); let listener = NetworkListener { - context: context, - task_source: window.networking_task_source(), - wrapper: Some(window.get_runnable_wrapper()), - }; - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_fetch(message.to().unwrap()); - }); - - let request = RequestInit { - url: img_url.clone(), - origin: document.url().clone(), - type_: RequestType::Image, - pipeline_id: Some(document.global().pipeline_id()), - .. RequestInit::default() + context, + task_source, + canceller: Some(canceller), }; + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + listener.notify_fetch(message.to().unwrap()); + }), + ); + + let request = image_fetch_request( + img_url.clone(), + document.origin().immutable().clone(), + document.global().get_referrer(), + document.global().pipeline_id(), + cors_setting_for_element(self.upcast()), + referrer_policy_for_element(self.upcast()), + if Self::uses_srcset_or_picture(self.upcast()) { + FromPictureOrSrcSet::Yes + } else { + FromPictureOrSrcSet::No + }, + ); // This is a background load because the load blocker already fulfills the // purpose of delaying the document's load event. - document.loader().fetch_async_background(request, action_sender); + document + .loader_mut() + .fetch_async_background(request, action_sender); } - fn process_image_response(&self, image: ImageResponse) { - let (image, metadata, trigger_image_load, trigger_image_error) = match image { - ImageResponse::Loaded(image) | ImageResponse::PlaceholderLoaded(image) => { - (Some(image.clone()), - Some(ImageMetadata { height: image.height, width: image.width }), - true, - false) - } - ImageResponse::MetadataLoaded(meta) => { - (None, Some(meta), false, false) - } - ImageResponse::None => (None, None, false, true) - }; - self.current_request.borrow_mut().image = image; - self.current_request.borrow_mut().metadata = metadata; - + // Steps common to when an image has been loaded. + fn handle_loaded_image(&self, image: Arc<Image>, url: ServoUrl) { + self.current_request.borrow_mut().metadata = Some(ImageMetadata { + height: image.height, + width: image.width, + }); + 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; + LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker); // Mark the node dirty self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } - // Fire image.onload + /// Step 24 of https://html.spec.whatwg.org/multipage/#update-the-image-data + fn process_image_response(&self, image: ImageResponse) { + // TODO: Handle multipart/x-mixed-replace + let (trigger_image_load, trigger_image_error) = match (image, self.image_request.get()) { + (ImageResponse::Loaded(image, url), ImageRequestPhase::Current) => { + self.handle_loaded_image(image, url); + (true, false) + }, + (ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Current) => { + self.handle_loaded_image(image, url); + (false, true) + }, + (ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => { + self.abort_request(State::Unavailable, ImageRequestPhase::Pending); + self.image_request.set(ImageRequestPhase::Current); + self.handle_loaded_image(image, url); + (true, false) + }, + (ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Pending) => { + self.abort_request(State::Unavailable, ImageRequestPhase::Pending); + self.image_request.set(ImageRequestPhase::Current); + self.handle_loaded_image(image, url); + (false, true) + }, + (ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => { + self.current_request.borrow_mut().state = State::PartiallyAvailable; + self.current_request.borrow_mut().metadata = Some(meta); + (false, false) + }, + (ImageResponse::MetadataLoaded(_), ImageRequestPhase::Pending) => { + self.pending_request.borrow_mut().state = State::PartiallyAvailable; + (false, false) + }, + (ImageResponse::None, ImageRequestPhase::Current) => { + self.abort_request(State::Broken, ImageRequestPhase::Current); + (false, true) + }, + (ImageResponse::None, ImageRequestPhase::Pending) => { + self.abort_request(State::Broken, ImageRequestPhase::Current); + self.abort_request(State::Broken, ImageRequestPhase::Pending); + self.image_request.set(ImageRequestPhase::Current); + (false, true) + }, + }; + + // Fire image.onload and loadend if trigger_image_load { + // TODO: https://html.spec.whatwg.org/multipage/#fire-a-progress-event-or-event self.upcast::<EventTarget>().fire_event(atom!("load")); + self.upcast::<EventTarget>().fire_event(atom!("loadend")); } // Fire image.onerror if trigger_image_error { self.upcast::<EventTarget>().fire_event(atom!("error")); - } - - if trigger_image_load || trigger_image_error { - LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker); + self.upcast::<EventTarget>().fire_event(atom!("loadend")); } // Trigger reflow @@ -309,103 +479,794 @@ impl HTMLImageElement { window.add_pending_reflow(); } - /// Makes the local `image` member match the status of the `src` attribute and starts - /// prefetching the image. This method must be called after `src` is changed. - fn update_image(&self, value: Option<(DOMString, ServoUrl)>) { - // Force any in-progress request to be ignored. - self.generation.set(self.generation.get() + 1); + fn process_image_response_for_environment_change( + &self, + image: ImageResponse, + src: USVString, + generation: u32, + selected_pixel_density: f64, + ) { + match image { + ImageResponse::Loaded(image, url) | ImageResponse::PlaceholderLoaded(image, url) => { + self.pending_request.borrow_mut().metadata = Some(ImageMetadata { + height: image.height, + width: image.width, + }); + 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::MetadataLoaded(meta) => { + self.pending_request.borrow_mut().metadata = Some(meta); + }, + ImageResponse::None => { + self.abort_request(State::Unavailable, ImageRequestPhase::Pending); + }, + }; + } + + /// <https://html.spec.whatwg.org/multipage/#abort-the-image-request> + fn abort_request(&self, state: State, request: ImageRequestPhase) { + let mut request = match request { + ImageRequestPhase::Current => self.current_request.borrow_mut(), + ImageRequestPhase::Pending => self.pending_request.borrow_mut(), + }; + LoadBlocker::terminate(&mut request.blocker); + request.state = state; + request.image = None; + request.metadata = None; + } + + /// <https://html.spec.whatwg.org/multipage/#update-the-source-set> + fn update_source_set(&self) { + // Step 1 + *self.source_set.borrow_mut() = SourceSet::new(); + + // Step 2 + let elem = self.upcast::<Element>(); + let parent = elem.upcast::<Node>().GetParentElement(); + let nodes; + let elements = match parent.as_ref() { + Some(p) => { + if p.is::<HTMLPictureElement>() { + nodes = p.upcast::<Node>().children(); + nodes + .filter_map(DomRoot::downcast::<Element>) + .map(|n| DomRoot::from_ref(&*n)) + .collect() + } else { + vec![DomRoot::from_ref(&*elem)] + } + }, + None => vec![DomRoot::from_ref(&*elem)], + }; + + // Step 3 + let width = match elem.get_attribute(&ns!(), &local_name!("width")) { + Some(x) => match parse_length(&x.value()) { + LengthOrPercentageOrAuto::Length(x) => { + let abs_length = AbsoluteLength::Px(x.to_f32_px()); + Some(Length::NoCalc(NoCalcLength::Absolute(abs_length))) + }, + _ => None, + }, + None => None, + }; + + // Step 4 + for element in &elements { + // Step 4.1 + if *element == DomRoot::from_ref(&*elem) { + let mut source_set = SourceSet::new(); + // Step 4.1.1 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("srcset")) { + source_set.image_sources = parse_a_srcset_attribute(&x.value()); + } + + // Step 4.1.2 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("sizes")) { + source_set.source_size = + parse_a_sizes_attribute(DOMString::from_string(x.value().to_string())); + } + + // Step 4.1.3 + let src_attribute = element.get_string_attribute(&local_name!("src")); + let is_src_empty = src_attribute.is_empty(); + let no_density_source_of_1 = source_set + .image_sources + .iter() + .all(|source| source.descriptor.den != Some(1.)); + let no_width_descriptor = source_set + .image_sources + .iter() + .all(|source| source.descriptor.wid.is_none()); + if !is_src_empty && no_density_source_of_1 && no_width_descriptor { + source_set.image_sources.push(ImageSource { + url: src_attribute.to_string(), + descriptor: Descriptor { + wid: None, + den: None, + }, + }) + } + + // Step 4.1.4 + self.normalise_source_densities(&mut source_set, width); + + // Step 4.1.5 + *self.source_set.borrow_mut() = source_set; + + // Step 4.1.6 + return; + } + // Step 4.2 + if !element.is::<HTMLSourceElement>() { + continue; + } + + // Step 4.3 - 4.4 + let mut source_set = SourceSet::new(); + match element.get_attribute(&ns!(), &local_name!("srcset")) { + Some(x) => { + source_set.image_sources = parse_a_srcset_attribute(&x.value()); + }, + _ => continue, + } + // Step 4.5 + if source_set.image_sources.is_empty() { + continue; + } + + // Step 4.6 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("media")) { + if !self.matches_environment(x.value().to_string()) { + continue; + } + } + + // Step 4.7 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("sizes")) { + source_set.source_size = + parse_a_sizes_attribute(DOMString::from_string(x.value().to_string())); + } + + // Step 4.8 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("type")) { + // TODO Handle unsupported mime type + let mime = x.value().parse::<Mime>(); + match mime { + Ok(m) => match m.type_() { + mime::IMAGE => (), + _ => continue, + }, + _ => continue, + } + } + + // Step 4.9 + self.normalise_source_densities(&mut source_set, width); + + // Step 4.10 + *self.source_set.borrow_mut() = source_set; + return; + } + } + + fn evaluate_source_size_list( + &self, + source_size_list: &mut SourceSizeList, + _width: Option<Length>, + ) -> Au { let document = document_from_node(self); - let window = document.window(); - match value { - None => { - self.current_request.borrow_mut().parsed_url = None; - self.current_request.borrow_mut().source_url = None; - LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker); - self.current_request.borrow_mut().image = None; + let device = document.device(); + let quirks_mode = document.quirks_mode(); + //FIXME https://github.com/whatwg/html/issues/3832 + source_size_list.evaluate(&device, quirks_mode) + } + + /// https://html.spec.whatwg.org/multipage/#matches-the-environment + fn matches_environment(&self, media_query: String) -> bool { + let document = document_from_node(self); + let quirks_mode = document.quirks_mode(); + let document_url = &document.url(); + // FIXME(emilio): This should do the same that we do for other media + // lists regarding the rule type and such, though it doesn't really + // matter right now... + // + // Also, ParsingMode::all() is wrong, and should be DEFAULT. + let context = ParserContext::new( + Origin::Author, + document_url, + Some(CssRuleType::Style), + ParsingMode::all(), + quirks_mode, + None, + None, + ); + let mut parserInput = ParserInput::new(&media_query); + let mut parser = Parser::new(&mut parserInput); + let media_list = MediaList::parse(&context, &mut parser); + media_list.evaluate(&document.device(), quirks_mode) + } + + /// <https://html.spec.whatwg.org/multipage/#normalise-the-source-densities> + fn normalise_source_densities(&self, source_set: &mut SourceSet, width: Option<Length>) { + // Step 1 + let mut source_size = &mut source_set.source_size; + + // Find source_size_length for Step 2.2 + let source_size_length = self.evaluate_source_size_list(&mut source_size, width); + + // Step 2 + for imgsource in &mut source_set.image_sources { + // Step 2.1 + if imgsource.descriptor.den.is_some() { + continue; } - Some((src, base_url)) => { - let img_url = base_url.join(&src); - if let Ok(img_url) = img_url { - self.update_image_with_url(img_url, src); - } else { - // https://html.spec.whatwg.org/multipage/#update-the-image-data - // Step 11 (error substeps) - debug!("Failed to parse URL {} with base {}", src, base_url); - let mut req = self.current_request.borrow_mut(); - - // Substeps 1,2 - req.image = None; - req.parsed_url = None; - req.state = State::Broken; - // todo: set pending request to null - // (pending requests aren't being used yet) - - - struct ImgParseErrorRunnable { - img: Trusted<HTMLImageElement>, - src: String, + // Step 2.2 + if imgsource.descriptor.wid.is_some() { + let wid = imgsource.descriptor.wid.unwrap(); + imgsource.descriptor.den = Some(wid as f64 / source_size_length.to_f64_px()); + } else { + //Step 2.3 + imgsource.descriptor.den = Some(1 as f64); + } + } + } + + /// <https://html.spec.whatwg.org/multipage/#select-an-image-source> + fn select_image_source(&self) -> Option<(USVString, f64)> { + // Step 1, 3 + self.update_source_set(); + let source_set = &*self.source_set.borrow_mut(); + let len = source_set.image_sources.len(); + + // Step 2 + if len == 0 { + return None; + } + + // Step 4 + let mut repeat_indices = HashSet::new(); + for outer_index in 0..len { + if repeat_indices.contains(&outer_index) { + continue; + } + let imgsource = &source_set.image_sources[outer_index]; + let pixel_density = imgsource.descriptor.den.unwrap(); + for inner_index in (outer_index + 1)..len { + let imgsource2 = &source_set.image_sources[inner_index]; + if pixel_density == imgsource2.descriptor.den.unwrap() { + repeat_indices.insert(inner_index); + } + } + } + + let mut max = (0f64, 0); + let img_sources = &mut vec![]; + for (index, image_source) in source_set.image_sources.iter().enumerate() { + if repeat_indices.contains(&index) { + continue; + } + let den = image_source.descriptor.den.unwrap(); + if max.0 < den { + max = (den, img_sources.len()); + } + img_sources.push(image_source); + } + + // Step 5 + let mut best_candidate = max; + let device = document_from_node(self).device(); + let device_den = device.device_pixel_ratio().get() as f64; + for (index, image_source) in img_sources.iter().enumerate() { + let current_den = image_source.descriptor.den.unwrap(); + if current_den < best_candidate.0 && current_den >= device_den { + best_candidate = (current_den, index); + } + } + let selected_source = img_sources.remove(best_candidate.1).clone(); + Some(( + USVString(selected_source.url), + selected_source.descriptor.den.unwrap() as f64, + )) + } + + fn init_image_request( + &self, + request: &mut RefMut<ImageRequest>, + url: &ServoUrl, + src: &USVString, + ) { + request.parsed_url = Some(url.clone()); + request.source_url = Some(src.clone()); + request.image = None; + request.metadata = None; + let document = document_from_node(self); + LoadBlocker::terminate(&mut request.blocker); + request.blocker = Some(LoadBlocker::new(&*document, LoadType::Image(url.clone()))); + } + + /// Step 13-17 of html.spec.whatwg.org/multipage/#update-the-image-data + fn prepare_image_request(&self, url: &ServoUrl, src: &USVString, selected_pixel_density: f64) { + match self.image_request.get() { + ImageRequestPhase::Pending => { + if let Some(pending_url) = self.pending_request.borrow().parsed_url.clone() { + // Step 13 + if pending_url == *url { + return; } - impl Runnable for ImgParseErrorRunnable { - fn handler(self: Box<Self>) { - // https://html.spec.whatwg.org/multipage/#update-the-image-data - // Step 11, substep 5 - let img = self.img.root(); - img.current_request.borrow_mut().source_url = Some(self.src.into()); - img.upcast::<EventTarget>().fire_event(atom!("error")); - img.upcast::<EventTarget>().fire_event(atom!("loadend")); + } + }, + ImageRequestPhase::Current => { + let mut current_request = self.current_request.borrow_mut(); + let mut pending_request = self.pending_request.borrow_mut(); + // step 16, create a new "image_request" + match (current_request.parsed_url.clone(), current_request.state) { + (Some(parsed_url), State::PartiallyAvailable) => { + // Step 14 + if parsed_url == *url { + // Step 15 abort pending request + pending_request.image = None; + pending_request.parsed_url = None; + LoadBlocker::terminate(&mut pending_request.blocker); + // TODO: queue a task to restart animation, if restart-animation is set + return; } - } + pending_request.current_pixel_density = Some(selected_pixel_density); + self.image_request.set(ImageRequestPhase::Pending); + self.init_image_request(&mut pending_request, &url, &src); + }, + (_, State::Broken) | (_, State::Unavailable) => { + // Step 17 + current_request.current_pixel_density = Some(selected_pixel_density); + self.init_image_request(&mut current_request, &url, &src); + }, + (_, _) => { + // step 17 + pending_request.current_pixel_density = Some(selected_pixel_density); + self.image_request.set(ImageRequestPhase::Pending); + self.init_image_request(&mut pending_request, &url, &src); + }, + } + }, + } + self.fetch_image(&url); + } + + /// Step 8-12 of html.spec.whatwg.org/multipage/#update-the-image-data + fn update_the_image_data_sync_steps(&self) { + let document = document_from_node(self); + let window = document.window(); + let task_source = window.task_manager().dom_manipulation_task_source(); + let this = Trusted::new(self); + let (src, pixel_density) = match self.select_image_source() { + // Step 8 + Some(data) => data, + None => { + self.abort_request(State::Broken, ImageRequestPhase::Current); + self.abort_request(State::Broken, ImageRequestPhase::Pending); + // Step 9. + // FIXME(nox): Why are errors silenced here? + let _ = task_source.queue( + task!(image_null_source_error: move || { + let this = this.root(); + { + let mut current_request = + this.current_request.borrow_mut(); + current_request.source_url = None; + current_request.parsed_url = None; + } + let elem = this.upcast::<Element>(); + let src_present = elem.has_attribute(&local_name!("src")); - let runnable = box ImgParseErrorRunnable { - img: Trusted::new(self), - src: src.into(), + if src_present || Self::uses_srcset_or_picture(elem) { + this.upcast::<EventTarget>().fire_event(atom!("error")); + } + }), + window.upcast(), + ); + return; + }, + }; + + // Step 11 + let base_url = document.base_url(); + let parsed_url = base_url.join(&src.0); + match parsed_url { + Ok(url) => { + // Step 13-17 + self.prepare_image_request(&url, &src, pixel_density); + }, + Err(_) => { + self.abort_request(State::Broken, ImageRequestPhase::Current); + self.abort_request(State::Broken, ImageRequestPhase::Pending); + // Step 12.1-12.5. + let src = src.0; + // FIXME(nox): Why are errors silenced here? + let _ = task_source.queue( + task!(image_selected_source_error: move || { + let this = this.root(); + { + let mut current_request = + this.current_request.borrow_mut(); + current_request.source_url = Some(USVString(src)) + } + this.upcast::<EventTarget>().fire_event(atom!("error")); + + }), + window.upcast(), + ); + }, + } + } + + /// <https://html.spec.whatwg.org/multipage/#update-the-image-data> + pub fn update_the_image_data(&self) { + let document = document_from_node(self); + let window = document.window(); + let elem = self.upcast::<Element>(); + let src = elem.get_url_attribute(&local_name!("src")); + let base_url = document.base_url(); + + // https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations + // Always first set the current request to unavailable, + // ensuring img.complete is false. + { + let mut current_request = self.current_request.borrow_mut(); + current_request.state = State::Unavailable; + } + + if !document.is_active() { + // Step 1 (if the document is inactive) + // TODO: use GlobalScope::enqueue_microtask, + // to queue micro task to come back to this algorithm + } + // Step 2 abort if user-agent does not supports images + // NOTE: Servo only supports images, skipping this step + + // Step 3, 4 + let mut selected_source = None; + let mut pixel_density = None; + let src_set = elem.get_url_attribute(&local_name!("srcset")); + let is_parent_picture = elem + .upcast::<Node>() + .GetParentElement() + .map_or(false, |p| p.is::<HTMLPictureElement>()); + if src_set.is_empty() && !is_parent_picture && !src.is_empty() { + selected_source = Some(src.clone()); + pixel_density = Some(1 as f64); + }; + + // Step 5 + *self.last_selected_source.borrow_mut() = selected_source.clone(); + + // Step 6, check the list of available images + if let Some(src) = selected_source { + if let Ok(img_url) = base_url.join(&src) { + let image_cache = window.image_cache(); + let response = image_cache.get_image( + img_url.clone(), + window.origin().immutable().clone(), + cors_setting_for_element(self.upcast()), + ); + + if let Some(image) = response { + // Cancel any outstanding tasks that were queued before the src was + // set on this element. + self.generation.set(self.generation.get() + 1); + // Step 6.3 + let metadata = ImageMetadata { + height: image.height, + width: image.width, }; - let task = window.dom_manipulation_task_source(); - let _ = task.queue(runnable, window.upcast()); + // Step 6.3.2 abort requests + self.abort_request(State::CompletelyAvailable, ImageRequestPhase::Current); + self.abort_request(State::Unavailable, ImageRequestPhase::Pending); + 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.metadata = Some(metadata); + // Step 6.3.6 + current_request.current_pixel_density = pixel_density; + let this = Trusted::new(self); + let src = src.0; + let _ = window.task_manager().dom_manipulation_task_source().queue( + task!(image_load_event: move || { + let this = this.root(); + { + let mut current_request = + this.current_request.borrow_mut(); + current_request.parsed_url = Some(img_url); + current_request.source_url = Some(USVString(src)); + } + // TODO: restart animation, if set. + this.upcast::<EventTarget>().fire_event(atom!("load")); + }), + window.upcast(), + ); + return; } } } + // step 7, await a stable state. + self.generation.set(self.generation.get() + 1); + let task = ImageElementMicrotask::StableStateUpdateImageDataTask { + elem: DomRoot::from_ref(self), + generation: self.generation.get(), + }; + ScriptThread::await_stable_state(Microtask::ImageElement(task)); + } + + /// <https://html.spec.whatwg.org/multipage/#img-environment-changes> + pub fn react_to_environment_changes(&self) { + // Step 1 + let task = ImageElementMicrotask::EnvironmentChangesTask { + elem: DomRoot::from_ref(self), + generation: self.generation.get(), + }; + ScriptThread::await_stable_state(Microtask::ImageElement(task)); + } + + /// Step 2-12 of https://html.spec.whatwg.org/multipage/#img-environment-changes + fn react_to_environment_changes_sync_steps(&self, generation: u32) { + // TODO reduce duplicacy of this code + + fn generate_cache_listener_for_element( + elem: &HTMLImageElement, + selected_source: String, + selected_pixel_density: f64, + ) -> IpcSender<PendingImageResponse> { + let trusted_node = Trusted::new(elem); + let (responder_sender, responder_receiver) = ipc::channel().unwrap(); + + let window = window_from_node(elem); + let (task_source, canceller) = window + .task_manager() + .networking_task_source_with_canceller(); + let generation = elem.generation.get(); + ROUTER.add_route( + responder_receiver.to_opaque(), + Box::new(move |message| { + debug!("Got image {:?}", message); + // Return the image via a message to the script thread, which marks + // the element as dirty and triggers a reflow. + let element = trusted_node.clone(); + let image = message.to().unwrap(); + let selected_source_clone = selected_source.clone(); + let _ = task_source.queue_with_canceller( + task!(process_image_response_for_environment_change: move || { + let element = element.root(); + // Ignore any image response for a previous request that has been discarded. + if generation == element.generation.get() { + element.process_image_response_for_environment_change(image, + USVString::from(selected_source_clone), generation, selected_pixel_density); + } + }), + &canceller, + ); + }), + ); + + responder_sender + } + + let elem = self.upcast::<Element>(); + let document = document_from_node(elem); + let has_pending_request = match self.image_request.get() { + ImageRequestPhase::Pending => true, + _ => false, + }; + + // Step 2 + if !document.is_active() || !Self::uses_srcset_or_picture(elem) || has_pending_request { + return; + } + + // Steps 3-4 + let (selected_source, selected_pixel_density) = match self.select_image_source() { + Some(selected) => selected, + None => return, + }; + + // Step 5 + let same_source = match *self.last_selected_source.borrow() { + Some(ref last_src) => *last_src == selected_source, + _ => false, + }; + + let same_selected_pixel_density = match self.current_request.borrow().current_pixel_density + { + Some(den) => selected_pixel_density == den, + _ => false, + }; + + if same_source && same_selected_pixel_density { + return; + } + + let base_url = document.base_url(); + // Step 6 + let img_url = match base_url.join(&selected_source.0) { + Ok(url) => url, + Err(_) => return, + }; + + // Step 12 + self.image_request.set(ImageRequestPhase::Pending); + self.init_image_request( + &mut self.pending_request.borrow_mut(), + &img_url, + &selected_source, + ); + + let window = window_from_node(self); + let image_cache = window.image_cache(); + + // Step 14 + let sender = generate_cache_listener_for_element( + self, + selected_source.0.clone(), + selected_pixel_density, + ); + let cache_result = image_cache.track_image( + img_url.clone(), + window.origin().immutable().clone(), + cors_setting_for_element(self.upcast()), + sender, + UsePlaceholder::No, + ); + + match cache_result { + ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { .. }) => { + // Step 15 + self.finish_reacting_to_environment_change( + selected_source, + generation, + selected_pixel_density, + ) + }, + ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m)) => { + self.process_image_response_for_environment_change( + ImageResponse::MetadataLoaded(m), + selected_source, + generation, + selected_pixel_density, + ); + }, + ImageCacheResult::LoadError => { + self.process_image_response_for_environment_change( + ImageResponse::None, + selected_source, + generation, + selected_pixel_density, + ); + }, + ImageCacheResult::ReadyForRequest(id) => self.fetch_request(&img_url, id), + ImageCacheResult::Pending(_) => (), + } + } + + /// Step 15 for <https://html.spec.whatwg.org/multipage/#img-environment-changes> + fn finish_reacting_to_environment_change( + &self, + src: USVString, + generation: u32, + selected_pixel_density: f64, + ) { + let this = Trusted::new(self); + let window = window_from_node(self); + let src = src.0; + let _ = window.task_manager().dom_manipulation_task_source().queue( + task!(image_load_event: move || { + let this = this.root(); + let relevant_mutation = this.generation.get() != generation; + // Step 15.1 + if relevant_mutation { + this.abort_request(State::Unavailable, ImageRequestPhase::Pending); + return; + } + // Step 15.2 + *this.last_selected_source.borrow_mut() = Some(USVString(src)); + + { + let mut pending_request = this.pending_request.borrow_mut(); + pending_request.current_pixel_density = Some(selected_pixel_density); + + // Step 15.3 + pending_request.state = State::CompletelyAvailable; + + // Step 15.4 + // Already a part of the list of available images due to Step 14 + + // Step 15.5 + mem::swap(&mut this.current_request.borrow_mut(), &mut pending_request); + this.abort_request(State::Unavailable, ImageRequestPhase::Pending); + } + + // Step 15.6 + this.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + + // Step 15.7 + this.upcast::<EventTarget>().fire_event(atom!("load")); + }), + window.upcast(), + ); + } + + fn uses_srcset_or_picture(elem: &Element) -> bool { + let has_src = elem.has_attribute(&local_name!("srcset")); + let is_parent_picture = elem + .upcast::<Node>() + .GetParentElement() + .map_or(false, |p| p.is::<HTMLPictureElement>()); + has_src || is_parent_picture } - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLImageElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLImageElement { HTMLImageElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), - current_request: DOMRefCell::new(ImageRequest { + image_request: Cell::new(ImageRequestPhase::Current), + current_request: DomRefCell::new(ImageRequest { state: State::Unavailable, parsed_url: None, source_url: None, image: None, metadata: None, blocker: None, + final_url: None, + current_pixel_density: None, }), - pending_request: DOMRefCell::new(ImageRequest { + pending_request: DomRefCell::new(ImageRequest { state: State::Unavailable, parsed_url: None, source_url: None, image: None, metadata: None, blocker: None, + final_url: None, + current_pixel_density: None, }), form_owner: Default::default(), generation: Default::default(), + source_set: DomRefCell::new(SourceSet::new()), + last_selected_source: DomRefCell::new(None), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLImageElement> { - Node::reflect_node(box HTMLImageElement::new_inherited(local_name, prefix, document), - document, - HTMLImageElementBinding::Wrap) - } - - pub fn Image(window: &Window, - width: Option<u32>, - height: Option<u32>) -> Fallible<Root<HTMLImageElement>> { - let document = window.Document(); - let image = HTMLImageElement::new(local_name!("img"), None, &document); + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLImageElement> { + Node::reflect_node( + Box::new(HTMLImageElement::new_inherited( + local_name, prefix, document, + )), + document, + ) + } + + pub fn Image( + window: &Window, + width: Option<u32>, + height: Option<u32>, + ) -> Fallible<DomRoot<HTMLImageElement>> { + let element = Element::create( + QualName::new(None, ns!(html), local_name!("img")), + None, + &window.Document(), + ElementCreator::ScriptCreated, + CustomElementCreationMode::Synchronous, + ); + + let image = DomRoot::downcast::<HTMLImageElement>(element).unwrap(); if let Some(w) = width { image.SetWidth(w); } @@ -415,75 +1276,169 @@ impl HTMLImageElement { Ok(image) } - pub fn areas(&self) -> Option<Vec<Root<HTMLAreaElement>>> { + pub fn areas(&self) -> Option<Vec<DomRoot<HTMLAreaElement>>> { let elem = self.upcast::<Element>(); - let usemap_attr = match elem.get_attribute(&ns!(), &local_name!("usemap")) { - Some(attr) => attr, - None => return None, - }; + let usemap_attr = elem.get_attribute(&ns!(), &local_name!("usemap"))?; let value = usemap_attr.value(); if value.len() == 0 || !value.is_char_boundary(1) { - return None + return None; } let (first, last) = value.split_at(1); if first != "#" || last.len() == 0 { - return None + return None; } - let useMapElements = document_from_node(self).upcast::<Node>() - .traverse_preorder() - .filter_map(Root::downcast::<HTMLMapElement>) - .find(|n| n.upcast::<Element>().get_string_attribute(&LocalName::from("name")) == last); + let useMapElements = document_from_node(self) + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<HTMLMapElement>) + .find(|n| { + n.upcast::<Element>() + .get_name() + .map_or(false, |n| *n == *last) + }); useMapElements.map(|mapElem| mapElem.get_area_elements()) } + + pub fn same_origin(&self, origin: &MutableOrigin) -> bool { + if let Some(ref image) = self.current_request.borrow().image { + return image.cors_status == CorsStatus::Safe; + } + + self.current_request + .borrow() + .final_url + .as_ref() + .map_or(false, |url| { + url.scheme() == "data" || url.origin().same_origin(origin) + }) + } } -pub trait LayoutHTMLImageElementHelpers { - #[allow(unsafe_code)] - unsafe fn image(&self) -> Option<Arc<Image>>; +#[derive(JSTraceable, MallocSizeOf)] +pub enum ImageElementMicrotask { + StableStateUpdateImageDataTask { + elem: DomRoot<HTMLImageElement>, + generation: u32, + }, + EnvironmentChangesTask { + elem: DomRoot<HTMLImageElement>, + generation: u32, + }, +} - #[allow(unsafe_code)] - unsafe fn image_url(&self) -> Option<ServoUrl>; +impl MicrotaskRunnable for ImageElementMicrotask { + fn handler(&self) { + match self { + &ImageElementMicrotask::StableStateUpdateImageDataTask { + ref elem, + ref generation, + } => { + // Step 7 of https://html.spec.whatwg.org/multipage/#update-the-image-data, + // stop here if other instances of this algorithm have been scheduled + if elem.generation.get() == *generation { + elem.update_the_image_data_sync_steps(); + } + }, + &ImageElementMicrotask::EnvironmentChangesTask { + ref elem, + ref generation, + } => { + elem.react_to_environment_changes_sync_steps(*generation); + }, + } + } +} - fn get_width(&self) -> LengthOrPercentageOrAuto; - fn get_height(&self) -> LengthOrPercentageOrAuto; +pub trait LayoutHTMLImageElementHelpers { + fn image(self) -> Option<Arc<Image>>; + fn image_url(self) -> Option<ServoUrl>; + fn image_density(self) -> Option<f64>; + fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>); + fn get_width(self) -> LengthOrPercentageOrAuto; + fn get_height(self) -> LengthOrPercentageOrAuto; } -impl LayoutHTMLImageElementHelpers for LayoutJS<HTMLImageElement> { +impl<'dom> LayoutDom<'dom, HTMLImageElement> { #[allow(unsafe_code)] - unsafe fn image(&self) -> Option<Arc<Image>> { - (*self.unsafe_get()).current_request.borrow_for_layout().image.clone() + fn current_request(self) -> &'dom ImageRequest { + unsafe { self.unsafe_get().current_request.borrow_for_layout() } } +} - #[allow(unsafe_code)] - unsafe fn image_url(&self) -> Option<ServoUrl> { - (*self.unsafe_get()).current_request.borrow_for_layout().parsed_url.clone() +impl LayoutHTMLImageElementHelpers for LayoutDom<'_, HTMLImageElement> { + fn image(self) -> Option<Arc<Image>> { + self.current_request().image.clone() } - #[allow(unsafe_code)] - fn get_width(&self) -> LengthOrPercentageOrAuto { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("width")) - .map(AttrValue::as_dimension) - .cloned() - .unwrap_or(LengthOrPercentageOrAuto::Auto) - } + fn image_url(self) -> Option<ServoUrl> { + self.current_request().parsed_url.clone() } - #[allow(unsafe_code)] - fn get_height(&self) -> LengthOrPercentageOrAuto { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("height")) - .map(AttrValue::as_dimension) - .cloned() - .unwrap_or(LengthOrPercentageOrAuto::Auto) + fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>) { + let current_request = self.current_request(); + ( + current_request.image.clone(), + current_request.metadata.clone(), + ) + } + + fn image_density(self) -> Option<f64> { + self.current_request().current_pixel_density.clone() + } + + fn get_width(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("width")) + .map(AttrValue::as_dimension) + .cloned() + .unwrap_or(LengthOrPercentageOrAuto::Auto) + } + + fn get_height(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("height")) + .map(AttrValue::as_dimension) + .cloned() + .unwrap_or(LengthOrPercentageOrAuto::Auto) + } +} + +//https://html.spec.whatwg.org/multipage/#parse-a-sizes-attribute +pub fn parse_a_sizes_attribute(value: DOMString) -> SourceSizeList { + let mut input = ParserInput::new(&value); + let mut parser = Parser::new(&mut input); + let url = ServoUrl::parse("about:blank").unwrap(); + let context = ParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Style), + // FIXME(emilio): why ::empty() instead of ::DEFAULT? Also, what do + // browsers do regarding quirks-mode in a media list? + ParsingMode::empty(), + QuirksMode::NoQuirks, + None, + None, + ); + SourceSizeList::parse(&context, &mut parser) +} + +fn get_correct_referrerpolicy_from_raw_token(token: &DOMString) -> DOMString { + if token == "" { + // Empty token is treated as no-referrer inside determine_policy_for_token, + // while here it should be treated as the default value, so it should remain unchanged. + DOMString::new() + } else { + match determine_policy_for_token(token) { + Some(policy) => DOMString::from_string(policy.to_string()), + // If the policy is set to an incorrect value, then it should be + // treated as an invalid value default (empty string). + None => DOMString::new(), } } } @@ -496,8 +1451,14 @@ impl HTMLImageElementMethods for HTMLImageElement { // https://html.spec.whatwg.org/multipage/#dom-img-src make_url_getter!(Src, "src"); + // https://html.spec.whatwg.org/multipage/#dom-img-src - make_setter!(SetSrc, "src"); + make_url_setter!(SetSrc, "src"); + + // https://html.spec.whatwg.org/multipage/#dom-img-srcset + make_url_getter!(Srcset, "srcset"); + // https://html.spec.whatwg.org/multipage/#dom-img-src + make_url_setter!(SetSrcset, "srcset"); // https://html.spec.whatwg.org/multipage/#dom-img-crossOrigin fn GetCrossOrigin(&self) -> Option<DOMString> { @@ -549,36 +1510,80 @@ impl HTMLImageElementMethods for HTMLImageElement { // https://html.spec.whatwg.org/multipage/#dom-img-naturalwidth fn NaturalWidth(&self) -> u32 { - let ref metadata = self.current_request.borrow().metadata; + let request = self.current_request.borrow(); + let pixel_density = request.current_pixel_density.unwrap_or(1f64); - match *metadata { - Some(ref metadata) => metadata.width, + match request.metadata { + Some(ref metadata) => (metadata.width as f64 / pixel_density) as u32, None => 0, } } // https://html.spec.whatwg.org/multipage/#dom-img-naturalheight fn NaturalHeight(&self) -> u32 { - let ref metadata = self.current_request.borrow().metadata; + let request = self.current_request.borrow(); + let pixel_density = request.current_pixel_density.unwrap_or(1f64); - match *metadata { - Some(ref metadata) => metadata.height, + match request.metadata { + Some(ref metadata) => (metadata.height as f64 / pixel_density) as u32, None => 0, } } // https://html.spec.whatwg.org/multipage/#dom-img-complete fn Complete(&self) -> bool { - let ref image = self.current_request.borrow().image; - image.is_some() + let elem = self.upcast::<Element>(); + let srcset_absent = !elem.has_attribute(&local_name!("srcset")); + if !elem.has_attribute(&local_name!("src")) && srcset_absent { + return true; + } + let src = elem.get_string_attribute(&local_name!("src")); + if srcset_absent && src.is_empty() { + return true; + } + let request = self.current_request.borrow(); + let request_state = request.state; + match request_state { + State::CompletelyAvailable | State::Broken => return true, + State::PartiallyAvailable | State::Unavailable => return false, + } } // https://html.spec.whatwg.org/multipage/#dom-img-currentsrc - fn CurrentSrc(&self) -> DOMString { - let ref url = self.current_request.borrow().source_url; + fn CurrentSrc(&self) -> USVString { + let current_request = self.current_request.borrow(); + let ref url = current_request.parsed_url; match *url { - Some(ref url) => url.clone(), - None => DOMString::from(""), + Some(ref url) => USVString(url.clone().into_string()), + None => { + let ref unparsed_url = current_request.source_url; + match *unparsed_url { + Some(ref url) => url.clone(), + None => USVString("".to_owned()), + } + }, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy + fn ReferrerPolicy(&self) -> DOMString { + let element = self.upcast::<Element>(); + let current_policy_value = element.get_string_attribute(&local_name!("referrerpolicy")); + get_correct_referrerpolicy_from_raw_token(¤t_policy_value) + } + + // https://html.spec.whatwg.org/multipage/#dom-img-referrerpolicy + fn SetReferrerPolicy(&self, value: DOMString) { + let referrerpolicy_attr_name = local_name!("referrerpolicy"); + let element = self.upcast::<Element>(); + let previous_correct_attribute_value = get_correct_referrerpolicy_from_raw_token( + &element.get_string_attribute(&referrerpolicy_attr_name), + ); + let correct_value_or_empty_string = get_correct_referrerpolicy_from_raw_token(&value); + if previous_correct_attribute_value != correct_value_or_empty_string { + // Setting the attribute to the same value will update the image. + // We don't want to start an update if referrerpolicy is set to the same value. + element.set_string_attribute(&referrerpolicy_attr_name, correct_value_or_empty_string); } } @@ -620,82 +1625,112 @@ impl HTMLImageElementMethods for HTMLImageElement { } impl VirtualMethods for HTMLImageElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn adopting_steps(&self, old_doc: &Document) { self.super_type().unwrap().adopting_steps(old_doc); - - let elem = self.upcast::<Element>(); - let document = document_from_node(self); - self.update_image(Some((elem.get_string_attribute(&local_name!("src")), - document.base_url()))); + self.update_the_image_data(); } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); match attr.local_name() { - &local_name!("src") => { - self.update_image(mutation.new_value(attr).map(|value| { - // FIXME(ajeffrey): convert directly from AttrValue to DOMString - (DOMString::from(&**value), document_from_node(self).base_url()) - })); - }, + &local_name!("src") | + &local_name!("srcset") | + &local_name!("width") | + &local_name!("crossorigin") | + &local_name!("sizes") | + &local_name!("referrerpolicy") => self.update_the_image_data(), _ => {}, } } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match name { - &local_name!("name") => AttrValue::from_atomic(value.into()), - &local_name!("width") | &local_name!("height") => AttrValue::from_dimension(value.into()), + &local_name!("width") | &local_name!("height") => { + AttrValue::from_dimension(value.into()) + }, &local_name!("hspace") | &local_name!("vspace") => AttrValue::from_u32(value.into(), 0), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } fn handle_event(&self, event: &Event) { - if event.type_() == atom!("click") { - let area_elements = self.areas(); - let elements = if let Some(x) = area_elements { - x - } else { - return - }; - - // Fetch click coordinates - let mouse_event = if let Some(x) = event.downcast::<MouseEvent>() { - x - } else { - return; - }; - - let point = Point2D::new(mouse_event.ClientX().to_f32().unwrap(), - mouse_event.ClientY().to_f32().unwrap()); - - // Walk HTMLAreaElements - for element in elements { - let shape = element.get_shape_from_coords(); - let p = Point2D::new(self.upcast::<Element>().GetBoundingClientRect().X() as f32, - self.upcast::<Element>().GetBoundingClientRect().Y() as f32); - - let shp = if let Some(x) = shape { - x.absolute_coords(p) - } else { - return - }; - if shp.hit_test(point) { - element.activation_behavior(event, self.upcast()); - return - } - } - } + if event.type_() != atom!("click") { + return; + } + + let area_elements = self.areas(); + let elements = match area_elements { + Some(x) => x, + None => return, + }; + + // Fetch click coordinates + let mouse_event = match event.downcast::<MouseEvent>() { + Some(x) => x, + None => return, + }; + + let point = Point2D::new( + mouse_event.ClientX().to_f32().unwrap(), + mouse_event.ClientY().to_f32().unwrap(), + ); + let bcr = self.upcast::<Element>().GetBoundingClientRect(); + let bcr_p = Point2D::new(bcr.X() as f32, bcr.Y() as f32); + + // Walk HTMLAreaElements + for element in elements { + let shape = element.get_shape_from_coords(); + let shp = match shape { + Some(x) => x.absolute_coords(bcr_p), + None => return, + }; + if shp.hit_test(&point) { + element.activation_behavior(event, self.upcast()); + return; + } + } + } + + fn bind_to_tree(&self, context: &BindContext) { + if let Some(ref s) = self.super_type() { + s.bind_to_tree(context); + } + let document = document_from_node(self); + if context.tree_connected { + document.register_responsive_image(self); + } + + // The element is inserted into a picture parent element + // https://html.spec.whatwg.org/multipage/#relevant-mutations + if let Some(parent) = self.upcast::<Node>().GetParentElement() { + if parent.is::<HTMLPictureElement>() { + self.update_the_image_data(); + } + } + } + + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); + let document = document_from_node(self); + document.unregister_responsive_image(self); + + // The element is removed from a picture parent element + // https://html.spec.whatwg.org/multipage/#relevant-mutations + if context.parent.is::<HTMLPictureElement>() { + self.update_the_image_data(); + } } } impl FormControl for HTMLImageElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } @@ -712,14 +1747,20 @@ impl FormControl for HTMLImageElement { } } +impl ImageCacheListener for HTMLImageElement { + fn generation_id(&self) -> u32 { + self.generation.get() + } + + fn process_image_response(&self, response: ImageResponse) { + self.process_image_response(response); + } +} + fn image_dimension_setter(element: &Element, attr: LocalName, value: u32) { // This setter is a bit weird: the IDL type is unsigned long, but it's parsed as // a dimension for rendering. - let value = if value > UNSIGNED_LONG_MAX { - 0 - } else { - value - }; + let value = if value > UNSIGNED_LONG_MAX { 0 } else { value }; // FIXME: There are probably quite a few more cases of this. This is the // only overflow that was hitting on automation, but we should consider what @@ -736,3 +1777,183 @@ fn image_dimension_setter(element: &Element, attr: LocalName, value: u32) { let value = AttrValue::Dimension(value.to_string(), dim); element.set_attribute(&attr, value); } + +/// Collect sequence of code points +pub fn collect_sequence_characters<F>(s: &str, predicate: F) -> (&str, &str) +where + F: Fn(&char) -> bool, +{ + for (i, ch) in s.chars().enumerate() { + if !predicate(&ch) { + return (&s[0..i], &s[i..]); + } + } + + return (s, ""); +} + +/// Parse an `srcset` attribute - https://html.spec.whatwg.org/multipage/#parsing-a-srcset-attribute. +pub fn parse_a_srcset_attribute(input: &str) -> Vec<ImageSource> { + let mut url_len = 0; + let mut candidates: Vec<ImageSource> = vec![]; + while url_len < input.len() { + let position = &input[url_len..]; + let (spaces, position) = + collect_sequence_characters(position, |c| *c == ',' || char::is_whitespace(*c)); + // add the length of the url that we parse to advance the start index + let space_len = spaces.char_indices().count(); + url_len += space_len; + if position.is_empty() { + return candidates; + } + let (url, spaces) = collect_sequence_characters(position, |c| !char::is_whitespace(*c)); + // add the counts of urls that we parse to advance the start index + url_len += url.chars().count(); + let comma_count = url.chars().rev().take_while(|c| *c == ',').count(); + let url: String = url + .chars() + .take(url.chars().count() - comma_count) + .collect(); + // add 1 to start index, for the comma + url_len += comma_count + 1; + let (space, position) = collect_sequence_characters(spaces, |c| char::is_whitespace(*c)); + let space_len = space.len(); + url_len += space_len; + let mut descriptors = Vec::new(); + let mut current_descriptor = String::new(); + let mut state = ParseState::InDescriptor; + let mut char_stream = position.chars().enumerate(); + let mut buffered: Option<(usize, char)> = None; + loop { + let next_char = buffered.take().or_else(|| char_stream.next()); + if next_char.is_some() { + url_len += 1; + } + match state { + ParseState::InDescriptor => match next_char { + Some((_, ' ')) => { + if !current_descriptor.is_empty() { + descriptors.push(current_descriptor.clone()); + current_descriptor = String::new(); + state = ParseState::AfterDescriptor; + } + continue; + }, + Some((_, ',')) => { + if !current_descriptor.is_empty() { + descriptors.push(current_descriptor.clone()); + } + break; + }, + Some((_, c @ '(')) => { + current_descriptor.push(c); + state = ParseState::InParens; + continue; + }, + Some((_, c)) => { + current_descriptor.push(c); + }, + None => { + if !current_descriptor.is_empty() { + descriptors.push(current_descriptor.clone()); + } + break; + }, + }, + ParseState::InParens => match next_char { + Some((_, c @ ')')) => { + current_descriptor.push(c); + state = ParseState::InDescriptor; + continue; + }, + Some((_, c)) => { + current_descriptor.push(c); + continue; + }, + None => { + if !current_descriptor.is_empty() { + descriptors.push(current_descriptor.clone()); + } + break; + }, + }, + ParseState::AfterDescriptor => match next_char { + Some((_, ' ')) => { + state = ParseState::AfterDescriptor; + continue; + }, + Some((idx, c)) => { + state = ParseState::InDescriptor; + buffered = Some((idx, c)); + continue; + }, + None => { + if !current_descriptor.is_empty() { + descriptors.push(current_descriptor.clone()); + } + break; + }, + }, + } + } + + let mut error = false; + let mut width: Option<u32> = None; + let mut density: Option<f64> = None; + let mut future_compat_h: Option<u32> = None; + for descriptor in descriptors { + let (digits, remaining) = + collect_sequence_characters(&descriptor, |c| is_ascii_digit(c) || *c == '.'); + let valid_non_negative_integer = parse_unsigned_integer(digits.chars()); + let has_w = remaining == "w"; + let valid_floating_point = parse_double(digits); + let has_x = remaining == "x"; + let has_h = remaining == "h"; + if valid_non_negative_integer.is_ok() && has_w { + let result = valid_non_negative_integer; + error = result.is_err(); + if width.is_some() || density.is_some() { + error = true; + } + if let Ok(w) = result { + width = Some(w); + } + } else if valid_floating_point.is_ok() && has_x { + let result = valid_floating_point; + error = result.is_err(); + if width.is_some() || density.is_some() || future_compat_h.is_some() { + error = true; + } + if let Ok(x) = result { + density = Some(x); + } + } else if valid_non_negative_integer.is_ok() && has_h { + let result = valid_non_negative_integer; + error = result.is_err(); + if density.is_some() || future_compat_h.is_some() { + error = true; + } + if let Ok(h) = result { + future_compat_h = Some(h); + } + } else { + error = true; + } + } + if future_compat_h.is_some() && width.is_none() { + error = true; + } + if !error { + let descriptor = Descriptor { + wid: width, + den: density, + }; + let image_source = ImageSource { + url: url, + descriptor: descriptor, + }; + candidates.push(image_source); + } + } + candidates +} diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index baff4983504..aefbea6f30e 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -1,78 +1,234 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use caseless::compatibility_caseless_match_str; -use dom::activation::{Activatable, ActivationSource, synthetic_click_activation}; -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::FileListBinding::FileListMethods; -use dom::bindings::codegen::Bindings::HTMLInputElementBinding; -use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; -use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods; -use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::{Error, ErrorResult}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, LayoutJS, MutNullableJS, Root, RootedReference}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element, LayoutElementHelpers, RawLayoutElementHelpers}; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::eventtarget::EventTarget; -use dom::file::File; -use dom::filelist::FileList; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlformelement::{FormControl, FormDatum, FormDatumValue, FormSubmitter, HTMLFormElement}; -use dom::htmlformelement::{ResetFrom, SubmittedFrom}; -use dom::keyboardevent::KeyboardEvent; -use dom::mouseevent::MouseEvent; -use dom::node::{Node, NodeDamage, UnbindContext}; -use dom::node::{document_from_node, window_from_node}; -use dom::nodelist::NodeList; -use dom::validation::Validatable; -use dom::validitystate::ValidationFlags; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::activation::Activatable; +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods; +use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode; +use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::compositionevent::CompositionEvent; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::file::File; +use crate::dom::filelist::FileList; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmldatalistelement::HTMLDataListElement; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlformelement::{ + FormControl, FormDatum, FormDatumValue, FormSubmitter, HTMLFormElement, +}; +use crate::dom::htmlformelement::{ResetFrom, SubmittedFrom}; +use crate::dom::keyboardevent::KeyboardEvent; +use crate::dom::mouseevent::MouseEvent; +use crate::dom::node::{document_from_node, window_from_node}; +use crate::dom::node::{ + BindContext, CloneChildrenFlag, Node, NodeDamage, ShadowIncluding, UnbindContext, +}; +use crate::dom::nodelist::NodeList; +use crate::dom::textcontrol::{TextControlElement, TextControlSelection}; +use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable}; +use crate::dom::validitystate::{ValidationFlags, ValidityState}; +use crate::dom::virtualmethods::VirtualMethods; +use crate::realms::enter_realm; +use crate::script_runtime::JSContext as SafeJSContext; +use crate::textinput::KeyReaction::{ + DispatchInput, Nothing, RedrawSelection, TriggerDefaultAction, +}; +use crate::textinput::Lines::Single; +use crate::textinput::{Direction, SelectionDirection, TextInput, UTF16CodeUnits, UTF8Bytes}; +use chrono::naive::{NaiveDate, NaiveDateTime}; +use chrono::{Datelike, Weekday}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use ipc_channel::ipc::{self, IpcSender}; -use mime_guess; -use net_traits::{CoreResourceMsg, IpcSend}; +use embedder_traits::FilterPattern; +use encoding_rs::Encoding; +use html5ever::{LocalName, Prefix}; +use js::jsapi::{ + ClippedTime, DateGetMsecSinceEpoch, Handle, JSObject, JS_ClearPendingException, NewDateObject, + NewUCRegExpObject, ObjectIsDate, RegExpFlag_Unicode, RegExpFlags, +}; +use js::jsval::UndefinedValue; +use js::rust::jsapi_wrapped::{ExecuteRegExpNoStatics, ObjectIsRegExp}; +use js::rust::{HandleObject, MutableHandleObject}; +use msg::constellation_msg::InputMethodType; use net_traits::blob_url_store::get_blob_origin; -use net_traits::filemanager_thread::{FileManagerThreadMsg, FilterPattern}; +use net_traits::filemanager_thread::FileManagerThreadMsg; +use net_traits::{CoreResourceMsg, IpcSend}; +use profile_traits::ipc; use script_layout_interface::rpc::TextIndexResponse; -use script_traits::ScriptMsg as ConstellationMsg; +use script_traits::ScriptToConstellationChan; use servo_atoms::Atom; -use std::borrow::ToOwned; +use std::borrow::Cow; use std::cell::Cell; +use std::f64; use std::ops::Range; +use std::ptr; +use std::ptr::NonNull; use style::attr::AttrValue; -use style::element_state::*; -use style::str::split_commas; -use textinput::{SelectionDirection, TextInput}; -use textinput::KeyReaction::{DispatchInput, Nothing, RedrawSelection, TriggerDefaultAction}; -use textinput::Lines::Single; +use style::element_state::ElementState; +use style::str::{split_commas, str_join}; +use unicode_bidi::{bidi_class, BidiClass}; +use url::Url; const DEFAULT_SUBMIT_VALUE: &'static str = "Submit"; const DEFAULT_RESET_VALUE: &'static str = "Reset"; const PASSWORD_REPLACEMENT_CHAR: char = '●'; -#[derive(JSTraceable, PartialEq, Copy, Clone)] +#[derive(Clone, Copy, JSTraceable, PartialEq)] #[allow(dead_code)] -#[derive(HeapSizeOf)] -enum InputType { - InputSubmit, - InputReset, - InputButton, - InputText, - InputFile, - InputImage, - InputCheckbox, - InputRadio, - InputPassword +#[derive(MallocSizeOf)] +pub enum InputType { + Button, + Checkbox, + Color, + Date, + DatetimeLocal, + Email, + File, + Hidden, + Image, + Month, + Number, + Password, + Radio, + Range, + Reset, + Search, + Submit, + Tel, + Text, + Time, + Url, + Week, +} + +impl InputType { + // Note that Password is not included here since it is handled + // slightly differently, with placeholder characters shown rather + // than the underlying value. + fn is_textual(&self) -> bool { + match *self { + InputType::Color | + InputType::Date | + InputType::DatetimeLocal | + InputType::Email | + InputType::Hidden | + InputType::Month | + InputType::Number | + InputType::Range | + InputType::Search | + InputType::Tel | + InputType::Text | + InputType::Time | + InputType::Url | + InputType::Week => true, + + _ => false, + } + } + + fn is_textual_or_password(&self) -> bool { + self.is_textual() || *self == InputType::Password + } + + // https://html.spec.whatwg.org/multipage/#has-a-periodic-domain + fn has_periodic_domain(&self) -> bool { + *self == InputType::Time + } + + fn to_str(&self) -> &str { + match *self { + InputType::Button => "button", + InputType::Checkbox => "checkbox", + InputType::Color => "color", + InputType::Date => "date", + InputType::DatetimeLocal => "datetime-local", + InputType::Email => "email", + InputType::File => "file", + InputType::Hidden => "hidden", + InputType::Image => "image", + InputType::Month => "month", + InputType::Number => "number", + InputType::Password => "password", + InputType::Radio => "radio", + InputType::Range => "range", + InputType::Reset => "reset", + InputType::Search => "search", + InputType::Submit => "submit", + InputType::Tel => "tel", + InputType::Text => "text", + InputType::Time => "time", + InputType::Url => "url", + InputType::Week => "week", + } + } + + pub fn as_ime_type(&self) -> Option<InputMethodType> { + match *self { + InputType::Color => Some(InputMethodType::Color), + InputType::Date => Some(InputMethodType::Date), + InputType::DatetimeLocal => Some(InputMethodType::DatetimeLocal), + InputType::Email => Some(InputMethodType::Email), + InputType::Month => Some(InputMethodType::Month), + InputType::Number => Some(InputMethodType::Number), + InputType::Password => Some(InputMethodType::Password), + InputType::Search => Some(InputMethodType::Search), + InputType::Tel => Some(InputMethodType::Tel), + InputType::Text => Some(InputMethodType::Text), + InputType::Time => Some(InputMethodType::Time), + InputType::Url => Some(InputMethodType::Url), + InputType::Week => Some(InputMethodType::Week), + _ => None, + } + } +} + +impl<'a> From<&'a Atom> for InputType { + fn from(value: &Atom) -> InputType { + match value.to_ascii_lowercase() { + atom!("button") => InputType::Button, + atom!("checkbox") => InputType::Checkbox, + atom!("color") => InputType::Color, + atom!("date") => InputType::Date, + atom!("datetime-local") => InputType::DatetimeLocal, + atom!("email") => InputType::Email, + atom!("file") => InputType::File, + atom!("hidden") => InputType::Hidden, + atom!("image") => InputType::Image, + atom!("month") => InputType::Month, + atom!("number") => InputType::Number, + atom!("password") => InputType::Password, + atom!("radio") => InputType::Radio, + atom!("range") => InputType::Range, + atom!("reset") => InputType::Reset, + atom!("search") => InputType::Search, + atom!("submit") => InputType::Submit, + atom!("tel") => InputType::Tel, + atom!("text") => InputType::Text, + atom!("time") => InputType::Time, + atom!("url") => InputType::Url, + atom!("week") => InputType::Week, + _ => Self::default(), + } + } +} + +impl Default for InputType { + fn default() -> InputType { + InputType::Text + } } #[derive(Debug, PartialEq)] @@ -83,212 +239,916 @@ enum ValueMode { Filename, } +#[derive(Debug, PartialEq)] +enum StepDirection { + Up, + Down, +} + #[dom_struct] pub struct HTMLInputElement { htmlelement: HTMLElement, input_type: Cell<InputType>, checked_changed: Cell<bool>, - placeholder: DOMRefCell<DOMString>, - value_changed: Cell<bool>, + placeholder: DomRefCell<DOMString>, size: Cell<u32>, maxlength: Cell<i32>, minlength: Cell<i32>, - #[ignore_heap_size_of = "#7193"] - textinput: DOMRefCell<TextInput<IpcSender<ConstellationMsg>>>, - activation_state: DOMRefCell<InputActivationState>, + #[ignore_malloc_size_of = "#7193"] + textinput: DomRefCell<TextInput<ScriptToConstellationChan>>, // https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag value_dirty: Cell<bool>, - - filelist: MutNullableJS<FileList>, - form_owner: MutNullableJS<HTMLFormElement>, + // not specified explicitly, but implied by the fact that sanitization can't + // happen until after all of step/min/max/value content attributes have + // been added + sanitization_flag: Cell<bool>, + + filelist: MutNullableDom<FileList>, + form_owner: MutNullableDom<HTMLFormElement>, + labels_node_list: MutNullableDom<NodeList>, + validity_state: MutNullableDom<ValidityState>, } #[derive(JSTraceable)] -#[must_root] -#[derive(HeapSizeOf)] -struct InputActivationState { +pub struct InputActivationState { indeterminate: bool, checked: bool, - checked_changed: bool, - checked_radio: Option<JS<HTMLInputElement>>, - // In case mutability changed - was_mutable: bool, + checked_radio: Option<DomRoot<HTMLInputElement>>, // In case the type changed old_type: InputType, -} - -impl InputActivationState { - fn new() -> InputActivationState { - InputActivationState { - indeterminate: false, - checked: false, - checked_changed: false, - checked_radio: None, - was_mutable: false, - old_type: InputType::InputText - } - } + // was_mutable is implied: pre-activation would return None if it wasn't } static DEFAULT_INPUT_SIZE: u32 = 20; static DEFAULT_MAX_LENGTH: i32 = -1; static DEFAULT_MIN_LENGTH: i32 = -1; +#[allow(non_snake_case)] impl HTMLInputElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLInputElement { - let chan = document.window().upcast::<GlobalScope>().constellation_chan().clone(); + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLInputElement { + let chan = document + .window() + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .clone(); HTMLInputElement { - htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE | IN_READ_WRITE_STATE, - local_name, prefix, document), - input_type: Cell::new(InputType::InputText), - placeholder: DOMRefCell::new(DOMString::new()), + htmlelement: HTMLElement::new_inherited_with_state( + ElementState::IN_ENABLED_STATE | ElementState::IN_READ_WRITE_STATE, + local_name, + prefix, + document, + ), + input_type: Cell::new(Default::default()), + placeholder: DomRefCell::new(DOMString::new()), checked_changed: Cell::new(false), - value_changed: Cell::new(false), maxlength: Cell::new(DEFAULT_MAX_LENGTH), minlength: Cell::new(DEFAULT_MIN_LENGTH), size: Cell::new(DEFAULT_INPUT_SIZE), - textinput: DOMRefCell::new(TextInput::new(Single, - DOMString::new(), - chan, - None, - None, - SelectionDirection::None)), - activation_state: DOMRefCell::new(InputActivationState::new()), + textinput: DomRefCell::new(TextInput::new( + Single, + DOMString::new(), + chan, + None, + None, + SelectionDirection::None, + )), value_dirty: Cell::new(false), - filelist: MutNullableJS::new(None), + sanitization_flag: Cell::new(true), + filelist: MutNullableDom::new(None), form_owner: Default::default(), + labels_node_list: MutNullableDom::new(None), + validity_state: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLInputElement> { - Node::reflect_node(box HTMLInputElement::new_inherited(local_name, prefix, document), - document, - HTMLInputElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLInputElement> { + Node::reflect_node( + Box::new(HTMLInputElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } - pub fn type_(&self) -> Atom { - self.upcast::<Element>() - .get_attribute(&ns!(), &local_name!("type")) - .map_or_else(|| atom!(""), |a| a.value().as_atom().to_owned()) + pub fn auto_directionality(&self) -> Option<String> { + match self.input_type() { + InputType::Text | InputType::Search | InputType::Url | InputType::Email => { + let value: String = self.Value().to_string(); + Some(HTMLInputElement::directionality_from_value(&value)) + }, + _ => None, + } + } + + pub fn directionality_from_value(value: &str) -> String { + if HTMLInputElement::is_first_strong_character_rtl(value) { + "rtl".to_owned() + } else { + "ltr".to_owned() + } + } + + fn is_first_strong_character_rtl(value: &str) -> bool { + for ch in value.chars() { + return match bidi_class(ch) { + BidiClass::L => false, + BidiClass::AL => true, + BidiClass::R => true, + _ => continue, + }; + } + false } - // https://html.spec.whatwg.org/multipage/#input-type-attr-summary + // https://html.spec.whatwg.org/multipage/#dom-input-value + // https://html.spec.whatwg.org/multipage/#concept-input-apply fn value_mode(&self) -> ValueMode { - match self.input_type.get() { - InputType::InputSubmit | - InputType::InputReset | - InputType::InputButton | - InputType::InputImage => ValueMode::Default, - InputType::InputCheckbox | - InputType::InputRadio => ValueMode::DefaultOn, - InputType::InputPassword | - InputType::InputText => ValueMode::Value, - InputType::InputFile => ValueMode::Filename, + match self.input_type() { + InputType::Submit | + InputType::Reset | + InputType::Button | + InputType::Image | + InputType::Hidden => ValueMode::Default, + + InputType::Checkbox | InputType::Radio => ValueMode::DefaultOn, + + InputType::Color | + InputType::Date | + InputType::DatetimeLocal | + InputType::Email | + InputType::Month | + InputType::Number | + InputType::Password | + InputType::Range | + InputType::Search | + InputType::Tel | + InputType::Text | + InputType::Time | + InputType::Url | + InputType::Week => ValueMode::Value, + + InputType::File => ValueMode::Filename, + } + } + + #[inline] + pub fn input_type(&self) -> InputType { + self.input_type.get() + } + + #[inline] + pub fn is_submit_button(&self) -> bool { + let input_type = self.input_type.get(); + input_type == InputType::Submit || input_type == InputType::Image + } + + pub fn disable_sanitization(&self) { + self.sanitization_flag.set(false); + } + + pub fn enable_sanitization(&self) { + self.sanitization_flag.set(true); + let mut textinput = self.textinput.borrow_mut(); + let mut value = textinput.single_line_content().clone(); + self.sanitize_value(&mut value); + textinput.set_content(value); + } + + fn does_readonly_apply(&self) -> bool { + match self.input_type() { + InputType::Text | + InputType::Search | + InputType::Url | + InputType::Tel | + InputType::Email | + InputType::Password | + InputType::Date | + InputType::Month | + InputType::Week | + InputType::Time | + InputType::DatetimeLocal | + InputType::Number => true, + _ => false, + } + } + + fn does_minmaxlength_apply(&self) -> bool { + match self.input_type() { + InputType::Text | + InputType::Search | + InputType::Url | + InputType::Tel | + InputType::Email | + InputType::Password => true, + _ => false, + } + } + + fn does_pattern_apply(&self) -> bool { + match self.input_type() { + InputType::Text | + InputType::Search | + InputType::Url | + InputType::Tel | + InputType::Email | + InputType::Password => true, + _ => false, + } + } + + fn does_multiple_apply(&self) -> bool { + self.input_type() == InputType::Email + } + + // valueAsNumber, step, min, and max all share the same set of + // input types they apply to + fn does_value_as_number_apply(&self) -> bool { + match self.input_type() { + InputType::Date | + InputType::Month | + InputType::Week | + InputType::Time | + InputType::DatetimeLocal | + InputType::Number | + InputType::Range => true, + _ => false, + } + } + + fn does_value_as_date_apply(&self) -> bool { + match self.input_type() { + InputType::Date | InputType::Month | InputType::Week | InputType::Time => true, + // surprisingly, spec says false for DateTimeLocal! + _ => false, + } + } + + // https://html.spec.whatwg.org/multipage#concept-input-step + fn allowed_value_step(&self) -> Option<f64> { + if let Some(attr) = self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("step")) + { + if let Ok(step) = DOMString::from(attr.summarize().value).parse_floating_point_number() + { + if step > 0.0 { + return Some(step * self.step_scale_factor()); + } + } + } + self.default_step() + .map(|step| step * self.step_scale_factor()) + } + + // https://html.spec.whatwg.org/multipage#concept-input-min + fn minimum(&self) -> Option<f64> { + if let Some(attr) = self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("min")) + { + if let Ok(min) = self.convert_string_to_number(&DOMString::from(attr.summarize().value)) + { + return Some(min); + } + } + return self.default_minimum(); + } + + // https://html.spec.whatwg.org/multipage#concept-input-max + fn maximum(&self) -> Option<f64> { + if let Some(attr) = self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("max")) + { + if let Ok(max) = self.convert_string_to_number(&DOMString::from(attr.summarize().value)) + { + return Some(max); + } + } + return self.default_maximum(); + } + + // when allowed_value_step and minumum both exist, this is the smallest + // value >= minimum that lies on an integer step + fn stepped_minimum(&self) -> Option<f64> { + match (self.minimum(), self.allowed_value_step()) { + (Some(min), Some(allowed_step)) => { + let step_base = self.step_base(); + // how many steps is min from step_base? + let nsteps = (min - step_base) / allowed_step; + // count that many integer steps, rounded +, from step_base + Some(step_base + (allowed_step * nsteps.ceil())) + }, + (_, _) => None, + } + } + + // when allowed_value_step and maximum both exist, this is the smallest + // value <= maximum that lies on an integer step + fn stepped_maximum(&self) -> Option<f64> { + match (self.maximum(), self.allowed_value_step()) { + (Some(max), Some(allowed_step)) => { + let step_base = self.step_base(); + // how many steps is max from step_base? + let nsteps = (max - step_base) / allowed_step; + // count that many integer steps, rounded -, from step_base + Some(step_base + (allowed_step * nsteps.floor())) + }, + (_, _) => None, + } + } + + // https://html.spec.whatwg.org/multipage#concept-input-min-default + fn default_minimum(&self) -> Option<f64> { + match self.input_type() { + InputType::Range => Some(0.0), + _ => None, + } + } + + // https://html.spec.whatwg.org/multipage#concept-input-max-default + fn default_maximum(&self) -> Option<f64> { + match self.input_type() { + InputType::Range => Some(100.0), + _ => None, + } + } + + // https://html.spec.whatwg.org/multipage#concept-input-value-default-range + fn default_range_value(&self) -> f64 { + let min = self.minimum().unwrap_or(0.0); + let max = self.maximum().unwrap_or(100.0); + if max < min { + min + } else { + min + (max - min) * 0.5 + } + } + + // https://html.spec.whatwg.org/multipage#concept-input-step-default + fn default_step(&self) -> Option<f64> { + match self.input_type() { + InputType::Date => Some(1.0), + InputType::Month => Some(1.0), + InputType::Week => Some(1.0), + InputType::Time => Some(60.0), + InputType::DatetimeLocal => Some(60.0), + InputType::Number => Some(1.0), + InputType::Range => Some(1.0), + _ => None, + } + } + + // https://html.spec.whatwg.org/multipage#concept-input-step-scale + fn step_scale_factor(&self) -> f64 { + match self.input_type() { + InputType::Date => 86400000.0, + InputType::Month => 1.0, + InputType::Week => 604800000.0, + InputType::Time => 1000.0, + InputType::DatetimeLocal => 1000.0, + InputType::Number => 1.0, + InputType::Range => 1.0, + _ => unreachable!(), + } + } + + // https://html.spec.whatwg.org/multipage#concept-input-min-zero + fn step_base(&self) -> f64 { + if let Some(attr) = self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("min")) + { + let minstr = &DOMString::from(attr.summarize().value); + if let Ok(min) = self.convert_string_to_number(minstr) { + return min; + } + } + if let Some(attr) = self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("value")) + { + if let Ok(value) = + self.convert_string_to_number(&DOMString::from(attr.summarize().value)) + { + return value; + } + } + self.default_step_base().unwrap_or(0.0) + } + + // https://html.spec.whatwg.org/multipage#concept-input-step-default-base + fn default_step_base(&self) -> Option<f64> { + match self.input_type() { + InputType::Week => Some(-259200000.0), + _ => None, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-input-stepdown + // https://html.spec.whatwg.org/multipage/#dom-input-stepup + fn step_up_or_down(&self, n: i32, dir: StepDirection) -> ErrorResult { + // Step 1 + if !self.does_value_as_number_apply() { + return Err(Error::InvalidState); + } + let step_base = self.step_base(); + // Step 2 + let allowed_value_step = match self.allowed_value_step() { + Some(avs) => avs, + None => return Err(Error::InvalidState), + }; + let minimum = self.minimum(); + let maximum = self.maximum(); + if let (Some(min), Some(max)) = (minimum, maximum) { + // Step 3 + if min > max { + return Ok(()); + } + // Step 4 + if let Some(smin) = self.stepped_minimum() { + if smin > max { + return Ok(()); + } + } + } + // Step 5 + let mut value: f64 = self.convert_string_to_number(&self.Value()).unwrap_or(0.0); + + // Step 6 + let valueBeforeStepping = value; + + // Step 7 + if (value - step_base) % allowed_value_step != 0.0 { + value = match dir { + StepDirection::Down => + //step down a fractional step to be on a step multiple + { + let intervals_from_base = ((value - step_base) / allowed_value_step).floor(); + intervals_from_base * allowed_value_step + step_base + } + StepDirection::Up => + // step up a fractional step to be on a step multiple + { + let intervals_from_base = ((value - step_base) / allowed_value_step).ceil(); + intervals_from_base * allowed_value_step + step_base + } + }; + } else { + value = value + + match dir { + StepDirection::Down => -f64::from(n) * allowed_value_step, + StepDirection::Up => f64::from(n) * allowed_value_step, + }; + } + + // Step 8 + if let Some(min) = minimum { + if value < min { + value = self.stepped_minimum().unwrap_or(value); + } + } + + // Step 9 + if let Some(max) = maximum { + if value > max { + value = self.stepped_maximum().unwrap_or(value); + } + } + + // Step 10 + match dir { + StepDirection::Down => { + if value > valueBeforeStepping { + return Ok(()); + } + }, + StepDirection::Up => { + if value < valueBeforeStepping { + return Ok(()); + } + }, + } + + // Step 11 + self.SetValueAsNumber(value) + } + + // https://html.spec.whatwg.org/multipage/#concept-input-list + fn suggestions_source_element(&self) -> Option<DomRoot<HTMLElement>> { + let list_string = self + .upcast::<Element>() + .get_string_attribute(&local_name!("list")); + if list_string.is_empty() { + return None; + } + let ancestor = self + .upcast::<Node>() + .GetRootNode(&GetRootNodeOptions::empty()); + let first_with_id = &ancestor + .traverse_preorder(ShadowIncluding::No) + .find(|node| { + node.downcast::<Element>() + .map_or(false, |e| e.Id() == list_string) + }); + first_with_id + .as_ref() + .and_then(|el| { + el.downcast::<HTMLDataListElement>() + .map(|data_el| data_el.upcast::<HTMLElement>()) + }) + .map(|el| DomRoot::from_ref(&*el)) + } + + // 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 + InputType::Checkbox => self.Required() && !self.Checked(), + // https://html.spec.whatwg.org/multipage/#radio-button-state-(type%3Dradio)%3Asuffering-from-being-missing + InputType::Radio => { + let mut is_required = self.Required(); + let mut is_checked = self.Checked(); + for other in radio_group_iter(self, self.radio_group_name().as_ref()) { + is_required = is_required || other.Required(); + is_checked = is_checked || other.Checked(); + } + is_required && !is_checked + }, + // https://html.spec.whatwg.org/multipage/#file-upload-state-(type%3Dfile)%3Asuffering-from-being-missing + InputType::File => { + self.Required() && + self.filelist + .get() + .map_or(true, |files| files.Length() == 0) + }, + // https://html.spec.whatwg.org/multipage/#the-required-attribute%3Asuffering-from-being-missing + _ => { + self.Required() && + self.value_mode() == ValueMode::Value && + self.is_mutable() && + value.is_empty() + }, + } + } + + // https://html.spec.whatwg.org/multipage/#suffering-from-a-type-mismatch + fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool { + if value.is_empty() { + return false; + } + + match self.input_type() { + // https://html.spec.whatwg.org/multipage/#url-state-(type%3Durl)%3Asuffering-from-a-type-mismatch + InputType::Url => Url::parse(&value).is_err(), + // https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-a-type-mismatch + // https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-a-type-mismatch-2 + InputType::Email => { + if self.Multiple() { + !split_commas(&value).all(|s| { + DOMString::from_string(s.to_string()).is_valid_email_address_string() + }) + } else { + !value.is_valid_email_address_string() + } + }, + // Other input types don't suffer from type mismatch + _ => false, + } + } + + // https://html.spec.whatwg.org/multipage/#suffering-from-a-pattern-mismatch + fn suffers_from_pattern_mismatch(&self, value: &DOMString) -> bool { + // https://html.spec.whatwg.org/multipage/#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch + // https://html.spec.whatwg.org/multipage/#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch-2 + let pattern_str = self.Pattern(); + if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() { + return false; + } + + // Rust's regex is not compatible, we need to use mozjs RegExp. + let cx = self.global().get_cx(); + let _ac = enter_realm(self); + rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>()); + + if compile_pattern(cx, &pattern_str, pattern.handle_mut()) { + if self.Multiple() && self.does_multiple_apply() { + !split_commas(&value) + .all(|s| matches_js_regex(cx, pattern.handle(), s).unwrap_or(true)) + } else { + !matches_js_regex(cx, pattern.handle(), &value).unwrap_or(true) + } + } else { + // Element doesn't suffer from pattern mismatch if pattern is invalid. + false + } + } + + // https://html.spec.whatwg.org/multipage/#suffering-from-bad-input + fn suffers_from_bad_input(&self, value: &DOMString) -> bool { + if value.is_empty() { + return false; + } + + match self.input_type() { + // https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-bad-input + // https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-bad-input-2 + InputType::Email => { + // TODO: Check for input that cannot be converted to punycode. + // Currently we don't support conversion of email values to punycode + // so always return false. + false + }, + // https://html.spec.whatwg.org/multipage/#date-state-(type%3Ddate)%3Asuffering-from-bad-input + InputType::Date => !value.is_valid_date_string(), + // https://html.spec.whatwg.org/multipage/#month-state-(type%3Dmonth)%3Asuffering-from-bad-input + InputType::Month => !value.is_valid_month_string(), + // https://html.spec.whatwg.org/multipage/#week-state-(type%3Dweek)%3Asuffering-from-bad-input + InputType::Week => !value.is_valid_week_string(), + // https://html.spec.whatwg.org/multipage/#time-state-(type%3Dtime)%3Asuffering-from-bad-input + InputType::Time => !value.is_valid_time_string(), + // https://html.spec.whatwg.org/multipage/#local-date-and-time-state-(type%3Ddatetime-local)%3Asuffering-from-bad-input + InputType::DatetimeLocal => value.parse_local_date_and_time_string().is_err(), + // https://html.spec.whatwg.org/multipage/#number-state-(type%3Dnumber)%3Asuffering-from-bad-input + // https://html.spec.whatwg.org/multipage/#range-state-(type%3Drange)%3Asuffering-from-bad-input + InputType::Number | InputType::Range => !value.is_valid_floating_point_number_string(), + // https://html.spec.whatwg.org/multipage/#color-state-(type%3Dcolor)%3Asuffering-from-bad-input + InputType::Color => !value.is_valid_simple_color_string(), + // Other input types don't suffer from bad input + _ => false, + } + } + + // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long + // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short + fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags { + // https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long + // https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short + let value_dirty = self.value_dirty.get(); + let textinput = self.textinput.borrow(); + let edit_by_user = !textinput.was_last_change_by_set_content(); + + if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() { + return ValidationFlags::empty(); + } + + let mut failed_flags = ValidationFlags::empty(); + let UTF16CodeUnits(value_len) = textinput.utf16_len(); + let min_length = self.MinLength(); + let max_length = self.MaxLength(); + + if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) { + failed_flags.insert(ValidationFlags::TOO_SHORT); + } + + if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) { + failed_flags.insert(ValidationFlags::TOO_LONG); + } + + 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 + fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags { + if value.is_empty() || !self.does_value_as_number_apply() { + return ValidationFlags::empty(); + } + + let value_as_number = match self.convert_string_to_number(&value) { + Ok(num) => num, + Err(()) => return ValidationFlags::empty(), + }; + + let mut failed_flags = ValidationFlags::empty(); + let min_value = self.minimum(); + let max_value = self.maximum(); + + // https://html.spec.whatwg.org/multipage/#has-a-reversed-range + let has_reversed_range = match (min_value, max_value) { + (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max, + _ => false, + }; + + if has_reversed_range { + // https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes:has-a-reversed-range-3 + if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() { + failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW); + failed_flags.insert(ValidationFlags::RANGE_OVERFLOW); + } + } else { + // https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes%3Asuffering-from-an-underflow-2 + if let Some(min_value) = min_value { + if value_as_number < min_value { + failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW); + } + } + // https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes%3Asuffering-from-an-overflow-2 + if let Some(max_value) = max_value { + if value_as_number > max_value { + failed_flags.insert(ValidationFlags::RANGE_OVERFLOW); + } + } + } + + // https://html.spec.whatwg.org/multipage/#the-step-attribute%3Asuffering-from-a-step-mismatch + if let Some(step) = self.allowed_value_step() { + // TODO: Spec has some issues here, see https://github.com/whatwg/html/issues/5207. + // Chrome and Firefox parse values as decimals to get exact results, + // we probably should too. + let diff = (self.step_base() - value_as_number) % step / value_as_number; + if diff.abs() > 1e-12 { + failed_flags.insert(ValidationFlags::STEP_MISMATCH); + } } + + failed_flags } } -pub trait LayoutHTMLInputElementHelpers { - #[allow(unsafe_code)] - unsafe fn value_for_layout(self) -> String; - #[allow(unsafe_code)] - unsafe fn size_for_layout(self) -> u32; - #[allow(unsafe_code)] - unsafe fn selection_for_layout(self) -> Option<Range<usize>>; - #[allow(unsafe_code)] - unsafe fn checked_state_for_layout(self) -> bool; - #[allow(unsafe_code)] - unsafe fn indeterminate_state_for_layout(self) -> bool; +pub trait LayoutHTMLInputElementHelpers<'dom> { + fn value_for_layout(self) -> Cow<'dom, str>; + fn size_for_layout(self) -> u32; + fn selection_for_layout(self) -> Option<Range<usize>>; + fn checked_state_for_layout(self) -> bool; + fn indeterminate_state_for_layout(self) -> bool; } #[allow(unsafe_code)] -unsafe fn get_raw_textinput_value(input: LayoutJS<HTMLInputElement>) -> DOMString { - (*input.unsafe_get()).textinput.borrow_for_layout().get_content() +impl<'dom> LayoutDom<'dom, HTMLInputElement> { + fn get_raw_textinput_value(self) -> DOMString { + unsafe { + self.unsafe_get() + .textinput + .borrow_for_layout() + .get_content() + } + } + + fn placeholder(self) -> &'dom str { + unsafe { self.unsafe_get().placeholder.borrow_for_layout() } + } + + fn input_type(self) -> InputType { + unsafe { self.unsafe_get().input_type.get() } + } + + fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> { + unsafe { + self.unsafe_get() + .textinput + .borrow_for_layout() + .sorted_selection_offsets_range() + } + } } -impl LayoutHTMLInputElementHelpers for LayoutJS<HTMLInputElement> { - #[allow(unsafe_code)] - unsafe fn value_for_layout(self) -> String { - #[allow(unsafe_code)] - unsafe fn get_raw_attr_value(input: LayoutJS<HTMLInputElement>, default: &str) -> String { - let elem = input.upcast::<Element>(); - let value = (*elem.unsafe_get()) +impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElement> { + fn value_for_layout(self) -> Cow<'dom, str> { + fn get_raw_attr_value<'dom>( + input: LayoutDom<'dom, HTMLInputElement>, + default: &'static str, + ) -> Cow<'dom, str> { + input + .upcast::<Element>() .get_attr_val_for_layout(&ns!(), &local_name!("value")) - .unwrap_or(default); - String::from(value) - } - - match (*self.unsafe_get()).input_type.get() { - InputType::InputCheckbox | InputType::InputRadio => String::new(), - InputType::InputFile | InputType::InputImage => String::new(), - InputType::InputButton => get_raw_attr_value(self, ""), - InputType::InputSubmit => get_raw_attr_value(self, DEFAULT_SUBMIT_VALUE), - InputType::InputReset => get_raw_attr_value(self, DEFAULT_RESET_VALUE), - InputType::InputPassword => { - let text = get_raw_textinput_value(self); + .unwrap_or(default) + .into() + } + + match self.input_type() { + InputType::Checkbox | InputType::Radio => "".into(), + InputType::File | InputType::Image => "".into(), + InputType::Button => get_raw_attr_value(self, ""), + InputType::Submit => get_raw_attr_value(self, DEFAULT_SUBMIT_VALUE), + InputType::Reset => get_raw_attr_value(self, DEFAULT_RESET_VALUE), + InputType::Password => { + let text = self.get_raw_textinput_value(); if !text.is_empty() { - text.chars().map(|_| PASSWORD_REPLACEMENT_CHAR).collect() + text.chars() + .map(|_| PASSWORD_REPLACEMENT_CHAR) + .collect::<String>() + .into() } else { - String::from((*self.unsafe_get()).placeholder.borrow_for_layout().clone()) + self.placeholder().into() } }, _ => { - let text = get_raw_textinput_value(self); + let text = self.get_raw_textinput_value(); if !text.is_empty() { - String::from(text) + text.into() } else { - String::from((*self.unsafe_get()).placeholder.borrow_for_layout().clone()) + self.placeholder().into() } }, } } - #[allow(unrooted_must_root)] #[allow(unsafe_code)] - unsafe fn size_for_layout(self) -> u32 { - (*self.unsafe_get()).size.get() + fn size_for_layout(self) -> u32 { + unsafe { self.unsafe_get().size.get() } } - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] - unsafe fn selection_for_layout(self) -> Option<Range<usize>> { - if !(*self.unsafe_get()).upcast::<Element>().focus_state() { + fn selection_for_layout(self) -> Option<Range<usize>> { + if !self.upcast::<Element>().focus_state() { return None; } - let textinput = (*self.unsafe_get()).textinput.borrow_for_layout(); + let sorted_selection_offsets_range = self.textinput_sorted_selection_offsets_range(); - match (*self.unsafe_get()).input_type.get() { - InputType::InputPassword => { - let text = get_raw_textinput_value(self); - let sel = textinput.get_absolute_selection_range(); + match self.input_type() { + InputType::Password => { + let text = self.get_raw_textinput_value(); + let sel = UTF8Bytes::unwrap_range(sorted_selection_offsets_range); // Translate indices from the raw value to indices in the replacement value. - let char_start = text[.. sel.start].chars().count(); + let char_start = text[..sel.start].chars().count(); let char_end = char_start + text[sel].chars().count(); let bytes_per_char = PASSWORD_REPLACEMENT_CHAR.len_utf8(); - Some(char_start * bytes_per_char .. char_end * bytes_per_char) - } - InputType::InputText => Some(textinput.get_absolute_selection_range()), - _ => None + Some(char_start * bytes_per_char..char_end * bytes_per_char) + }, + input_type if input_type.is_textual() => { + Some(UTF8Bytes::unwrap_range(sorted_selection_offsets_range)) + }, + _ => None, } } - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] - unsafe fn checked_state_for_layout(self) -> bool { - self.upcast::<Element>().get_state_for_layout().contains(IN_CHECKED_STATE) + fn checked_state_for_layout(self) -> bool { + self.upcast::<Element>() + .get_state_for_layout() + .contains(ElementState::IN_CHECKED_STATE) } - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] - unsafe fn indeterminate_state_for_layout(self) -> bool { - self.upcast::<Element>().get_state_for_layout().contains(IN_INDETERMINATE_STATE) + fn indeterminate_state_for_layout(self) -> bool { + self.upcast::<Element>() + .get_state_for_layout() + .contains(ElementState::IN_INDETERMINATE_STATE) + } +} + +impl TextControlElement for HTMLInputElement { + // https://html.spec.whatwg.org/multipage/#concept-input-apply + fn selection_api_applies(&self) -> bool { + match self.input_type() { + InputType::Text | + InputType::Search | + InputType::Url | + InputType::Tel | + InputType::Password => true, + + _ => false, + } + } + + // https://html.spec.whatwg.org/multipage/#concept-input-apply + // + // Defines input types to which the select() IDL method applies. These are a superset of the + // types for which selection_api_applies() returns true. + // + // Types omitted which could theoretically be included if they were + // rendered as a text control: file + fn has_selectable_text(&self) -> bool { + match self.input_type() { + InputType::Text | + InputType::Search | + InputType::Url | + InputType::Tel | + InputType::Password | + InputType::Email | + InputType::Date | + InputType::Month | + InputType::Week | + InputType::Time | + InputType::DatetimeLocal | + InputType::Number | + InputType::Color => true, + + InputType::Button | + InputType::Checkbox | + InputType::File | + InputType::Hidden | + InputType::Image | + InputType::Radio | + InputType::Range | + InputType::Reset | + InputType::Submit => false, + } + } + + fn set_dirty_value_flag(&self, value: bool) { + self.value_dirty.set(value) } } @@ -318,12 +1178,12 @@ impl HTMLInputElementMethods for HTMLInputElement { make_bool_setter!(SetDisabled, "disabled"); // https://html.spec.whatwg.org/multipage/#dom-fae-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner() } // https://html.spec.whatwg.org/multipage/#dom-input-files - fn GetFiles(&self) -> Option<Root<FileList>> { + fn GetFiles(&self) -> Option<DomRoot<FileList>> { match self.filelist.get() { Some(ref fl) => Some(fl.clone()), None => None, @@ -338,7 +1198,9 @@ impl HTMLInputElementMethods for HTMLInputElement { // https://html.spec.whatwg.org/multipage/#dom-input-checked fn Checked(&self) -> bool { - self.upcast::<Element>().state().contains(IN_CHECKED_STATE) + self.upcast::<Element>() + .state() + .contains(ElementState::IN_CHECKED_STATE) } // https://html.spec.whatwg.org/multipage/#dom-input-checked @@ -359,16 +1221,9 @@ impl HTMLInputElementMethods for HTMLInputElement { make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE); // https://html.spec.whatwg.org/multipage/#dom-input-type - make_enumerated_getter!(Type, - "type", - "text", - "hidden" | "search" | "tel" | - "url" | "email" | "password" | - "datetime" | "date" | "month" | - "week" | "time" | "datetime-local" | - "number" | "range" | "color" | - "checkbox" | "radio" | "file" | - "submit" | "image" | "reset" | "button"); + fn Type(&self) -> DOMString { + DOMString::from(self.input_type().to_str()) + } // https://html.spec.whatwg.org/multipage/#dom-input-type make_atomic_setter!(SetType, "type"); @@ -377,18 +1232,18 @@ impl HTMLInputElementMethods for HTMLInputElement { fn Value(&self) -> DOMString { match self.value_mode() { ValueMode::Value => self.textinput.borrow().get_content(), - ValueMode::Default => { - self.upcast::<Element>() - .get_attribute(&ns!(), &local_name!("value")) - .map_or(DOMString::from(""), - |a| DOMString::from(a.summarize().value)) - } - ValueMode::DefaultOn => { - self.upcast::<Element>() - .get_attribute(&ns!(), &local_name!("value")) - .map_or(DOMString::from("on"), - |a| DOMString::from(a.summarize().value)) - } + ValueMode::Default => self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("value")) + .map_or(DOMString::from(""), |a| { + DOMString::from(a.summarize().value) + }), + ValueMode::DefaultOn => self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("value")) + .map_or(DOMString::from("on"), |a| { + DOMString::from(a.summarize().value) + }), ValueMode::Filename => { let mut path = DOMString::from(""); match self.filelist.get() { @@ -397,26 +1252,40 @@ impl HTMLInputElementMethods for HTMLInputElement { path.push_str("C:\\fakepath\\"); path.push_str(f.name()); path - } + }, None => path, }, None => path, } - } + }, } } // https://html.spec.whatwg.org/multipage/#dom-input-value - fn SetValue(&self, value: DOMString) -> ErrorResult { + fn SetValue(&self, mut value: DOMString) -> ErrorResult { match self.value_mode() { ValueMode::Value => { - self.textinput.borrow_mut().set_content(value); + // Step 3. self.value_dirty.set(true); - } - ValueMode::Default | - ValueMode::DefaultOn => { - self.upcast::<Element>().set_string_attribute(&local_name!("value"), value); - } + + // Step 4. + self.sanitize_value(&mut value); + + let mut textinput = self.textinput.borrow_mut(); + + // Step 5. + if *textinput.single_line_content() != value { + // Steps 1-2 + textinput.set_content(value); + + // Step 5. + textinput.clear_selection_to_limit(Direction::Forward); + } + }, + ValueMode::Default | ValueMode::DefaultOn => { + self.upcast::<Element>() + .set_string_attribute(&local_name!("value"), value); + }, ValueMode::Filename => { if value.is_empty() { let window = window_from_node(self); @@ -425,10 +1294,9 @@ impl HTMLInputElementMethods for HTMLInputElement { } else { return Err(Error::InvalidState); } - } + }, } - self.value_changed.set(true); self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); Ok(()) } @@ -439,6 +1307,96 @@ impl HTMLInputElementMethods for HTMLInputElement { // https://html.spec.whatwg.org/multipage/#dom-input-defaultvalue make_setter!(SetDefaultValue, "value"); + // https://html.spec.whatwg.org/multipage/#dom-input-min + make_getter!(Min, "min"); + + // https://html.spec.whatwg.org/multipage/#dom-input-min + make_setter!(SetMin, "min"); + + // https://html.spec.whatwg.org/multipage/#dom-input-list + fn GetList(&self) -> Option<DomRoot<HTMLElement>> { + self.suggestions_source_element() + } + + // https://html.spec.whatwg.org/multipage/#dom-input-valueasdate + #[allow(unsafe_code)] + fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> { + self.convert_string_to_naive_datetime(self.Value()) + .map(|dt| unsafe { + let time = ClippedTime { + t: dt.timestamp_millis() as f64, + }; + NonNull::new_unchecked(NewDateObject(*cx, time)) + }) + .ok() + } + + // https://html.spec.whatwg.org/multipage/#dom-input-valueasdate + #[allow(unsafe_code, non_snake_case)] + fn SetValueAsDate(&self, cx: SafeJSContext, value: *mut JSObject) -> ErrorResult { + rooted!(in(*cx) let value = value); + if !self.does_value_as_date_apply() { + return Err(Error::InvalidState); + } + if value.is_null() { + return self.SetValue(DOMString::from("")); + } + let mut msecs: f64 = 0.0; + // We need to go through unsafe code to interrogate jsapi about a Date. + // To minimize the amount of unsafe code to maintain, this just gets the milliseconds, + // which we then reinflate into a NaiveDate for use in safe code. + unsafe { + let mut isDate = false; + if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) { + return Err(Error::JSFailed); + } + if !isDate { + return Err(Error::Type("Value was not a date".to_string())); + } + if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) { + return Err(Error::JSFailed); + } + if !msecs.is_finite() { + return self.SetValue(DOMString::from("")); + } + } + // now we make a Rust date out of it so we can use safe code for the + // actual conversion logic + match milliseconds_to_datetime(msecs) { + Ok(dt) => match self.convert_naive_datetime_to_string(dt) { + Ok(converted) => self.SetValue(converted), + _ => self.SetValue(DOMString::from("")), + }, + _ => self.SetValue(DOMString::from("")), + } + } + + // https://html.spec.whatwg.org/multipage/#dom-input-valueasnumber + fn ValueAsNumber(&self) -> f64 { + self.convert_string_to_number(&self.Value()) + .unwrap_or(std::f64::NAN) + } + + // https://html.spec.whatwg.org/multipage/#dom-input-valueasnumber + fn SetValueAsNumber(&self, value: f64) -> ErrorResult { + if value.is_infinite() { + Err(Error::Type("value is not finite".to_string())) + } else if !self.does_value_as_number_apply() { + Err(Error::InvalidState) + } else if value.is_nan() { + self.SetValue(DOMString::from("")) + } else if let Ok(converted) = self.convert_number_to_string(value) { + self.SetValue(converted) + } else { + // The most literal spec-compliant implementation would + // use bignum chrono types so overflow is impossible, + // but just setting an overflow to the empty string matches + // Firefox's behavior. + // (for example, try input.valueAsNumber=1e30 on a type="date" input) + self.SetValue(DOMString::from("")) + } + } + // https://html.spec.whatwg.org/multipage/#attr-fe-name make_getter!(Name, "name"); @@ -452,16 +1410,18 @@ impl HTMLInputElementMethods for HTMLInputElement { make_setter!(SetPlaceholder, "placeholder"); // https://html.spec.whatwg.org/multipage/#dom-input-formaction - make_url_or_base_getter!(FormAction, "formaction"); + make_form_action_getter!(FormAction, "formaction"); // https://html.spec.whatwg.org/multipage/#dom-input-formaction make_setter!(SetFormAction, "formaction"); // https://html.spec.whatwg.org/multipage/#dom-input-formenctype - make_enumerated_getter!(FormEnctype, - "formenctype", - "application/x-www-form-urlencoded", - "text/plain" | "multipart/form-data"); + make_enumerated_getter!( + FormEnctype, + "formenctype", + "application/x-www-form-urlencoded", + "text/plain" | "multipart/form-data" + ); // https://html.spec.whatwg.org/multipage/#dom-input-formenctype make_setter!(SetFormEnctype, "formenctype"); @@ -502,12 +1462,6 @@ impl HTMLInputElementMethods for HTMLInputElement { // https://html.spec.whatwg.org/multipage/#dom-input-minlength make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH); - // https://html.spec.whatwg.org/multipage/#dom-input-min - make_getter!(Min, "min"); - - // https://html.spec.whatwg.org/multipage/#dom-input-min - make_setter!(SetMin, "min"); - // https://html.spec.whatwg.org/multipage/#dom-input-multiple make_bool_getter!(Multiple, "multiple"); @@ -540,71 +1494,89 @@ impl HTMLInputElementMethods for HTMLInputElement { // https://html.spec.whatwg.org/multipage/#dom-input-indeterminate fn Indeterminate(&self) -> bool { - self.upcast::<Element>().state().contains(IN_INDETERMINATE_STATE) + self.upcast::<Element>() + .state() + .contains(ElementState::IN_INDETERMINATE_STATE) } // https://html.spec.whatwg.org/multipage/#dom-input-indeterminate fn SetIndeterminate(&self, val: bool) { - self.upcast::<Element>().set_state(IN_INDETERMINATE_STATE, val) + self.upcast::<Element>() + .set_state(ElementState::IN_INDETERMINATE_STATE, val) } // https://html.spec.whatwg.org/multipage/#dom-lfe-labels - fn Labels(&self) -> Root<NodeList> { - if self.type_() == atom!("hidden") { - let window = window_from_node(self); - NodeList::empty(&window) + // Different from make_labels_getter because this one + // conditionally returns null. + fn GetLabels(&self) -> Option<DomRoot<NodeList>> { + if self.input_type() == InputType::Hidden { + None } else { - self.upcast::<HTMLElement>().labels() + Some(self.labels_node_list.or_init(|| { + NodeList::new_labels_list( + self.upcast::<Node>().owner_doc().window(), + self.upcast::<HTMLElement>(), + ) + })) } } - // https://html.spec.whatwg.org/multipage/#dom-input-selectionstart - fn SelectionStart(&self) -> u32 { - self.textinput.borrow().get_selection_start() + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-select + fn Select(&self) { + self.selection().dom_select(); } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart - fn SetSelectionStart(&self, start: u32) { - let selection_end = self.SelectionEnd(); - self.textinput.borrow_mut().set_selection_range(start, selection_end); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + fn GetSelectionStart(&self) -> Option<u32> { + self.selection().dom_start() + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart + fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult { + self.selection().set_dom_start(start) } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend - fn SelectionEnd(&self) -> u32 { - self.textinput.borrow().get_absolute_insertion_point() as u32 + fn GetSelectionEnd(&self) -> Option<u32> { + self.selection().dom_end() } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend - fn SetSelectionEnd(&self, end: u32) { - let selection_start = self.SelectionStart(); - self.textinput.borrow_mut().set_selection_range(selection_start, end); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult { + self.selection().set_dom_end(end) } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection - fn SelectionDirection(&self) -> DOMString { - DOMString::from(self.textinput.borrow().selection_direction) + fn GetSelectionDirection(&self) -> Option<DOMString> { + self.selection().dom_direction() } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection - fn SetSelectionDirection(&self, direction: DOMString) { - self.textinput.borrow_mut().selection_direction = SelectionDirection::from(direction); + fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult { + self.selection().set_dom_direction(direction) } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange - fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) { - let direction = direction.map_or(SelectionDirection::None, |d| SelectionDirection::from(d)); - self.textinput.borrow_mut().selection_direction = direction; - self.textinput.borrow_mut().set_selection_range(start, end); - let window = window_from_node(self); - let _ = window.user_interaction_task_source().queue_event( - &self.upcast(), - atom!("select"), - EventBubbles::Bubbles, - EventCancelable::NotCancelable, - &window); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult { + self.selection().set_dom_range(start, end, direction) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext + fn SetRangeText(&self, replacement: DOMString) -> ErrorResult { + self.selection() + .set_dom_range_text(replacement, None, None, Default::default()) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext + fn SetRangeText_( + &self, + replacement: DOMString, + start: u32, + end: u32, + selection_mode: SelectionMode, + ) -> ErrorResult { + self.selection() + .set_dom_range_text(replacement, Some(start), Some(end), selection_mode) } // Select the files based on filepaths passed in, @@ -612,53 +1584,105 @@ impl HTMLInputElementMethods for HTMLInputElement { // used for test purpose. // check-tidy: no specs after this line fn SelectFiles(&self, paths: Vec<DOMString>) { - if self.input_type.get() == InputType::InputFile { + if self.input_type() == InputType::File { self.select_files(Some(paths)); } } + + // https://html.spec.whatwg.org/multipage/#dom-input-stepup + fn StepUp(&self, n: i32) -> ErrorResult { + self.step_up_or_down(n, StepDirection::Up) + } + + // https://html.spec.whatwg.org/multipage/#dom-input-stepdown + fn StepDown(&self, n: i32) -> ErrorResult { + self.step_up_or_down(n, StepDirection::Down) + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate + fn WillValidate(&self) -> bool { + self.is_instance_validatable() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validity + fn Validity(&self) -> DomRoot<ValidityState> { + self.validity_state() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity + fn CheckValidity(&self) -> bool { + self.check_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity + fn ReportValidity(&self) -> bool { + self.report_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage + fn ValidationMessage(&self) -> DOMString { + self.validation_message() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity + fn SetCustomValidity(&self, error: DOMString) { + self.validity_state().set_custom_error_message(error); + } } +fn radio_group_iter<'a>( + elem: &'a HTMLInputElement, + group: Option<&'a Atom>, +) -> impl Iterator<Item = DomRoot<HTMLInputElement>> + 'a { + let owner = elem.form_owner(); + let root = elem + .upcast::<Node>() + .GetRootNode(&GetRootNodeOptions::empty()); + + // If group is None, in_same_group always fails, but we need to always return elem. + root.traverse_preorder(ShadowIncluding::No) + .filter_map(|r| DomRoot::downcast::<HTMLInputElement>(r)) + .filter(move |r| &**r == elem || in_same_group(&r, owner.as_deref(), group, None)) +} -#[allow(unsafe_code)] fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>) { - match group { - None | Some(&atom!("")) => { - // Radio input elements with a missing or empty name are alone in their - // own group. - return; - }, - _ => {}, - } - - //TODO: if not in document, use root ancestor instead of document - let owner = broadcaster.form_owner(); - let doc = document_from_node(broadcaster); - - // This function is a workaround for lifetime constraint difficulties. - fn do_broadcast(doc_node: &Node, broadcaster: &HTMLInputElement, - owner: Option<&HTMLFormElement>, group: Option<&Atom>) { - let iter = doc_node.query_selector_iter(DOMString::from("input[type=radio]")).unwrap() - .filter_map(Root::downcast::<HTMLInputElement>) - .filter(|r| in_same_group(&r, owner, group) && broadcaster != &**r); - for ref r in iter { - if r.Checked() { - r.SetChecked(false); - } + for r in radio_group_iter(broadcaster, group) { + if broadcaster != &*r && r.Checked() { + r.SetChecked(false); } } - - do_broadcast(doc.upcast(), broadcaster, owner.r(), group) } // https://html.spec.whatwg.org/multipage/#radio-button-group -fn in_same_group(other: &HTMLInputElement, owner: Option<&HTMLFormElement>, - group: Option<&Atom>) -> bool { - other.input_type.get() == InputType::InputRadio && - // TODO Both a and b are in the same home subtree. - other.form_owner().r() == owner && - match (other.radio_group_name(), group) { - (Some(ref s1), Some(s2)) => compatibility_caseless_match_str(s1, s2) && s2 != &atom!(""), - _ => false +fn in_same_group( + other: &HTMLInputElement, + owner: Option<&HTMLFormElement>, + group: Option<&Atom>, + tree_root: Option<&Node>, +) -> bool { + if group.is_none() { + // Radio input elements with a missing or empty name are alone in their own group. + return false; + } + + if other.input_type() != InputType::Radio || + other.form_owner().as_deref() != owner || + other.radio_group_name().as_ref() != group + { + return false; + } + + match tree_root { + Some(tree_root) => { + let other_root = other + .upcast::<Node>() + .GetRootNode(&GetRootNodeOptions::empty()); + tree_root == &*other_root + }, + None => { + // Skip check if the tree root isn't provided. + true + }, } } @@ -669,93 +1693,121 @@ impl HTMLInputElement { } } - /// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set - /// Steps range from 3.1 to 3.7 (specific to HTMLInputElement) - pub fn form_datums(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> { + /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set> + /// Steps range from 5.1 to 5.10 (specific to HTMLInputElement) + pub fn form_datums( + &self, + submitter: Option<FormSubmitter>, + encoding: Option<&'static Encoding>, + ) -> Vec<FormDatum> { // 3.1: disabled state check is in get_unclean_dataset - // Step 3.2 - let ty = self.type_(); - // Step 3.4 + // Step 5.2 + let ty = self.Type(); + + // Step 5.4 let name = self.Name(); let is_submitter = match submitter { - Some(FormSubmitter::InputElement(s)) => { - self == s - }, - _ => false + Some(FormSubmitter::InputElement(s)) => self == s, + _ => false, }; - match ty { - // Step 3.1: it's a button but it is not submitter. - atom!("submit") | atom!("button") | atom!("reset") if !is_submitter => return vec![], - // Step 3.1: it's the "Checkbox" or "Radio Button" and whose checkedness is false. - atom!("radio") | atom!("checkbox") => if !self.Checked() || name.is_empty() { + match self.input_type() { + // Step 5.1: it's a button but it is not submitter. + InputType::Submit | InputType::Button | InputType::Reset if !is_submitter => { return vec![]; }, - atom!("file") => { + + // Step 5.1: it's the "Checkbox" or "Radio Button" and whose checkedness is false. + InputType::Radio | InputType::Checkbox => { + if !self.Checked() || name.is_empty() { + return vec![]; + } + }, + + InputType::File => { let mut datums = vec![]; - // Step 3.2-3.7 + // Step 5.2-5.7 let name = self.Name(); - let type_ = self.Type(); match self.GetFiles() { Some(fl) => { for f in fl.iter_files() { datums.push(FormDatum { - ty: type_.clone(), + ty: ty.clone(), name: name.clone(), - value: FormDatumValue::File(Root::from_ref(&f)), + value: FormDatumValue::File(DomRoot::from_ref(&f)), }); } - } + }, None => { datums.push(FormDatum { // XXX(izgzhen): Spec says 'application/octet-stream' as the type, // but this is _type_ of element rather than content right? - ty: type_.clone(), + ty: ty.clone(), name: name.clone(), value: FormDatumValue::String(DOMString::from("")), }) - } + }, } return datums; - } - atom!("image") => return vec![], // Unimplemented - // Step 3.1: it's not the "Image Button" and doesn't have a name attribute. - _ => if name.is_empty() { - return vec![]; - } + }, + + InputType::Image => return vec![], // Unimplemented + + // Step 5.10: it's a hidden field named _charset_ + InputType::Hidden => { + if name.to_ascii_lowercase() == "_charset_" { + return vec![FormDatum { + ty: ty.clone(), + name: name, + value: FormDatumValue::String(match encoding { + None => DOMString::from("UTF-8"), + Some(enc) => DOMString::from(enc.name()), + }), + }]; + } + }, + // Step 5.1: it's not the "Image Button" and doesn't have a name attribute. + _ => { + if name.is_empty() { + return vec![]; + } + }, } - // Step 3.9 + // Step 5.12 vec![FormDatum { - ty: DOMString::from(&*ty), // FIXME(ajeffrey): Convert directly from Atoms to DOMStrings + ty: ty.clone(), name: name, - value: FormDatumValue::String(self.Value()) + value: FormDatumValue::String(self.Value()), }] } // https://html.spec.whatwg.org/multipage/#radio-button-group fn radio_group_name(&self) -> Option<Atom> { - //TODO: determine form owner - self.upcast::<Element>() - .get_attribute(&ns!(), &local_name!("name")) - .map(|name| name.value().as_atom().clone()) + self.upcast::<Element>().get_name().and_then(|name| { + if name == atom!("") { + None + } else { + Some(name) + } + }) } fn update_checked_state(&self, checked: bool, dirty: bool) { - self.upcast::<Element>().set_state(IN_CHECKED_STATE, checked); + self.upcast::<Element>() + .set_state(ElementState::IN_CHECKED_STATE, checked); if dirty { self.checked_changed.set(true); } - if self.input_type.get() == InputType::InputRadio && checked { - broadcast_radio_checked(self, - self.radio_group_name().as_ref()); + if self.input_type() == InputType::Radio && checked { + broadcast_radio_checked(self, self.radio_group_name().as_ref()); } self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); @@ -771,30 +1823,28 @@ impl HTMLInputElement { // https://html.spec.whatwg.org/multipage/#the-input-element:concept-form-reset-control pub fn reset(&self) { - match self.input_type.get() { - InputType::InputRadio | InputType::InputCheckbox => { + match self.input_type() { + InputType::Radio | InputType::Checkbox => { self.update_checked_state(self.DefaultChecked(), false); self.checked_changed.set(false); }, - InputType::InputImage => (), - _ => () + InputType::Image => (), + _ => (), } - - self.SetValue(self.DefaultValue()) - .expect("Failed to reset input value to default."); + self.textinput.borrow_mut().set_content(self.DefaultValue()); self.value_dirty.set(false); - self.value_changed.set(false); self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } fn update_placeholder_shown_state(&self) { - match self.input_type.get() { - InputType::InputText | InputType::InputPassword => {}, - _ => return, + if !self.input_type().is_textual_or_password() { + return; } + let has_placeholder = !self.placeholder.borrow().is_empty(); let has_value = !self.textinput.borrow().is_empty(); let el = self.upcast::<Element>(); + el.set_placeholder_shown_state(has_placeholder && !has_value); } @@ -805,18 +1855,22 @@ impl HTMLInputElement { let origin = get_blob_origin(&window.get_url()); let resource_threads = window.upcast::<GlobalScope>().resource_threads(); - let mut files: Vec<Root<File>> = vec![]; + let mut files: Vec<DomRoot<File>> = vec![]; let mut error = None; let filter = filter_from_accept(&self.Accept()); let target = self.upcast::<EventTarget>(); if self.Multiple() { - let opt_test_paths = opt_test_paths.map(|paths| paths.iter().map(|p| p.to_string()).collect()); + let opt_test_paths = + opt_test_paths.map(|paths| paths.iter().map(|p| p.to_string()).collect()); - let (chan, recv) = ipc::channel().expect("Error initializing channel"); + let (chan, recv) = ipc::channel(self.global().time_profiler_chan().clone()) + .expect("Error initializing channel"); let msg = FileManagerThreadMsg::SelectFiles(filter, chan, origin, opt_test_paths); - let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg)).unwrap(); + let _ = resource_threads + .send(CoreResourceMsg::ToFileManager(msg)) + .unwrap(); match recv.recv().expect("IpcSender side error") { Ok(selected_files) => { @@ -834,13 +1888,16 @@ impl HTMLInputElement { } else { Some(paths[0].to_string()) // neglect other paths } - } + }, None => None, }; - let (chan, recv) = ipc::channel().expect("Error initializing channel"); + let (chan, recv) = ipc::channel(self.global().time_profiler_chan().clone()) + .expect("Error initializing channel"); let msg = FileManagerThreadMsg::SelectFile(filter, chan, origin, opt_test_path); - let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg)).unwrap(); + let _ = resource_threads + .send(CoreResourceMsg::ToFileManager(msg)) + .unwrap(); match recv.recv().expect("IpcSender side error") { Ok(selected) => { @@ -860,23 +1917,374 @@ impl HTMLInputElement { target.fire_bubbling_event(atom!("change")); } } + + // https://html.spec.whatwg.org/multipage/#value-sanitization-algorithm + fn sanitize_value(&self, value: &mut DOMString) { + // if sanitization_flag is false, we are setting content attributes + // on an element we haven't really finished creating; we will + // enable the flag and really sanitize before this element becomes + // observable. + if !self.sanitization_flag.get() { + return; + } + match self.input_type() { + InputType::Text | InputType::Search | InputType::Tel | InputType::Password => { + value.strip_newlines(); + }, + InputType::Url => { + value.strip_newlines(); + value.strip_leading_and_trailing_ascii_whitespace(); + }, + InputType::Date => { + if !value.is_valid_date_string() { + value.clear(); + } + }, + InputType::Month => { + if !value.is_valid_month_string() { + value.clear(); + } + }, + InputType::Week => { + if !value.is_valid_week_string() { + value.clear(); + } + }, + InputType::Color => { + if value.is_valid_simple_color_string() { + value.make_ascii_lowercase(); + } else { + *value = "#000000".into(); + } + }, + InputType::Time => { + if !value.is_valid_time_string() { + value.clear(); + } + }, + InputType::DatetimeLocal => { + if value + .convert_valid_normalized_local_date_and_time_string() + .is_err() + { + value.clear(); + } + }, + InputType::Number => { + if !value.is_valid_floating_point_number_string() { + value.clear(); + } + // Spec says that user agent "may" round the value + // when it's suffering a step mismatch, but WPT tests + // want it unrounded, and this matches other browser + // behavior (typing an unrounded number into an + // integer field box and pressing enter generally keeps + // the number intact but makes the input box :invalid) + }, + // https://html.spec.whatwg.org/multipage/#range-state-(type=range):value-sanitization-algorithm + InputType::Range => { + if !value.is_valid_floating_point_number_string() { + *value = DOMString::from(self.default_range_value().to_string()); + } + if let Ok(fval) = &value.parse::<f64>() { + let mut fval = *fval; + // comparing max first, because if they contradict + // the spec wants min to be the one that applies + if let Some(max) = self.maximum() { + if fval > max { + fval = max; + } + } + if let Some(min) = self.minimum() { + if fval < min { + fval = min; + } + } + // https://html.spec.whatwg.org/multipage/#range-state-(type=range):suffering-from-a-step-mismatch + // Spec does not describe this in a way that lends itself to + // reproducible handling of floating-point rounding; + // Servo may fail a WPT test because .1 * 6 == 6.000000000000001 + if let Some(allowed_value_step) = self.allowed_value_step() { + let step_base = self.step_base(); + let steps_from_base = (fval - step_base) / allowed_value_step; + if steps_from_base.fract() != 0.0 { + // not an integer number of steps, there's a mismatch + // round the number of steps... + let int_steps = round_halves_positive(steps_from_base); + // and snap the value to that rounded value... + fval = int_steps * allowed_value_step + step_base; + + // but if after snapping we're now outside min..max + // we have to adjust! (adjusting to min last because + // that "wins" over max in the spec) + if let Some(stepped_maximum) = self.stepped_maximum() { + if fval > stepped_maximum { + fval = stepped_maximum; + } + } + if let Some(stepped_minimum) = self.stepped_minimum() { + if fval < stepped_minimum { + fval = stepped_minimum; + } + } + } + } + *value = DOMString::from(fval.to_string()); + }; + }, + InputType::Email => { + if !self.Multiple() { + value.strip_newlines(); + value.strip_leading_and_trailing_ascii_whitespace(); + } else { + let sanitized = str_join( + split_commas(value).map(|token| { + let mut token = DOMString::from_string(token.to_string()); + token.strip_newlines(); + token.strip_leading_and_trailing_ascii_whitespace(); + token + }), + ",", + ); + value.clear(); + value.push_str(sanitized.as_str()); + } + }, + // The following inputs don't have a value sanitization algorithm. + // See https://html.spec.whatwg.org/multipage/#value-sanitization-algorithm + InputType::Button | + InputType::Checkbox | + InputType::File | + InputType::Hidden | + InputType::Image | + InputType::Radio | + InputType::Reset | + InputType::Submit => (), + } + } + + #[allow(unrooted_must_root)] + fn selection(&self) -> TextControlSelection<Self> { + TextControlSelection::new(&self, &self.textinput) + } + + // https://html.spec.whatwg.org/multipage/#implicit-submission + #[allow(unsafe_code)] + fn implicit_submission(&self) { + let doc = document_from_node(self); + let node = doc.upcast::<Node>(); + let owner = self.form_owner(); + let form = match owner { + None => return, + Some(ref f) => f, + }; + + if self.upcast::<Element>().click_in_progress() { + return; + } + let submit_button; + submit_button = node + .query_selector_iter(DOMString::from("input[type=submit]")) + .unwrap() + .filter_map(DomRoot::downcast::<HTMLInputElement>) + .find(|r| r.form_owner() == owner); + match submit_button { + Some(ref button) => { + if button.is_instance_activatable() { + // spec does not actually say to set the not trusted flag, + // but we can get here from synthetic keydown events + button + .upcast::<Node>() + .fire_synthetic_mouse_event_not_trusted(DOMString::from("click")); + } + }, + None => { + let inputs = node + .query_selector_iter(DOMString::from("input")) + .unwrap() + .filter_map(DomRoot::downcast::<HTMLInputElement>) + .filter(|input| { + input.form_owner() == owner && + match input.input_type() { + InputType::Text | + InputType::Search | + InputType::Url | + InputType::Tel | + InputType::Email | + InputType::Password | + InputType::Date | + InputType::Month | + InputType::Week | + InputType::Time | + InputType::DatetimeLocal | + InputType::Number => true, + _ => false, + } + }); + + if inputs.skip(1).next().is_some() { + // lazily test for > 1 submission-blocking inputs + return; + } + form.submit( + SubmittedFrom::NotFromForm, + FormSubmitter::FormElement(&form), + ); + }, + } + } + + // https://html.spec.whatwg.org/multipage/#concept-input-value-string-number + fn convert_string_to_number(&self, value: &DOMString) -> Result<f64, ()> { + match self.input_type() { + InputType::Date => match value.parse_date_string() { + Ok((year, month, day)) => { + let d = NaiveDate::from_ymd(year, month, day); + let duration = d.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + Ok(duration.num_milliseconds() as f64) + }, + _ => Err(()), + }, + InputType::Month => match value.parse_month_string() { + // This one returns number of months, not milliseconds + // (specification requires this, presumably because number of + // milliseconds is not consistent across months) + // the - 1.0 is because january is 1, not 0 + Ok((year, month)) => Ok(((year - 1970) * 12) as f64 + (month as f64 - 1.0)), + _ => Err(()), + }, + InputType::Week => match value.parse_week_string() { + Ok((year, weeknum)) => { + let d = NaiveDate::from_isoywd(year, weeknum, Weekday::Mon); + let duration = d.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + Ok(duration.num_milliseconds() as f64) + }, + _ => Err(()), + }, + InputType::Time => match value.parse_time_string() { + Ok((hours, minutes, seconds)) => { + Ok((seconds as f64 + 60.0 * minutes as f64 + 3600.0 * hours as f64) * 1000.0) + }, + _ => Err(()), + }, + InputType::DatetimeLocal => match value.parse_local_date_and_time_string() { + // Is this supposed to know the locale's daylight-savings-time rules? + Ok(((year, month, day), (hours, minutes, seconds))) => { + let d = NaiveDate::from_ymd(year, month, day); + let ymd_duration = d.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + let hms_millis = + (seconds + 60.0 * minutes as f64 + 3600.0 * hours as f64) * 1000.0; + Ok(ymd_duration.num_milliseconds() as f64 + hms_millis) + }, + _ => Err(()), + }, + InputType::Number | InputType::Range => value.parse_floating_point_number(), + // min/max/valueAsNumber/stepDown/stepUp do not apply to + // the remaining types + _ => Err(()), + } + } + + // https://html.spec.whatwg.org/multipage/#concept-input-value-string-number + fn convert_number_to_string(&self, value: f64) -> Result<DOMString, ()> { + match self.input_type() { + InputType::Date => { + let datetime = milliseconds_to_datetime(value)?; + Ok(DOMString::from(datetime.format("%Y-%m-%d").to_string())) + }, + InputType::Month => { + // interpret value as months(not millis) in epoch, return monthstring + let year_from_1970 = (value / 12.0).floor(); + let month = (value - year_from_1970 * 12.0).floor() as u32 + 1; // january is 1, not 0 + let year = (year_from_1970 + 1970.0) as u64; + Ok(DOMString::from(format!("{:04}-{:02}", year, month))) + }, + InputType::Week => { + let datetime = milliseconds_to_datetime(value)?; + let year = datetime.iso_week().year(); // not necessarily the same as datetime.year() + let week = datetime.iso_week().week(); + Ok(DOMString::from(format!("{:04}-W{:02}", year, week))) + }, + InputType::Time => { + let datetime = milliseconds_to_datetime(value)?; + Ok(DOMString::from(datetime.format("%H:%M:%S%.3f").to_string())) + }, + InputType::DatetimeLocal => { + let datetime = milliseconds_to_datetime(value)?; + Ok(DOMString::from( + datetime.format("%Y-%m-%dT%H:%M:%S%.3f").to_string(), + )) + }, + InputType::Number | InputType::Range => Ok(DOMString::from(value.to_string())), + // this won't be called from other input types + _ => unreachable!(), + } + } + + // https://html.spec.whatwg.org/multipage/#concept-input-value-string-date + // This does the safe Rust part of conversion; the unsafe JS Date part + // is in GetValueAsDate + fn convert_string_to_naive_datetime(&self, value: DOMString) -> Result<NaiveDateTime, ()> { + match self.input_type() { + InputType::Date => value + .parse_date_string() + .and_then(|(y, m, d)| NaiveDate::from_ymd_opt(y, m, d).ok_or(())) + .map(|date| date.and_hms(0, 0, 0)), + InputType::Time => value.parse_time_string().and_then(|(h, m, s)| { + let whole_seconds = s.floor(); + let nanos = ((s - whole_seconds) * 1e9).floor() as u32; + NaiveDate::from_ymd(1970, 1, 1) + .and_hms_nano_opt(h, m, whole_seconds as u32, nanos) + .ok_or(()) + }), + InputType::Week => value + .parse_week_string() + .and_then(|(iso_year, week)| { + NaiveDate::from_isoywd_opt(iso_year, week, Weekday::Mon).ok_or(()) + }) + .map(|date| date.and_hms(0, 0, 0)), + InputType::Month => value + .parse_month_string() + .and_then(|(y, m)| NaiveDate::from_ymd_opt(y, m, 1).ok_or(())) + .map(|date| date.and_hms(0, 0, 0)), + // does not apply to other types + _ => Err(()), + } + } + + // https://html.spec.whatwg.org/multipage/#concept-input-value-date-string + // This does the safe Rust part of conversion; the unsafe JS Date part + // is in SetValueAsDate + fn convert_naive_datetime_to_string(&self, value: NaiveDateTime) -> Result<DOMString, ()> { + match self.input_type() { + InputType::Date => Ok(DOMString::from(value.format("%Y-%m-%d").to_string())), + InputType::Month => Ok(DOMString::from(value.format("%Y-%m").to_string())), + InputType::Week => { + let year = value.iso_week().year(); // not necessarily the same as value.year() + let week = value.iso_week().week(); + Ok(DOMString::from(format!("{:04}-W{:02}", year, week))) + }, + InputType::Time => Ok(DOMString::from(value.format("%H:%M:%S%.3f").to_string())), + // this won't be called from other input types + _ => unreachable!(), + } + } } impl VirtualMethods for HTMLInputElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); - match attr.local_name() { &local_name!("disabled") => { let disabled_state = match mutation { AttributeMutation::Set(None) => true, AttributeMutation::Set(Some(_)) => { - // Input was already disabled before. - return; + // Input was already disabled before. + return; }, AttributeMutation::Removed => false, }; @@ -885,55 +2293,48 @@ impl VirtualMethods for HTMLInputElement { el.set_enabled_state(!disabled_state); el.check_ancestors_disabled_state_for_form_control(); - if self.input_type.get() == InputType::InputText { + if self.input_type().is_textual() { let read_write = !(self.ReadOnly() || el.disabled_state()); el.set_read_write_state(read_write); } + + el.update_sequentially_focusable_status(); }, &local_name!("checked") if !self.checked_changed.get() => { let checked_state = match mutation { AttributeMutation::Set(None) => true, AttributeMutation::Set(Some(_)) => { - // Input was already checked before. - return; + // Input was already checked before. + return; }, AttributeMutation::Removed => false, }; self.update_checked_state(checked_state, false); }, &local_name!("size") => { - let size = mutation.new_value(attr).map(|value| { - value.as_uint() - }); + let size = mutation.new_value(attr).map(|value| value.as_uint()); self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE)); - } + }, &local_name!("type") => { let el = self.upcast::<Element>(); match mutation { AttributeMutation::Set(_) => { - let new_type = match attr.value().as_atom() { - &atom!("button") => InputType::InputButton, - &atom!("submit") => InputType::InputSubmit, - &atom!("reset") => InputType::InputReset, - &atom!("file") => InputType::InputFile, - &atom!("radio") => InputType::InputRadio, - &atom!("checkbox") => InputType::InputCheckbox, - &atom!("password") => InputType::InputPassword, - _ => InputType::InputText, - }; + let new_type = InputType::from(attr.value().as_atom()); // https://html.spec.whatwg.org/multipage/#input-type-change let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value()); + let previously_selectable = self.selection_api_applies(); + self.input_type.set(new_type); - if new_type == InputType::InputText { + if new_type.is_textual() { let read_write = !(self.ReadOnly() || el.disabled_state()); el.set_read_write_state(read_write); } else { el.set_read_write_state(false); } - if new_type == InputType::InputFile { + if new_type == InputType::File { let window = window_from_node(self); let filelist = FileList::new(&window, vec![]); self.filelist.set(Some(&filelist)); @@ -947,96 +2348,112 @@ impl VirtualMethods for HTMLInputElement { (&ValueMode::Value, false, ValueMode::DefaultOn) => { self.SetValue(old_idl_value) .expect("Failed to set input value on type change to a default ValueMode."); - } + }, // Step 2 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => { - self.SetValue(self.upcast::<Element>() - .get_attribute(&ns!(), &local_name!("value")) - .map_or(DOMString::from(""), - |a| DOMString::from(a.summarize().value))) - .expect("Failed to set input value on type change to ValueMode::Value."); + self.SetValue( + self.upcast::<Element>() + .get_attribute(&ns!(), &local_name!("value")) + .map_or(DOMString::from(""), |a| { + DOMString::from(a.summarize().value) + }), + ) + .expect( + "Failed to set input value on type change to ValueMode::Value.", + ); self.value_dirty.set(false); - } + }, // Step 3 - (_, _, ValueMode::Filename) if old_value_mode != ValueMode::Filename => { + (_, _, ValueMode::Filename) + if old_value_mode != ValueMode::Filename => + { self.SetValue(DOMString::from("")) .expect("Failed to set input value on type change to ValueMode::Filename."); } - _ => {} + _ => {}, } // Step 5 - if new_type == InputType::InputRadio { - self.radio_group_updated( - self.radio_group_name().as_ref()); + if new_type == InputType::Radio { + self.radio_group_updated(self.radio_group_name().as_ref()); } - // TODO: Step 6 - value sanitization + // Step 6 + let mut textinput = self.textinput.borrow_mut(); + let mut value = textinput.single_line_content().clone(); + self.sanitize_value(&mut value); + textinput.set_content(value); + + // Steps 7-9 + if !previously_selectable && self.selection_api_applies() { + textinput.clear_selection_to_limit(Direction::Backward); + } }, AttributeMutation::Removed => { - if self.input_type.get() == InputType::InputRadio { - broadcast_radio_checked( - self, - self.radio_group_name().as_ref()); + if self.input_type() == InputType::Radio { + broadcast_radio_checked(self, self.radio_group_name().as_ref()); } - self.input_type.set(InputType::InputText); + self.input_type.set(InputType::default()); let el = self.upcast::<Element>(); let read_write = !(self.ReadOnly() || el.disabled_state()); el.set_read_write_state(read_write); - } + }, } self.update_placeholder_shown_state(); }, - &local_name!("value") if !self.value_changed.get() => { + &local_name!("value") if !self.value_dirty.get() => { let value = mutation.new_value(attr).map(|value| (**value).to_owned()); - self.textinput.borrow_mut().set_content( - value.map_or(DOMString::new(), DOMString::from)); + let mut value = value.map_or(DOMString::new(), DOMString::from); + + self.sanitize_value(&mut value); + self.textinput.borrow_mut().set_content(value); self.update_placeholder_shown_state(); }, - &local_name!("name") if self.input_type.get() == InputType::InputRadio => { + &local_name!("name") if self.input_type() == InputType::Radio => { self.radio_group_updated( - mutation.new_value(attr).as_ref().map(|name| name.as_atom())); + mutation.new_value(attr).as_ref().map(|name| name.as_atom()), + ); }, - &local_name!("maxlength") => { - match *attr.value() { - AttrValue::Int(_, value) => { - if value < 0 { - self.textinput.borrow_mut().max_length = None - } else { - self.textinput.borrow_mut().max_length = Some(value as usize) - } - }, - _ => panic!("Expected an AttrValue::Int"), - } + &local_name!("maxlength") => match *attr.value() { + AttrValue::Int(_, value) => { + let mut textinput = self.textinput.borrow_mut(); + + if value < 0 { + textinput.set_max_length(None); + } else { + textinput.set_max_length(Some(UTF16CodeUnits(value as usize))) + } + }, + _ => panic!("Expected an AttrValue::Int"), }, - &local_name!("minlength") => { - match *attr.value() { - AttrValue::Int(_, value) => { - if value < 0 { - self.textinput.borrow_mut().min_length = None - } else { - self.textinput.borrow_mut().min_length = Some(value as usize) - } - }, - _ => panic!("Expected an AttrValue::Int"), - } + &local_name!("minlength") => match *attr.value() { + AttrValue::Int(_, value) => { + let mut textinput = self.textinput.borrow_mut(); + + if value < 0 { + textinput.set_min_length(None); + } else { + textinput.set_min_length(Some(UTF16CodeUnits(value as usize))) + } + }, + _ => panic!("Expected an AttrValue::Int"), }, &local_name!("placeholder") => { { let mut placeholder = self.placeholder.borrow_mut(); placeholder.clear(); if let AttributeMutation::Set(_) = mutation { - placeholder.extend( - attr.value().chars().filter(|&c| c != '\n' && c != '\r')); + placeholder + .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r')); } } self.update_placeholder_shown_state(); }, - &local_name!("readonly") if self.input_type.get() == InputType::InputText => { + &local_name!("readonly") if self.input_type().is_textual() => { let el = self.upcast::<Element>(); match mutation { AttributeMutation::Set(_) => { @@ -1044,7 +2461,7 @@ impl VirtualMethods for HTMLInputElement { }, AttributeMutation::Removed => { el.set_read_write_state(!el.disabled_state()); - } + }, } }, &local_name!("form") => { @@ -1057,21 +2474,27 @@ impl VirtualMethods for HTMLInputElement { fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match name { &local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()), - &local_name!("name") => AttrValue::from_atomic(value.into()), &local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE), &local_name!("type") => AttrValue::from_atomic(value.into()), - &local_name!("maxlength") => AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH), - &local_name!("minlength") => AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + &local_name!("maxlength") => { + AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH) + }, + &local_name!("minlength") => { + AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH) + }, + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - - self.upcast::<Element>().check_ancestors_disabled_state_for_form_control(); + self.upcast::<Element>() + .check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { @@ -1079,97 +2502,138 @@ impl VirtualMethods for HTMLInputElement { let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); - if node.ancestors().any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) { + if node + .ancestors() + .any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) + { el.check_ancestors_disabled_state_for_form_control(); } else { el.check_disabled_attribute(); } } + // This represents behavior for which the UIEvents spec and the + // DOM/HTML specs are out of sync. + // Compare: + // https://w3c.github.io/uievents/#default-action + // https://dom.spec.whatwg.org/#action-versus-occurance fn handle_event(&self, event: &Event) { if let Some(s) = self.super_type() { s.handle_event(event); } if event.type_() == atom!("click") && !event.DefaultPrevented() { - // TODO: Dispatch events for non activatable inputs - // https://html.spec.whatwg.org/multipage/#common-input-element-events + // WHATWG-specified activation behaviors are handled elsewhere; + // this is for all the other things a UI click might do //TODO: set the editing position for text inputs - document_from_node(self).request_focus(self.upcast()); - if (self.input_type.get() == InputType::InputText || - self.input_type.get() == InputType::InputPassword) && + if self.input_type().is_textual_or_password() && // Check if we display a placeholder. Layout doesn't know about this. - !self.textinput.borrow().is_empty() { - if let Some(mouse_event) = event.downcast::<MouseEvent>() { - // dispatch_key_event (document.rs) triggers a click event when releasing - // the space key. There's no nice way to catch this so let's use this for - // now. - if !(mouse_event.ScreenX() == 0 && mouse_event.ScreenY() == 0 && - mouse_event.GetRelatedTarget().is_none()) { - let window = window_from_node(self); - let translated_x = mouse_event.ClientX() + window.PageXOffset(); - let translated_y = mouse_event.ClientY() + window.PageYOffset(); - let TextIndexResponse(index) = window.text_index_query( - self.upcast::<Node>().to_trusted_node_address(), - translated_x, - translated_y - ); - if let Some(i) = index { - self.textinput.borrow_mut().set_edit_point_index(i as usize); - // trigger redraw - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); - event.PreventDefault(); - } - } - } - } - } else if event.type_() == atom!("keydown") && !event.DefaultPrevented() && - (self.input_type.get() == InputType::InputText || - self.input_type.get() == InputType::InputPassword) { - if let Some(keyevent) = event.downcast::<KeyboardEvent>() { - // This can't be inlined, as holding on to textinput.borrow_mut() - // during self.implicit_submission will cause a panic. - let action = self.textinput.borrow_mut().handle_keydown(keyevent); - match action { - TriggerDefaultAction => { - self.implicit_submission(keyevent.CtrlKey(), - keyevent.ShiftKey(), - keyevent.AltKey(), - keyevent.MetaKey()); - }, - DispatchInput => { - self.value_changed.set(true); - self.update_placeholder_shown_state(); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); - event.mark_as_handled(); - } - RedrawSelection => { + !self.textinput.borrow().is_empty() + { + if let Some(mouse_event) = event.downcast::<MouseEvent>() { + // dispatch_key_event (document.rs) triggers a click event when releasing + // the space key. There's no nice way to catch this so let's use this for + // now. + if let Some(point_in_target) = mouse_event.point_in_target() { + let window = window_from_node(self); + let TextIndexResponse(index) = + window.text_index_query(self.upcast::<Node>(), point_in_target); + if let Some(i) = index { + self.textinput.borrow_mut().set_edit_point_index(i as usize); + // trigger redraw self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); - event.mark_as_handled(); + event.PreventDefault(); } - Nothing => (), } } - } else if event.type_() == atom!("keypress") && !event.DefaultPrevented() && - (self.input_type.get() == InputType::InputText || - self.input_type.get() == InputType::InputPassword) { - if event.IsTrusted() { - let window = window_from_node(self); - let _ = window.user_interaction_task_source() - .queue_event(&self.upcast(), - atom!("input"), - EventBubbles::Bubbles, - EventCancelable::NotCancelable, - &window); + } + } else if event.type_() == atom!("keydown") && + !event.DefaultPrevented() && + self.input_type().is_textual_or_password() + { + if let Some(keyevent) = event.downcast::<KeyboardEvent>() { + // This can't be inlined, as holding on to textinput.borrow_mut() + // during self.implicit_submission will cause a panic. + let action = self.textinput.borrow_mut().handle_keydown(keyevent); + match action { + TriggerDefaultAction => { + self.implicit_submission(); + }, + DispatchInput => { + self.value_dirty.set(true); + self.update_placeholder_shown_state(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + event.mark_as_handled(); + }, + RedrawSelection => { + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + event.mark_as_handled(); + }, + Nothing => (), + } + } + } else if event.type_() == atom!("keypress") && + !event.DefaultPrevented() && + self.input_type().is_textual_or_password() + { + if event.IsTrusted() { + let window = window_from_node(self); + let _ = window + .task_manager() + .user_interaction_task_source() + .queue_event( + &self.upcast(), + atom!("input"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + &window, + ); + } + } else if (event.type_() == atom!("compositionstart") || + event.type_() == atom!("compositionupdate") || + event.type_() == atom!("compositionend")) && + self.input_type().is_textual_or_password() + { + // TODO: Update DOM on start and continue + // and generally do proper CompositionEvent handling. + if let Some(compositionevent) = event.downcast::<CompositionEvent>() { + if event.type_() == atom!("compositionend") { + let _ = self + .textinput + .borrow_mut() + .handle_compositionend(compositionevent); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } + event.mark_as_handled(); } + } + } + + // https://html.spec.whatwg.org/multipage/#the-input-element%3Aconcept-node-clone-ext + fn cloning_steps( + &self, + copy: &Node, + maybe_doc: Option<&Document>, + clone_children: CloneChildrenFlag, + ) { + if let Some(ref s) = self.super_type() { + s.cloning_steps(copy, maybe_doc, clone_children); + } + let elem = copy.downcast::<HTMLInputElement>().unwrap(); + elem.value_dirty.set(self.value_dirty.get()); + elem.checked_changed.set(self.checked_changed.get()); + elem.upcast::<Element>() + .set_state(ElementState::IN_CHECKED_STATE, self.Checked()); + elem.textinput + .borrow_mut() + .set_content(self.textinput.borrow().get_content()); } } impl FormControl for HTMLInputElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } @@ -1183,13 +2647,73 @@ impl FormControl for HTMLInputElement { } impl Validatable for HTMLInputElement { + fn as_element(&self) -> &Element { + self.upcast() + } + + fn validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), self.upcast())) + } + fn is_instance_validatable(&self) -> bool { - // https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation - true + // https://html.spec.whatwg.org/multipage/#hidden-state-(type%3Dhidden)%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#button-state-(type%3Dbutton)%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#reset-button-state-(type%3Dreset)%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#the-readonly-attribute%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation + match self.input_type() { + InputType::Hidden | InputType::Button | InputType::Reset => false, + _ => { + !(self.upcast::<Element>().disabled_state() || + (self.ReadOnly() && self.does_readonly_apply()) || + is_barred_by_datalist_ancestor(self.upcast())) + }, + } } - fn validate(&self, _validate_flags: ValidationFlags) -> bool { - // call stub methods defined in validityState.rs file here according to the flags set in validate_flags - true + + fn perform_validation(&self, validate_flags: ValidationFlags) -> ValidationFlags { + let mut failed_flags = ValidationFlags::empty(); + let value = self.Value(); + + if validate_flags.contains(ValidationFlags::VALUE_MISSING) { + if self.suffers_from_being_missing(&value) { + failed_flags.insert(ValidationFlags::VALUE_MISSING); + } + } + + if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) { + if self.suffers_from_type_mismatch(&value) { + failed_flags.insert(ValidationFlags::TYPE_MISMATCH); + } + } + + if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) { + if self.suffers_from_pattern_mismatch(&value) { + failed_flags.insert(ValidationFlags::PATTERN_MISMATCH); + } + } + + if validate_flags.contains(ValidationFlags::BAD_INPUT) { + if self.suffers_from_bad_input(&value) { + failed_flags.insert(ValidationFlags::BAD_INPUT); + } + } + + if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) { + failed_flags |= self.suffers_from_length_issues(&value); + } + + if validate_flags.intersects( + ValidationFlags::RANGE_UNDERFLOW | + ValidationFlags::RANGE_OVERFLOW | + ValidationFlags::STEP_MISMATCH, + ) { + failed_flags |= self.suffers_from_range_issues(&value); + } + + failed_flags & validate_flags } } @@ -1199,202 +2723,134 @@ impl Activatable for HTMLInputElement { } fn is_instance_activatable(&self) -> bool { - match self.input_type.get() { + match self.input_type() { // https://html.spec.whatwg.org/multipage/#submit-button-state-%28type=submit%29:activation-behaviour-2 // https://html.spec.whatwg.org/multipage/#reset-button-state-%28type=reset%29:activation-behaviour-2 // https://html.spec.whatwg.org/multipage/#checkbox-state-%28type=checkbox%29:activation-behaviour-2 // https://html.spec.whatwg.org/multipage/#radio-button-state-%28type=radio%29:activation-behaviour-2 - InputType::InputSubmit | InputType::InputReset | InputType::InputFile - | InputType::InputCheckbox | InputType::InputRadio => self.is_mutable(), - _ => false + InputType::Submit | InputType::Reset | InputType::File => self.is_mutable(), + InputType::Checkbox | InputType::Radio => true, + _ => false, } } - // https://html.spec.whatwg.org/multipage/#run-pre-click-activation-steps - #[allow(unsafe_code)] - fn pre_click_activation(&self) { - let mut cache = self.activation_state.borrow_mut(); - let ty = self.input_type.get(); - cache.old_type = ty; - cache.was_mutable = self.is_mutable(); - if cache.was_mutable { - match ty { - // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit):activation-behavior - // InputType::InputSubmit => (), // No behavior defined - // https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset):activation-behavior - // InputType::InputSubmit => (), // No behavior defined - InputType::InputCheckbox => { - /* - https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):pre-click-activation-steps - cache current values of `checked` and `indeterminate` - we may need to restore them later - */ - cache.indeterminate = self.Indeterminate(); - cache.checked = self.Checked(); - cache.checked_changed = self.checked_changed.get(); - self.SetIndeterminate(false); - self.SetChecked(!cache.checked); - }, - // https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):pre-click-activation-steps - InputType::InputRadio => { - //TODO: if not in document, use root ancestor instead of document - let owner = self.form_owner(); - let doc = document_from_node(self); - let doc_node = doc.upcast::<Node>(); - let group = self.radio_group_name();; - - // Safe since we only manipulate the DOM tree after finding an element - let checked_member = doc_node.query_selector_iter(DOMString::from("input[type=radio]")) - .unwrap() - .filter_map(Root::downcast::<HTMLInputElement>) - .find(|r| { - in_same_group(&*r, owner.r(), group.as_ref()) && - r.Checked() - }); - cache.checked_radio = checked_member.r().map(JS::from_ref); - cache.checked_changed = self.checked_changed.get(); - self.SetChecked(true); - } - _ => () - } + // https://dom.spec.whatwg.org/#eventtarget-legacy-pre-activation-behavior + fn legacy_pre_activation_behavior(&self) -> Option<InputActivationState> { + let ty = self.input_type(); + match ty { + InputType::Checkbox => { + let was_checked = self.Checked(); + let was_indeterminate = self.Indeterminate(); + self.SetIndeterminate(false); + self.SetChecked(!was_checked); + return Some(InputActivationState { + checked: was_checked, + indeterminate: was_indeterminate, + checked_radio: None, + old_type: InputType::Checkbox, + }); + }, + InputType::Radio => { + let checked_member = + radio_group_iter(self, self.radio_group_name().as_ref()).find(|r| r.Checked()); + let was_checked = self.Checked(); + self.SetChecked(true); + return Some(InputActivationState { + checked: was_checked, + indeterminate: false, + checked_radio: checked_member.as_deref().map(DomRoot::from_ref), + old_type: InputType::Radio, + }); + }, + _ => (), } + return None; } - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self) { - let cache = self.activation_state.borrow(); - let ty = self.input_type.get(); - if cache.old_type != ty { - // Type changed, abandon ship - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27414 - return; - } - match ty { - // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit):activation-behavior - // InputType::InputSubmit => (), // No behavior defined - // https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset):activation-behavior - // InputType::InputReset => (), // No behavior defined - // https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):canceled-activation-steps - InputType::InputCheckbox => { - // We want to restore state only if the element had been changed in the first place - if cache.was_mutable { - self.SetIndeterminate(cache.indeterminate); - self.SetChecked(cache.checked); - self.checked_changed.set(cache.checked_changed); + // https://dom.spec.whatwg.org/#eventtarget-legacy-canceled-activation-behavior + fn legacy_canceled_activation_behavior(&self, cache: Option<InputActivationState>) { + // Step 1 + let ty = self.input_type(); + let cache = match cache { + Some(cache) => { + if cache.old_type != ty { + // Type changed, abandon ship + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27414 + return; } + cache }, - // https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):canceled-activation-steps - InputType::InputRadio => { - // We want to restore state only if the element had been changed in the first place - if cache.was_mutable { - match cache.checked_radio.r() { - Some(o) => { - // Avoiding iterating through the whole tree here, instead - // we can check if the conditions for radio group siblings apply - if in_same_group(&o, self.form_owner().r(), self.radio_group_name().as_ref()) { - o.SetChecked(true); - } else { - self.SetChecked(false); - } - }, - None => self.SetChecked(false) - }; - self.checked_changed.set(cache.checked_changed); + None => { + return; + }, + }; + + match ty { + // Step 2 + InputType::Checkbox => { + self.SetIndeterminate(cache.indeterminate); + self.SetChecked(cache.checked); + }, + // Step 3 + InputType::Radio => { + if let Some(ref o) = cache.checked_radio { + let tree_root = self + .upcast::<Node>() + .GetRootNode(&GetRootNodeOptions::empty()); + // Avoiding iterating through the whole tree here, instead + // we can check if the conditions for radio group siblings apply + if in_same_group( + &o, + self.form_owner().as_deref(), + self.radio_group_name().as_ref(), + Some(&*tree_root), + ) { + o.SetChecked(true); + } else { + self.SetChecked(false); + } + } else { + self.SetChecked(false); } - } - _ => () + }, + _ => (), } } // https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps fn activation_behavior(&self, _event: &Event, _target: &EventTarget) { - let ty = self.input_type.get(); - if self.activation_state.borrow().old_type != ty || !self.is_mutable() { - // Type changed or input is immutable, abandon ship - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27414 - return; - } + let ty = self.input_type(); match ty { - InputType::InputSubmit => { + InputType::Submit => { // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit):activation-behavior // FIXME (Manishearth): support document owners (needs ability to get parent browsing context) // Check if document owner is fully active self.form_owner().map(|o| { - o.submit(SubmittedFrom::NotFromForm, - FormSubmitter::InputElement(self.clone())) + o.submit( + SubmittedFrom::NotFromForm, + FormSubmitter::InputElement(self), + ) }); }, - InputType::InputReset => { + InputType::Reset => { // https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset):activation-behavior // FIXME (Manishearth): support document owners (needs ability to get parent browsing context) // Check if document owner is fully active - self.form_owner().map(|o| { - o.reset(ResetFrom::NotFromForm) - }); + self.form_owner().map(|o| o.reset(ResetFrom::NotFromForm)); }, - InputType::InputCheckbox | InputType::InputRadio => { + InputType::Checkbox | InputType::Radio => { // https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):activation-behavior // https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):activation-behavior // Check if document owner is fully active + if !self.upcast::<Node>().is_connected() { + return (); + } let target = self.upcast::<EventTarget>(); target.fire_bubbling_event(atom!("input")); target.fire_bubbling_event(atom!("change")); }, - InputType::InputFile => self.select_files(None), - _ => () - } - } - - // https://html.spec.whatwg.org/multipage/#implicit-submission - #[allow(unsafe_code)] - fn implicit_submission(&self, ctrl_key: bool, shift_key: bool, alt_key: bool, meta_key: bool) { - let doc = document_from_node(self); - let node = doc.upcast::<Node>(); - let owner = self.form_owner(); - let form = match owner { - None => return, - Some(ref f) => f - }; - - if self.upcast::<Element>().click_in_progress() { - return; - } - let submit_button; - submit_button = node.query_selector_iter(DOMString::from("input[type=submit]")).unwrap() - .filter_map(Root::downcast::<HTMLInputElement>) - .find(|r| r.form_owner() == owner); - match submit_button { - Some(ref button) => { - if button.is_instance_activatable() { - synthetic_click_activation(button.as_element(), - ctrl_key, - shift_key, - alt_key, - meta_key, - ActivationSource::NotFromClick) - } - } - None => { - let inputs = node.query_selector_iter(DOMString::from("input")).unwrap() - .filter_map(Root::downcast::<HTMLInputElement>) - .filter(|input| { - input.form_owner() == owner && match input.type_() { - atom!("text") | atom!("search") | atom!("url") | atom!("tel") | - atom!("email") | atom!("password") | atom!("datetime") | - atom!("date") | atom!("month") | atom!("week") | atom!("time") | - atom!("datetime-local") | atom!("number") - => true, - _ => false - } - }); - - if inputs.skip(1).next().is_some() { - // lazily test for > 1 submission-blocking inputs - return; - } - form.submit(SubmittedFrom::NotFromForm, - FormSubmitter::FormElement(&form)); - } + InputType::File => self.select_files(None), + _ => (), } } } @@ -1416,3 +2872,86 @@ fn filter_from_accept(s: &DOMString) -> Vec<FilterPattern> { filter } + +fn round_halves_positive(n: f64) -> f64 { + // WHATWG specs about input steps say to round to the nearest step, + // rounding halves always to positive infinity. + // This differs from Rust's .round() in the case of -X.5. + if n.fract() == -0.5 { + n.ceil() + } else { + n.round() + } +} + +fn milliseconds_to_datetime(value: f64) -> Result<NaiveDateTime, ()> { + let seconds = (value / 1000.0).floor(); + let milliseconds = value - (seconds * 1000.0); + let nanoseconds = milliseconds * 1e6; + NaiveDateTime::from_timestamp_opt(seconds as i64, nanoseconds as u32).ok_or(()) +} + +// This is used to compile JS-compatible regex provided in pattern attribute +// that matches only the entirety of string. +// https://html.spec.whatwg.org/multipage/#compiled-pattern-regular-expression +fn compile_pattern(cx: SafeJSContext, pattern_str: &str, out_regex: MutableHandleObject) -> bool { + // First check if pattern compiles... + if new_js_regex(cx, pattern_str, out_regex) { + // ...and if it does make pattern that matches only the entirety of string + let pattern_str = format!("^(?:{})$", pattern_str); + new_js_regex(cx, &pattern_str, out_regex) + } else { + false + } +} + +#[allow(unsafe_code)] +fn new_js_regex(cx: SafeJSContext, pattern: &str, mut out_regex: MutableHandleObject) -> bool { + let pattern: Vec<u16> = pattern.encode_utf16().collect(); + unsafe { + out_regex.set(NewUCRegExpObject( + *cx, + pattern.as_ptr(), + pattern.len(), + RegExpFlags { + flags_: RegExpFlag_Unicode, + }, + )); + if out_regex.is_null() { + JS_ClearPendingException(*cx); + return false; + } + } + true +} + +#[allow(unsafe_code)] +fn matches_js_regex(cx: SafeJSContext, regex_obj: HandleObject, value: &str) -> Result<bool, ()> { + let mut value: Vec<u16> = value.encode_utf16().collect(); + + unsafe { + let mut is_regex = false; + assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex)); + assert!(is_regex); + + rooted!(in(*cx) let mut rval = UndefinedValue()); + let mut index = 0; + + let ok = ExecuteRegExpNoStatics( + *cx, + regex_obj, + value.as_mut_ptr(), + value.len(), + &mut index, + true, + &mut rval.handle_mut(), + ); + + if ok { + Ok(!rval.is_null()) + } else { + JS_ClearPendingException(*cx); + Err(()) + } + } +} diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index 73a62af5570..970f4a97b7a 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -1,48 +1,57 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::activation::{Activatable, ActivationSource, synthetic_click_activation}; -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::HTMLLabelElementBinding; -use dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::htmlelement::HTMLElement; -use dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement}; -use dom::node::{document_from_node, Node}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::activation::Activatable; +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; +use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement}; +use crate::dom::node::{Node, ShadowIncluding}; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use style::attr::AttrValue; #[dom_struct] pub struct HTMLLabelElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLLabelElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLLabelElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLLabelElement { HTMLLabelElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLLabelElement> { - Node::reflect_node(box HTMLLabelElement::new_inherited(local_name, prefix, document), - document, - HTMLLabelElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLLabelElement> { + Node::reflect_node( + Box::new(HTMLLabelElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } @@ -55,40 +64,21 @@ impl Activatable for HTMLLabelElement { true } - // https://html.spec.whatwg.org/multipage/#run-pre-click-activation-steps - // https://html.spec.whatwg.org/multipage/#the-button-element:activation-behavior - fn pre_click_activation(&self) { - } - - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self) { - } - - // https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps + // https://html.spec.whatwg.org/multipage/#the-label-element:activation_behaviour + // Basically this is telling us that if activation bubbles up to the label + // at all, we are free to do an implementation-dependent thing; + // firing a click event is an example, and the precise details of that + // click event (e.g. isTrusted) are not specified. fn activation_behavior(&self, _event: &Event, _target: &EventTarget) { if let Some(e) = self.GetControl() { - let elem = e.upcast::<Element>(); - synthetic_click_activation(elem, - false, - false, - false, - false, - ActivationSource::NotFromClick); + e.Click(); } } - - // https://html.spec.whatwg.org/multipage/#implicit-submission - fn implicit_submission(&self, _ctrl_key: bool, _shift_key: bool, _alt_key: bool, _meta_key: bool) { - //FIXME: Investigate and implement implicit submission for label elements - // Issue filed at https://github.com/servo/servo/issues/8263 - } - - } impl HTMLLabelElementMethods for HTMLLabelElement { // https://html.spec.whatwg.org/multipage/#dom-fae-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner() } @@ -99,34 +89,64 @@ impl HTMLLabelElementMethods for HTMLLabelElement { make_atomic_setter!(SetHtmlFor, "for"); // https://html.spec.whatwg.org/multipage/#dom-label-control - fn GetControl(&self) -> Option<Root<HTMLElement>> { - if !self.upcast::<Node>().is_in_doc() { - return None; - } - - let for_attr = match self.upcast::<Element>().get_attribute(&ns!(), &local_name!("for")) { + fn GetControl(&self) -> Option<DomRoot<HTMLElement>> { + let for_attr = match self + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("for")) + { Some(for_attr) => for_attr, None => return self.first_labelable_descendant(), }; - let for_value = for_attr.value(); - document_from_node(self).get_element_by_id(for_value.as_atom()) - .and_then(Root::downcast::<HTMLElement>) - .into_iter() - .filter(|e| e.is_labelable_element()) - .next() + let for_value = for_attr.Value(); + + // "If the attribute is specified and there is an element in the tree + // whose ID is equal to the value of the for attribute, and the first + // such element in tree order is a labelable element, then that + // element is the label element's labeled control." + // Two subtle points here: we need to search the _tree_, which is + // not necessarily the document if we're detached from the document, + // and we only consider one element even if a later element with + // the same ID is labelable. + + let maybe_found = self + .upcast::<Node>() + .GetRootNode(&GetRootNodeOptions::empty()) + .traverse_preorder(ShadowIncluding::No) + .find_map(|e| { + if let Some(htmle) = e.downcast::<HTMLElement>() { + if htmle.upcast::<Element>().Id() == for_value { + Some(DomRoot::from_ref(htmle)) + } else { + None + } + } else { + None + } + }); + // We now have the element that we would return, but only return it + // if it's labelable. + if let Some(ref maybe_labelable) = maybe_found { + if maybe_labelable.is_labelable_element() { + return maybe_found; + } + } + None } } impl VirtualMethods for HTMLLabelElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match name { &local_name!("for") => AttrValue::from_atomic(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } @@ -142,20 +162,23 @@ impl VirtualMethods for HTMLLabelElement { } impl HTMLLabelElement { - pub fn first_labelable_descendant(&self) -> Option<Root<HTMLElement>> { + pub fn first_labelable_descendant(&self) -> Option<DomRoot<HTMLElement>> { self.upcast::<Node>() - .traverse_preorder() - .filter_map(Root::downcast::<HTMLElement>) + .traverse_preorder(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<HTMLElement>) .filter(|elem| elem.is_labelable_element()) .next() } } impl FormControl for HTMLLabelElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { - self.GetControl().map(Root::upcast::<Element>).and_then(|elem| { - elem.as_maybe_form_control().and_then(|control| control.form_owner()) - }) + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { + self.GetControl() + .map(DomRoot::upcast::<Element>) + .and_then(|elem| { + elem.as_maybe_form_control() + .and_then(|control| control.form_owner()) + }) } fn set_form_owner(&self, _: Option<&HTMLFormElement>) { diff --git a/components/script/dom/htmllegendelement.rs b/components/script/dom/htmllegendelement.rs index 86b34c27dd0..bfb0f0701c8 100644 --- a/components/script/dom/htmllegendelement.rs +++ b/components/script/dom/htmllegendelement.rs @@ -1,34 +1,33 @@ // 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 http://mozilla.org/MPL/2.0/. +// file, You can obtain one at https://mozilla.org/MPL/2.0/. -use dom::bindings::codegen::Bindings::HTMLLegendElementBinding; -use dom::bindings::codegen::Bindings::HTMLLegendElementBinding::HTMLLegendElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::Element; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlformelement::{HTMLFormElement, FormControl}; -use dom::node::{Node, UnbindContext}; -use dom::virtualmethods::VirtualMethods; +use crate::dom::bindings::codegen::Bindings::HTMLLegendElementBinding::HTMLLegendElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; +use crate::dom::node::{BindContext, Node, UnbindContext}; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLLegendElement { htmlelement: HTMLElement, - form_owner: MutNullableJS<HTMLFormElement>, + form_owner: MutNullableDom<HTMLFormElement>, } impl HTMLLegendElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) - -> HTMLLegendElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLLegendElement { HTMLLegendElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), form_owner: Default::default(), @@ -36,27 +35,32 @@ impl HTMLLegendElement { } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) - -> Root<HTMLLegendElement> { - Node::reflect_node(box HTMLLegendElement::new_inherited(local_name, prefix, document), - document, - HTMLLegendElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLLegendElement> { + Node::reflect_node( + Box::new(HTMLLegendElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } impl VirtualMethods for HTMLLegendElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - self.upcast::<Element>().check_ancestors_disabled_state_for_form_control(); + self.upcast::<Element>() + .check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { @@ -64,7 +68,10 @@ impl VirtualMethods for HTMLLegendElement { let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); - if node.ancestors().any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) { + if node + .ancestors() + .any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) + { el.check_ancestors_disabled_state_for_form_control(); } else { el.check_disabled_attribute(); @@ -72,14 +79,10 @@ impl VirtualMethods for HTMLLegendElement { } } - impl HTMLLegendElementMethods for HTMLLegendElement { // https://html.spec.whatwg.org/multipage/#dom-legend-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { - let parent = match self.upcast::<Node>().GetParentElement() { - Some(parent) => parent, - None => return None, - }; + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { + let parent = self.upcast::<Node>().GetParentElement()?; if parent.is::<HTMLFieldSetElement>() { return self.form_owner(); } @@ -88,7 +91,7 @@ impl HTMLLegendElementMethods for HTMLLegendElement { } impl FormControl for HTMLLegendElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } diff --git a/components/script/dom/htmllielement.rs b/components/script/dom/htmllielement.rs index aef80a52311..2697f6643e8 100644 --- a/components/script/dom/htmllielement.rs +++ b/components/script/dom/htmllielement.rs @@ -1,18 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HTMLLIElementBinding; -use dom::bindings::codegen::Bindings::HTMLLIElementBinding::HTMLLIElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::HTMLLIElementBinding::HTMLLIElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use style::attr::AttrValue; #[dom_struct] @@ -21,19 +20,26 @@ pub struct HTMLLIElement { } impl HTMLLIElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLLIElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLLIElement { HTMLLIElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLLIElement> { - Node::reflect_node(box HTMLLIElement::new_inherited(local_name, prefix, document), - document, - HTMLLIElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLLIElement> { + Node::reflect_node( + Box::new(HTMLLIElement::new_inherited(local_name, prefix, document)), + document, + ) } } @@ -46,14 +52,17 @@ impl HTMLLIElementMethods for HTMLLIElement { } impl VirtualMethods for HTMLLIElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match name { &local_name!("value") => AttrValue::from_i32(value.into(), 0), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } } diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index c0d0d12a775..30a45471203 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -1,46 +1,48 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use cssparser::Parser as CssParser; -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListBinding::DOMTokenListMethods; -use dom::bindings::codegen::Bindings::HTMLLinkElementBinding; -use dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root, RootedReference}; -use dom::bindings::str::DOMString; -use dom::cssstylesheet::CSSStyleSheet; -use dom::document::Document; -use dom::domtokenlist::DOMTokenList; -use dom::element::{AttributeMutation, Element, ElementCreator}; -use dom::element::{cors_setting_for_element, reflect_cross_origin_attribute, set_cross_origin_attribute}; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, UnbindContext, document_from_node, window_from_node}; -use dom::stylesheet::StyleSheet as DOMStyleSheet; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListBinding::DOMTokenListMethods; +use crate::dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::document::Document; +use crate::dom::domtokenlist::DOMTokenList; +use crate::dom::element::{ + cors_setting_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute, + set_cross_origin_attribute, +}; +use crate::dom::element::{AttributeMutation, Element, ElementCreator}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{ + document_from_node, stylesheets_owner_from_node, window_from_node, BindContext, Node, + UnbindContext, +}; +use crate::dom::stylesheet::StyleSheet as DOMStyleSheet; +use crate::dom::virtualmethods::VirtualMethods; +use crate::stylesheet_loader::{StylesheetContextSource, StylesheetLoader, StylesheetOwner}; +use cssparser::{Parser as CssParser, ParserInput}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use embedder_traits::EmbedderMsg; +use html5ever::{LocalName, Prefix}; use net_traits::ReferrerPolicy; -use script_layout_interface::message::Msg; -use script_traits::{MozBrowserEvent, ScriptMsg as ConstellationMsg}; -use std::ascii::AsciiExt; +use servo_arc::Arc; +use servo_atoms::Atom; use std::borrow::ToOwned; use std::cell::Cell; use std::default::Default; -use std::sync::Arc; use style::attr::AttrValue; -use style::media_queries::parse_media_query_list; -use style::parser::{LengthParsingMode, ParserContext as CssParserContext}; +use style::media_queries::MediaList; +use style::parser::ParserContext as CssParserContext; use style::str::HTML_SPACE_CHARACTERS; -use style::stylesheets::{CssRuleType, Stylesheet}; -use stylesheet_loader::{StylesheetLoader, StylesheetContextSource, StylesheetOwner}; +use style::stylesheets::{CssRuleType, Origin, Stylesheet}; +use style_traits::ParsingMode; -unsafe_no_jsmanaged_fields!(Stylesheet); - -#[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub struct RequestGenerationId(u32); impl RequestGenerationId { @@ -52,12 +54,12 @@ impl RequestGenerationId { #[dom_struct] pub struct HTMLLinkElement { htmlelement: HTMLElement, - rel_list: MutNullableJS<DOMTokenList>, - #[ignore_heap_size_of = "Arc"] - stylesheet: DOMRefCell<Option<Arc<Stylesheet>>>, - cssom_stylesheet: MutNullableJS<CSSStyleSheet>, + rel_list: MutNullableDom<DOMTokenList>, + #[ignore_malloc_size_of = "Arc"] + stylesheet: DomRefCell<Option<Arc<Stylesheet>>>, + cssom_stylesheet: MutNullableDom<CSSStyleSheet>, - /// https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts + /// <https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts> parser_inserted: Cell<bool>, /// The number of loads that this link element has triggered (could be more /// than one because of imports) and have not yet finished. @@ -69,14 +71,18 @@ pub struct HTMLLinkElement { } impl HTMLLinkElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document, - creator: ElementCreator) -> HTMLLinkElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + creator: ElementCreator, + ) -> HTMLLinkElement { HTMLLinkElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), rel_list: Default::default(), parser_inserted: Cell::new(creator.is_parser_created()), - stylesheet: DOMRefCell::new(None), - cssom_stylesheet: MutNullableJS::new(None), + stylesheet: DomRefCell::new(None), + cssom_stylesheet: MutNullableDom::new(None), pending_loads: Cell::new(0), any_failed_load: Cell::new(false), request_generation_id: Cell::new(RequestGenerationId(0)), @@ -84,38 +90,52 @@ impl HTMLLinkElement { } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document, - creator: ElementCreator) -> Root<HTMLLinkElement> { - Node::reflect_node(box HTMLLinkElement::new_inherited(local_name, prefix, document, creator), - document, - HTMLLinkElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + creator: ElementCreator, + ) -> DomRoot<HTMLLinkElement> { + Node::reflect_node( + Box::new(HTMLLinkElement::new_inherited( + local_name, prefix, document, creator, + )), + document, + ) } pub fn get_request_generation_id(&self) -> RequestGenerationId { self.request_generation_id.get() } + // FIXME(emilio): These methods are duplicated with + // HTMLStyleElement::set_stylesheet. + #[allow(unrooted_must_root)] pub fn set_stylesheet(&self, s: Arc<Stylesheet>) { + let stylesheets_owner = stylesheets_owner_from_node(self); + if let Some(ref s) = *self.stylesheet.borrow() { + stylesheets_owner.remove_stylesheet(self.upcast(), s) + } *self.stylesheet.borrow_mut() = Some(s.clone()); - window_from_node(self).layout_chan().send(Msg::AddStylesheet(s)).unwrap(); - document_from_node(self).invalidate_stylesheets(); + self.clean_stylesheet_ownership(); + stylesheets_owner.add_stylesheet(self.upcast(), s); } pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> { self.stylesheet.borrow().clone() } - pub fn get_cssom_stylesheet(&self) -> Option<Root<CSSStyleSheet>> { + pub fn get_cssom_stylesheet(&self) -> Option<DomRoot<CSSStyleSheet>> { self.get_stylesheet().map(|sheet| { self.cssom_stylesheet.or_init(|| { - CSSStyleSheet::new(&window_from_node(self), - self.upcast::<Element>(), - "text/css".into(), - None, // todo handle location - None, // todo handle title - sheet) + CSSStyleSheet::new( + &window_from_node(self), + self.upcast::<Element>(), + "text/css".into(), + None, // todo handle location + None, // todo handle title + sheet, + ) }) }) } @@ -123,13 +143,19 @@ impl HTMLLinkElement { pub fn is_alternate(&self) -> bool { let rel = get_attr(self.upcast(), &local_name!("rel")); match rel { - Some(ref value) => { - value.split(HTML_SPACE_CHARACTERS) - .any(|s| s.eq_ignore_ascii_case("alternate")) - }, + Some(ref value) => value + .split(HTML_SPACE_CHARACTERS) + .any(|s| s.eq_ignore_ascii_case("alternate")), None => false, } } + + fn clean_stylesheet_ownership(&self) { + if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() { + cssom_stylesheet.set_owner(None); + } + self.cssom_stylesheet.set(None); + } } fn get_attr(element: &Element, local_name: &LocalName) -> Option<String> { @@ -142,35 +168,33 @@ fn get_attr(element: &Element, local_name: &LocalName) -> Option<String> { fn string_is_stylesheet(value: &Option<String>) -> bool { match *value { - Some(ref value) => { - value.split(HTML_SPACE_CHARACTERS) - .any(|s| s.eq_ignore_ascii_case("stylesheet")) - }, + Some(ref value) => value + .split(HTML_SPACE_CHARACTERS) + .any(|s| s.eq_ignore_ascii_case("stylesheet")), None => false, } } /// Favicon spec usage in accordance with CEF implementation: /// only url of icon is required/used -/// https://html.spec.whatwg.org/multipage/#rel-icon +/// <https://html.spec.whatwg.org/multipage/#rel-icon> fn is_favicon(value: &Option<String>) -> bool { match *value { - Some(ref value) => { - value.split(HTML_SPACE_CHARACTERS) - .any(|s| s.eq_ignore_ascii_case("icon") || s.eq_ignore_ascii_case("apple-touch-icon")) - }, + Some(ref value) => value + .split(HTML_SPACE_CHARACTERS) + .any(|s| s.eq_ignore_ascii_case("icon") || s.eq_ignore_ascii_case("apple-touch-icon")), None => false, } } impl VirtualMethods for HTMLLinkElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); - if !self.upcast::<Node>().is_in_doc() || mutation.is_removal() { + if !self.upcast::<Node>().is_connected() || mutation.is_removal() { return; } @@ -187,14 +211,11 @@ impl VirtualMethods for HTMLLinkElement { &local_name!("sizes") => { if is_favicon(&rel) { if let Some(ref href) = get_attr(self.upcast(), &local_name!("href")) { - self.handle_favicon_url(rel.as_ref().unwrap(), href, &Some(attr.value().to_string())); - } - } - }, - &local_name!("media") => { - if string_is_stylesheet(&rel) { - if let Some(href) = self.upcast::<Element>().get_attribute(&ns!(), &local_name!("href")) { - self.handle_stylesheet_url(&href.value()); + self.handle_favicon_url( + rel.as_ref().unwrap(), + href, + &Some(attr.value().to_string()), + ); } } }, @@ -205,16 +226,19 @@ impl VirtualMethods for HTMLLinkElement { fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match name { &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - if tree_in_doc { + if context.tree_connected { let element = self.upcast(); let rel = get_attr(element, &local_name!("rel")); @@ -224,11 +248,11 @@ impl VirtualMethods for HTMLLinkElement { match href { Some(ref href) if string_is_stylesheet(&rel) => { self.handle_stylesheet_url(href); - } + }, Some(ref href) if is_favicon(&rel) => { self.handle_favicon_url(rel.as_ref().unwrap(), href, &sizes); - } - _ => {} + }, + _ => {}, } } } @@ -238,14 +262,15 @@ impl VirtualMethods for HTMLLinkElement { s.unbind_from_tree(context); } - let document = document_from_node(self); - document.invalidate_stylesheets(); + if let Some(s) = self.stylesheet.borrow_mut().take() { + self.clean_stylesheet_ownership(); + stylesheets_owner_from_node(self).remove_stylesheet(self.upcast(), &s); + } } } - impl HTMLLinkElement { - /// https://html.spec.whatwg.org/multipage/#concept-link-obtain + /// <https://html.spec.whatwg.org/multipage/#concept-link-obtain> fn handle_stylesheet_url(&self, href: &str) { let document = document_from_node(self); if document.browsing_context().is_none() { @@ -263,7 +288,7 @@ impl HTMLLinkElement { Err(e) => { debug!("Parsing url {} failed: {}", href, e); return; - } + }, }; let element = self.upcast::<Element>(); @@ -272,50 +297,62 @@ impl HTMLLinkElement { let cors_setting = cors_setting_for_element(element); let mq_attribute = element.get_attribute(&ns!(), &local_name!("media")); - let value = mq_attribute.r().map(|a| a.value()); + let value = mq_attribute.as_ref().map(|a| a.value()); let mq_str = match value { Some(ref value) => &***value, None => "", }; - let mut css_parser = CssParser::new(&mq_str); - let win = document.window(); + let mut input = ParserInput::new(&mq_str); + let mut css_parser = CssParser::new(&mut input); let doc_url = document.url(); - let context = CssParserContext::new_for_cssom(&doc_url, win.css_error_reporter(), Some(CssRuleType::Media), - LengthParsingMode::Default); - let media = parse_media_query_list(&context, &mut css_parser); + let window = document.window(); + // FIXME(emilio): This looks somewhat fishy, since we use the context + // only to parse the media query list, CssRuleType::Media doesn't make + // much sense. + let context = CssParserContext::new( + Origin::Author, + &doc_url, + Some(CssRuleType::Media), + ParsingMode::DEFAULT, + document.quirks_mode(), + window.css_error_reporter(), + None, + ); + let media = MediaList::parse(&context, &mut css_parser); let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity")); - let integrity_val = im_attribute.r().map(|a| a.value()); + let integrity_val = im_attribute.as_ref().map(|a| a.value()); let integrity_metadata = match integrity_val { Some(ref value) => &***value, None => "", }; - self.request_generation_id.set(self.request_generation_id.get().increment()); + self.request_generation_id + .set(self.request_generation_id.get().increment()); // TODO: #8085 - Don't load external stylesheets if the node's mq // doesn't match. let loader = StylesheetLoader::for_element(self.upcast()); - loader.load(StylesheetContextSource::LinkElement { - media: Some(media), - }, link_url, cors_setting, integrity_metadata.to_owned()); + loader.load( + StylesheetContextSource::LinkElement { media: Some(media) }, + link_url, + cors_setting, + integrity_metadata.to_owned(), + ); } - fn handle_favicon_url(&self, rel: &str, href: &str, sizes: &Option<String>) { + fn handle_favicon_url(&self, _rel: &str, href: &str, _sizes: &Option<String>) { let document = document_from_node(self); match document.base_url().join(href) { Ok(url) => { - let event = ConstellationMsg::NewFavicon(url.clone()); - document.window().upcast::<GlobalScope>().constellation_chan().send(event).unwrap(); - - let mozbrowser_event = match *sizes { - Some(ref sizes) => MozBrowserEvent::IconChange(rel.to_owned(), url.to_string(), sizes.to_owned()), - None => MozBrowserEvent::IconChange(rel.to_owned(), url.to_string(), "".to_owned()) - }; - document.trigger_mozbrowser_event(mozbrowser_event); - } - Err(e) => debug!("Parsing url {} failed: {}", href, e) + let window = document.window(); + if window.is_top_level() { + let msg = EmbedderMsg::NewFavicon(url.clone()); + window.send_to_embedder(msg); + } + }, + Err(e) => debug!("Parsing url {} failed: {}", href, e), } } } @@ -347,7 +384,7 @@ impl StylesheetOwner for HTMLLinkElement { fn referrer_policy(&self) -> Option<ReferrerPolicy> { if self.RelList().Contains("noreferrer".into()) { - return Some(ReferrerPolicy::NoReferrer) + return Some(ReferrerPolicy::NoReferrer); } None @@ -365,14 +402,15 @@ impl HTMLLinkElementMethods for HTMLLinkElement { make_url_getter!(Href, "href"); // https://html.spec.whatwg.org/multipage/#dom-link-href - make_setter!(SetHref, "href"); + make_url_setter!(SetHref, "href"); // https://html.spec.whatwg.org/multipage/#dom-link-rel make_getter!(Rel, "rel"); // https://html.spec.whatwg.org/multipage/#dom-link-rel fn SetRel(&self, rel: DOMString) { - self.upcast::<Element>().set_tokenlist_attribute(&local_name!("rel"), rel); + self.upcast::<Element>() + .set_tokenlist_attribute(&local_name!("rel"), rel); } // https://html.spec.whatwg.org/multipage/#dom-link-media @@ -400,8 +438,30 @@ impl HTMLLinkElementMethods for HTMLLinkElement { make_setter!(SetType, "type"); // https://html.spec.whatwg.org/multipage/#dom-link-rellist - fn RelList(&self) -> Root<DOMTokenList> { - self.rel_list.or_init(|| DOMTokenList::new(self.upcast(), &local_name!("rel"))) + fn RelList(&self) -> DomRoot<DOMTokenList> { + self.rel_list.or_init(|| { + DOMTokenList::new( + self.upcast(), + &local_name!("rel"), + Some(vec![ + Atom::from("alternate"), + Atom::from("apple-touch-icon"), + Atom::from("apple-touch-icon-precomposed"), + Atom::from("canonical"), + Atom::from("dns-prefetch"), + Atom::from("icon"), + Atom::from("import"), + Atom::from("manifest"), + Atom::from("modulepreload"), + Atom::from("next"), + Atom::from("preconnect"), + Atom::from("prefetch"), + Atom::from("preload"), + Atom::from("prerender"), + Atom::from("stylesheet"), + ]), + ) + }) } // https://html.spec.whatwg.org/multipage/#dom-link-charset @@ -432,8 +492,16 @@ impl HTMLLinkElementMethods for HTMLLinkElement { set_cross_origin_attribute(self.upcast::<Element>(), value); } + // https://html.spec.whatwg.org/multipage/#dom-link-referrerpolicy + fn ReferrerPolicy(&self) -> DOMString { + reflect_referrer_policy_attribute(self.upcast::<Element>()) + } + + // https://html.spec.whatwg.org/multipage/#dom-link-referrerpolicy + make_setter!(SetReferrerPolicy, "referrerpolicy"); + // https://drafts.csswg.org/cssom/#dom-linkstyle-sheet - fn GetSheet(&self) -> Option<Root<DOMStyleSheet>> { - self.get_cssom_stylesheet().map(Root::upcast) + fn GetSheet(&self) -> Option<DomRoot<DOMStyleSheet>> { + self.get_cssom_stylesheet().map(DomRoot::upcast) } } diff --git a/components/script/dom/htmlmapelement.rs b/components/script/dom/htmlmapelement.rs index 9a2ae807619..d3024bb6e95 100644 --- a/components/script/dom/htmlmapelement.rs +++ b/components/script/dom/htmlmapelement.rs @@ -1,45 +1,48 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLMapElementBinding; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlareaelement::HTMLAreaElement; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlareaelement::HTMLAreaElement; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{Node, ShadowIncluding}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLMapElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLMapElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLMapElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLMapElement { HTMLMapElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLMapElement> { - Node::reflect_node(box HTMLMapElement::new_inherited(local_name, prefix, document), - document, - HTMLMapElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLMapElement> { + Node::reflect_node( + Box::new(HTMLMapElement::new_inherited(local_name, prefix, document)), + document, + ) } - pub fn get_area_elements(&self) -> Vec<Root<HTMLAreaElement>> { + pub fn get_area_elements(&self) -> Vec<DomRoot<HTMLAreaElement>> { self.upcast::<Node>() - .traverse_preorder() - .filter_map(Root::downcast::<HTMLAreaElement>).collect() + .traverse_preorder(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<HTMLAreaElement>) + .collect() } } - diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index fc036ee35cc..27af41c2b0e 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -1,652 +1,2007 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use audio_video_metadata; -use document_loader::LoadType; -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; -use dom::bindings::codegen::Bindings::HTMLMediaElementBinding::CanPlayTypeResult; -use dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementConstants::*; -use dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods; -use dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*; -use dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{Element, AttributeMutation}; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::htmlaudioelement::HTMLAudioElement; -use dom::htmlelement::HTMLElement; -use dom::htmlsourceelement::HTMLSourceElement; -use dom::htmlvideoelement::HTMLVideoElement; -use dom::mediaerror::MediaError; -use dom::node::{window_from_node, document_from_node, Node, UnbindContext}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::document_loader::{LoadBlocker, LoadType}; +use crate::dom::attr::Attr; +use crate::dom::audiotrack::AudioTrack; +use crate::dom::audiotracklist::AudioTrackList; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; +use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::CanPlayTypeResult; +use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementConstants; +use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLSourceElementBinding::HTMLSourceElementMethods; +use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*; +use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods; +use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackKind, TextTrackMode}; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::InheritTypes::{ElementTypeId, HTMLElementTypeId}; +use crate::dom::bindings::codegen::InheritTypes::{HTMLMediaElementTypeId, NodeTypeId}; +use crate::dom::bindings::codegen::UnionTypes::{ + MediaStreamOrBlob, VideoTrackOrAudioTrackOrTextTrack, +}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::blob::Blob; +use crate::dom::document::Document; +use crate::dom::element::{ + cors_setting_for_element, reflect_cross_origin_attribute, set_cross_origin_attribute, +}; +use crate::dom::element::{AttributeMutation, Element, ElementCreator}; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlscriptelement::HTMLScriptElement; +use crate::dom::htmlsourceelement::HTMLSourceElement; +use crate::dom::htmlstyleelement::HTMLStyleElement; +use crate::dom::htmlvideoelement::HTMLVideoElement; +use crate::dom::mediaerror::MediaError; +use crate::dom::mediafragmentparser::MediaFragmentParser; +use crate::dom::mediastream::MediaStream; +use crate::dom::node::{document_from_node, window_from_node, Node, NodeDamage, UnbindContext}; +use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::promise::Promise; +use crate::dom::shadowroot::IsUserAgentWidget; +use crate::dom::texttrack::TextTrack; +use crate::dom::texttracklist::TextTrackList; +use crate::dom::timeranges::{TimeRanges, TimeRangesContainer}; +use crate::dom::trackevent::TrackEvent; +use crate::dom::url::URL; +use crate::dom::videotrack::VideoTrack; +use crate::dom::videotracklist::VideoTrackList; +use crate::dom::virtualmethods::VirtualMethods; +use crate::fetch::{create_a_potential_cors_request, FetchCanceller}; +use crate::microtask::{Microtask, MicrotaskRunnable}; +use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; +use crate::realms::InRealm; +use crate::script_thread::ScriptThread; +use crate::task_source::TaskSource; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use embedder_traits::resources::{self, Resource as EmbedderResource}; +use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState}; +use euclid::default::Size2D; +use headers::{ContentLength, ContentRange, HeaderMapExt}; +use html5ever::{LocalName, Prefix}; +use http::header::{self, HeaderMap, HeaderValue}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; -use net_traits::{FetchResponseListener, FetchMetadata, Metadata, NetworkError}; -use net_traits::request::{CredentialsMode, Destination, RequestInit, Type as RequestType}; -use network_listener::{NetworkListener, PreInvoke}; -use script_thread::{Runnable, ScriptThread}; -use servo_atoms::Atom; +use media::{glplayer_channel, GLPlayerMsg, GLPlayerMsgForward, WindowGLContext}; +use net_traits::image::base::Image; +use net_traits::image_cache::ImageResponse; +use net_traits::request::Destination; +use net_traits::{CoreResourceMsg, FetchChannels, FetchMetadata, FetchResponseListener, Metadata}; +use net_traits::{NetworkError, ResourceFetchTiming, ResourceTimingType}; +use script_layout_interface::HTMLMediaData; +use script_traits::{ImageUpdate, WebrenderIpcSender}; +use servo_config::pref; +use servo_media::player::audio::AudioRenderer; +use servo_media::player::video::{VideoFrame, VideoFrameRenderer}; +use servo_media::player::{PlaybackState, Player, PlayerError, PlayerEvent, SeekLock, StreamType}; +use servo_media::{ClientContextId, ServoMedia, SupportsMediaType}; use servo_url::ServoUrl; use std::cell::Cell; +use std::collections::VecDeque; +use std::f64; +use std::mem; +use std::rc::Rc; use std::sync::{Arc, Mutex}; -use task_source::TaskSource; -use time::{self, Timespec, Duration}; - -struct HTMLMediaElementContext { - /// The element that initiated the request. - elem: Trusted<HTMLMediaElement>, - /// The response body received to date. - data: Vec<u8>, - /// The response metadata received to date. - metadata: Option<Metadata>, - /// The generation of the media element when this fetch started. - generation_id: u32, - /// Time of last progress notification. - next_progress_event: Timespec, - /// Url of resource requested. - url: ServoUrl, - /// Whether the media metadata has been completely received. - have_metadata: bool, - /// True if this response is invalid and should be ignored. - ignore_response: bool, +use time::{self, Duration, Timespec}; +use webrender_api::ImageKey; +use webrender_api::{ExternalImageData, ExternalImageId, ExternalImageType, TextureTarget}; +use webrender_api::{ImageData, ImageDescriptor, ImageDescriptorFlags, ImageFormat}; + +#[derive(PartialEq)] +enum FrameStatus { + Locked, + Unlocked, } -impl FetchResponseListener for HTMLMediaElementContext { - fn process_request_body(&mut self) {} +struct FrameHolder(FrameStatus, VideoFrame); - fn process_request_eof(&mut self) {} +impl FrameHolder { + fn new(frame: VideoFrame) -> FrameHolder { + FrameHolder(FrameStatus::Unlocked, frame) + } - // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list - fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { - self.metadata = metadata.ok().map(|m| { - match m { - FetchMetadata::Unfiltered(m) => m, - FetchMetadata::Filtered { unsafe_, .. } => unsafe_ - } - }); + fn lock(&mut self) { + if self.0 == FrameStatus::Unlocked { + self.0 = FrameStatus::Locked; + }; + } - // => "If the media data cannot be fetched at all..." - let is_failure = self.metadata - .as_ref() - .and_then(|m| m.status - .as_ref() - .map(|&(s, _)| s < 200 || s >= 300)) - .unwrap_or(false); - if is_failure { - // Ensure that the element doesn't receive any further notifications - // of the aborted fetch. The dedicated failure steps will be executed - // when response_complete runs. - self.ignore_response = true; - } + fn unlock(&mut self) { + if self.0 == FrameStatus::Locked { + self.0 = FrameStatus::Unlocked; + }; } - fn process_response_chunk(&mut self, mut payload: Vec<u8>) { - if self.ignore_response { - return; + fn set(&mut self, new_frame: VideoFrame) { + if self.0 == FrameStatus::Unlocked { + self.1 = new_frame + }; + } + + fn get(&self) -> (u32, Size2D<i32>, usize) { + if self.0 == FrameStatus::Locked { + ( + self.1.get_texture_id(), + Size2D::new(self.1.get_width(), self.1.get_height()), + 0, + ) + } else { + unreachable!(); } + } - self.data.append(&mut payload); + fn get_frame(&self) -> VideoFrame { + self.1.clone() + } +} - let elem = self.elem.root(); +pub struct MediaFrameRenderer { + player_id: Option<u64>, + api: WebrenderIpcSender, + current_frame: Option<(ImageKey, i32, i32)>, + old_frame: Option<ImageKey>, + very_old_frame: Option<ImageKey>, + current_frame_holder: Option<FrameHolder>, +} - // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list - // => "Once enough of the media data has been fetched to determine the duration..." - if !self.have_metadata { - self.check_metadata(&elem); - } else { - elem.change_ready_state(HAVE_CURRENT_DATA); +impl MediaFrameRenderer { + fn new(render_api_sender: WebrenderIpcSender) -> Self { + Self { + player_id: None, + api: render_api_sender, + current_frame: None, + old_frame: None, + very_old_frame: None, + current_frame_holder: None, } + } - // https://html.spec.whatwg.org/multipage/#concept-media-load-resource step 4, - // => "If mode is remote" step 2 - if time::get_time() > self.next_progress_event { - elem.queue_fire_simple_event("progress"); - self.next_progress_event = time::get_time() + Duration::milliseconds(350); + fn render_poster_frame(&mut self, image: Arc<Image>) { + if let Some(image_id) = image.id { + self.current_frame = Some((image_id, image.width as i32, image.height as i32)); } } +} - // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list - fn process_response_eof(&mut self, status: Result<(), NetworkError>) { - let elem = self.elem.root(); +impl VideoFrameRenderer for MediaFrameRenderer { + fn render(&mut self, frame: VideoFrame) { + let mut updates = vec![]; - // => "If the media data can be fetched but is found by inspection to be in an unsupported - // format, or can otherwise not be rendered at all" - if !self.have_metadata { - elem.queue_dedicated_media_source_failure_steps(); + if let Some(old_image_key) = mem::replace(&mut self.very_old_frame, self.old_frame.take()) { + updates.push(ImageUpdate::DeleteImage(old_image_key)); } - // => "Once the entire media resource has been fetched..." - else if status.is_ok() { - elem.change_ready_state(HAVE_ENOUGH_DATA); - elem.fire_simple_event("progress"); - - elem.network_state.set(NETWORK_IDLE); + let descriptor = ImageDescriptor::new( + frame.get_width(), + frame.get_height(), + ImageFormat::BGRA8, + ImageDescriptorFlags::empty(), + ); + + match self.current_frame { + Some((ref image_key, ref mut width, ref mut height)) + if *width == frame.get_width() && *height == frame.get_height() => + { + if !frame.is_gl_texture() { + updates.push(ImageUpdate::UpdateImage( + *image_key, + descriptor, + ImageData::Raw(frame.get_data()), + )); + } - elem.fire_simple_event("suspend"); - } - // => "If the connection is interrupted after some media data has been received..." - else if elem.ready_state.get() != HAVE_NOTHING { - // Step 2 - elem.error.set(Some(&*MediaError::new(&*window_from_node(&*elem), - MEDIA_ERR_NETWORK))); + self.current_frame_holder + .get_or_insert_with(|| FrameHolder::new(frame.clone())) + .set(frame); - // Step 3 - elem.network_state.set(NETWORK_IDLE); + if let Some(old_image_key) = self.old_frame.take() { + updates.push(ImageUpdate::DeleteImage(old_image_key)); + } + } + Some((ref mut image_key, ref mut width, ref mut height)) => { + self.old_frame = Some(*image_key); + + let new_image_key = match self.api.generate_image_key() { + Ok(key) => key, + Err(()) => return, + }; + + /* update current_frame */ + *image_key = new_image_key; + *width = frame.get_width(); + *height = frame.get_height(); + + let image_data = if frame.is_gl_texture() && self.player_id.is_some() { + let texture_target = if frame.is_external_oes() { + TextureTarget::External + } else { + TextureTarget::Default + }; + + ImageData::External(ExternalImageData { + id: ExternalImageId(self.player_id.unwrap()), + channel_index: 0, + image_type: ExternalImageType::TextureHandle(texture_target), + }) + } else { + ImageData::Raw(frame.get_data()) + }; + + self.current_frame_holder + .get_or_insert_with(|| FrameHolder::new(frame.clone())) + .set(frame); + + updates.push(ImageUpdate::AddImage(new_image_key, descriptor, image_data)); + }, + None => { + let image_key = match self.api.generate_image_key() { + Ok(key) => key, + Err(()) => return, + }; + self.current_frame = Some((image_key, frame.get_width(), frame.get_height())); + + let image_data = if frame.is_gl_texture() && self.player_id.is_some() { + let texture_target = if frame.is_external_oes() { + TextureTarget::External + } else { + TextureTarget::Default + }; + + ImageData::External(ExternalImageData { + id: ExternalImageId(self.player_id.unwrap()), + channel_index: 0, + image_type: ExternalImageType::TextureHandle(texture_target), + }) + } else { + ImageData::Raw(frame.get_data()) + }; - // TODO: Step 4 - update delay load flag + self.current_frame_holder = Some(FrameHolder::new(frame)); - // Step 5 - elem.fire_simple_event("error"); - } else { - // => "If the media data cannot be fetched at all..." - elem.queue_dedicated_media_source_failure_steps(); + updates.push(ImageUpdate::AddImage(image_key, descriptor, image_data)); + }, } - - let document = document_from_node(&*elem); - document.finish_load(LoadType::Media(self.url.clone())); + self.api.update_images(updates); } } -impl PreInvoke for HTMLMediaElementContext { - fn should_invoke(&self) -> bool { - //TODO: finish_load needs to run at some point if the generation changes. - self.elem.root().generation_id.get() == self.generation_id - } +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +enum SrcObject { + MediaStream(Dom<MediaStream>), + Blob(Dom<Blob>), } -impl HTMLMediaElementContext { - fn new(elem: &HTMLMediaElement, url: ServoUrl) -> HTMLMediaElementContext { - HTMLMediaElementContext { - elem: Trusted::new(elem), - data: vec![], - metadata: None, - generation_id: elem.generation_id.get(), - next_progress_event: time::get_time() + Duration::milliseconds(350), - url: url, - have_metadata: false, - ignore_response: false, - } - } - - fn check_metadata(&mut self, elem: &HTMLMediaElement) { - match audio_video_metadata::get_format_from_slice(&self.data) { - Ok(audio_video_metadata::Metadata::Video(meta)) => { - let dur = meta.audio.duration.unwrap_or(::std::time::Duration::new(0, 0)); - *elem.video.borrow_mut() = Some(VideoMedia { - format: format!("{:?}", meta.format), - duration: Duration::seconds(dur.as_secs() as i64) + - Duration::nanoseconds(dur.subsec_nanos() as i64), - width: meta.dimensions.width, - height: meta.dimensions.height, - video: meta.video.unwrap_or("".to_owned()), - audio: meta.audio.audio, - }); - // Step 6 - elem.change_ready_state(HAVE_METADATA); - self.have_metadata = true; - } - _ => {} +impl From<MediaStreamOrBlob> for SrcObject { + #[allow(unrooted_must_root)] + fn from(src_object: MediaStreamOrBlob) -> SrcObject { + match src_object { + MediaStreamOrBlob::Blob(blob) => SrcObject::Blob(Dom::from_ref(&*blob)), + MediaStreamOrBlob::MediaStream(stream) => { + SrcObject::MediaStream(Dom::from_ref(&*stream)) + }, } } } -#[derive(JSTraceable, HeapSizeOf)] -pub struct VideoMedia { - format: String, - #[ignore_heap_size_of = "defined in time"] - duration: Duration, - width: u32, - height: u32, - video: String, - audio: Option<String>, -} - #[dom_struct] +#[allow(non_snake_case)] pub struct HTMLMediaElement { htmlelement: HTMLElement, - network_state: Cell<u16>, - ready_state: Cell<u16>, - current_src: DOMRefCell<String>, + /// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate> + network_state: Cell<NetworkState>, + /// <https://html.spec.whatwg.org/multipage/#dom-media-readystate> + ready_state: Cell<ReadyState>, + /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject> + src_object: DomRefCell<Option<SrcObject>>, + /// <https://html.spec.whatwg.org/multipage/#dom-media-currentsrc> + current_src: DomRefCell<String>, + /// Incremented whenever tasks associated with this element are cancelled. generation_id: Cell<u32>, - first_data_load: Cell<bool>, - error: MutNullableJS<MediaError>, + /// <https://html.spec.whatwg.org/multipage/#fire-loadeddata> + /// + /// Reset to false every time the load algorithm is invoked. + fired_loadeddata_event: Cell<bool>, + /// <https://html.spec.whatwg.org/multipage/#dom-media-error> + error: MutNullableDom<MediaError>, + /// <https://html.spec.whatwg.org/multipage/#dom-media-paused> paused: Cell<bool>, + /// <https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate> + defaultPlaybackRate: Cell<f64>, + /// <https://html.spec.whatwg.org/multipage/#dom-media-playbackrate> + playbackRate: Cell<f64>, + /// <https://html.spec.whatwg.org/multipage/#attr-media-autoplay> autoplaying: Cell<bool>, - video: DOMRefCell<Option<VideoMedia>>, + /// <https://html.spec.whatwg.org/multipage/#delaying-the-load-event-flag> + delaying_the_load_event_flag: DomRefCell<Option<LoadBlocker>>, + /// <https://html.spec.whatwg.org/multipage/#list-of-pending-play-promises> + #[ignore_malloc_size_of = "promises are hard"] + pending_play_promises: DomRefCell<Vec<Rc<Promise>>>, + /// Play promises which are soon to be fulfilled by a queued task. + #[ignore_malloc_size_of = "promises are hard"] + in_flight_play_promises_queue: DomRefCell<VecDeque<(Box<[Rc<Promise>]>, ErrorResult)>>, + #[ignore_malloc_size_of = "servo_media"] + player: DomRefCell<Option<Arc<Mutex<dyn Player>>>>, + #[ignore_malloc_size_of = "Arc"] + video_renderer: Arc<Mutex<MediaFrameRenderer>>, + #[ignore_malloc_size_of = "Arc"] + audio_renderer: DomRefCell<Option<Arc<Mutex<dyn AudioRenderer>>>>, + /// https://html.spec.whatwg.org/multipage/#show-poster-flag + show_poster: Cell<bool>, + /// https://html.spec.whatwg.org/multipage/#dom-media-duration + duration: Cell<f64>, + /// https://html.spec.whatwg.org/multipage/#official-playback-position + playback_position: Cell<f64>, + /// https://html.spec.whatwg.org/multipage/#default-playback-start-position + default_playback_start_position: Cell<f64>, + /// https://html.spec.whatwg.org/multipage/#dom-media-volume + volume: Cell<f64>, + /// https://html.spec.whatwg.org/multipage/#dom-media-seeking + seeking: Cell<bool>, + /// https://html.spec.whatwg.org/multipage/#dom-media-muted + muted: Cell<bool>, + /// URL of the media resource, if any. + resource_url: DomRefCell<Option<ServoUrl>>, + /// URL of the media resource, if the resource is set through the src_object attribute and it + /// is a blob. + blob_url: DomRefCell<Option<ServoUrl>>, + /// https://html.spec.whatwg.org/multipage/#dom-media-played + #[ignore_malloc_size_of = "Rc"] + played: DomRefCell<TimeRangesContainer>, + // https://html.spec.whatwg.org/multipage/#dom-media-audiotracks + audio_tracks_list: MutNullableDom<AudioTrackList>, + // https://html.spec.whatwg.org/multipage/#dom-media-videotracks + video_tracks_list: MutNullableDom<VideoTrackList>, + /// https://html.spec.whatwg.org/multipage/#dom-media-texttracks + text_tracks_list: MutNullableDom<TextTrackList>, + /// Time of last timeupdate notification. + #[ignore_malloc_size_of = "Defined in time"] + next_timeupdate_event: Cell<Timespec>, + /// Latest fetch request context. + current_fetch_context: DomRefCell<Option<HTMLMediaElementFetchContext>>, + /// Player Id reported the player thread + id: Cell<u64>, + /// Media controls id. + /// In order to workaround the lack of privileged JS context, we secure the + /// the access to the "privileged" document.servoGetMediaControls(id) API by + /// keeping a whitelist of media controls identifiers. + media_controls_id: DomRefCell<Option<String>>, + #[ignore_malloc_size_of = "Defined in other crates"] + player_context: WindowGLContext, +} + +/// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate> +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +#[repr(u8)] +pub enum NetworkState { + Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8, + Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8, + Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8, + NoSource = HTMLMediaElementConstants::NETWORK_NO_SOURCE as u8, +} + +/// <https://html.spec.whatwg.org/multipage/#dom-media-readystate> +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)] +#[repr(u8)] +pub enum ReadyState { + HaveNothing = HTMLMediaElementConstants::HAVE_NOTHING as u8, + HaveMetadata = HTMLMediaElementConstants::HAVE_METADATA as u8, + HaveCurrentData = HTMLMediaElementConstants::HAVE_CURRENT_DATA as u8, + HaveFutureData = HTMLMediaElementConstants::HAVE_FUTURE_DATA as u8, + HaveEnoughData = HTMLMediaElementConstants::HAVE_ENOUGH_DATA as u8, } impl HTMLMediaElement { - pub fn new_inherited(tag_name: LocalName, - prefix: Option<DOMString>, document: &Document) - -> HTMLMediaElement { - HTMLMediaElement { - htmlelement: - HTMLElement::new_inherited(tag_name, prefix, document), - network_state: Cell::new(NETWORK_EMPTY), - ready_state: Cell::new(HAVE_NOTHING), - current_src: DOMRefCell::new("".to_owned()), + pub fn new_inherited(tag_name: LocalName, prefix: Option<Prefix>, document: &Document) -> Self { + Self { + htmlelement: HTMLElement::new_inherited(tag_name, prefix, document), + network_state: Cell::new(NetworkState::Empty), + ready_state: Cell::new(ReadyState::HaveNothing), + src_object: Default::default(), + current_src: DomRefCell::new("".to_owned()), generation_id: Cell::new(0), - first_data_load: Cell::new(true), + fired_loadeddata_event: Cell::new(false), error: Default::default(), paused: Cell::new(true), + defaultPlaybackRate: Cell::new(1.0), + playbackRate: Cell::new(1.0), + muted: Cell::new(false), + // FIXME(nox): Why is this initialised to true? autoplaying: Cell::new(true), - video: DOMRefCell::new(None), + delaying_the_load_event_flag: Default::default(), + pending_play_promises: Default::default(), + in_flight_play_promises_queue: Default::default(), + player: Default::default(), + video_renderer: Arc::new(Mutex::new(MediaFrameRenderer::new( + document.window().get_webrender_api_sender(), + ))), + audio_renderer: Default::default(), + show_poster: Cell::new(true), + duration: Cell::new(f64::NAN), + playback_position: Cell::new(0.), + default_playback_start_position: Cell::new(0.), + volume: Cell::new(1.0), + seeking: Cell::new(false), + resource_url: DomRefCell::new(None), + blob_url: DomRefCell::new(None), + played: DomRefCell::new(TimeRangesContainer::new()), + audio_tracks_list: Default::default(), + video_tracks_list: Default::default(), + text_tracks_list: Default::default(), + next_timeupdate_event: Cell::new(time::get_time() + Duration::milliseconds(250)), + current_fetch_context: DomRefCell::new(None), + id: Cell::new(0), + media_controls_id: DomRefCell::new(None), + player_context: document.window().get_player_context(), } } - // https://html.spec.whatwg.org/multipage/#internal-pause-steps - fn internal_pause_steps(&self) { - // Step 1 - self.autoplaying.set(false); - - // Step 2 - if !self.Paused() { - // 2.1 - self.paused.set(true); - - // 2.2 - self.queue_internal_pause_steps_task(); + pub fn get_ready_state(&self) -> ReadyState { + self.ready_state.get() + } - // TODO 2.3 (official playback position) + fn media_type_id(&self) -> HTMLMediaElementTypeId { + match self.upcast::<Node>().type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLMediaElement(media_type_id), + )) => media_type_id, + _ => unreachable!(), } - - // TODO step 3 (media controller) } - // https://html.spec.whatwg.org/multipage/#notify-about-playing - fn notify_about_playing(&self) { - // Step 1 - self.fire_simple_event("playing"); - // TODO Step 2 + fn play_media(&self) { + if let Some(ref player) = *self.player.borrow() { + if let Err(e) = player.lock().unwrap().set_rate(self.playbackRate.get()) { + warn!("Could not set the playback rate {:?}", e); + } + if let Err(e) = player.lock().unwrap().play() { + warn!("Could not play media {:?}", e); + } + } } - fn queue_notify_about_playing(&self) { - struct Task { - elem: Trusted<HTMLMediaElement>, + /// Marks that element as delaying the load event or not. + /// + /// Nothing happens if the element was already delaying the load event and + /// we pass true to that method again. + /// + /// <https://html.spec.whatwg.org/multipage/#delaying-the-load-event-flag> + pub fn delay_load_event(&self, delay: bool) { + let mut blocker = self.delaying_the_load_event_flag.borrow_mut(); + if delay && blocker.is_none() { + *blocker = Some(LoadBlocker::new(&document_from_node(self), LoadType::Media)); + } else if !delay && blocker.is_some() { + LoadBlocker::terminate(&mut *blocker); } + } - impl Runnable for Task { - fn handler(self: Box<Task>) { - self.elem.root().notify_about_playing(); - } + /// https://html.spec.whatwg.org/multipage/#time-marches-on + fn time_marches_on(&self) { + // Step 6. + if time::get_time() > self.next_timeupdate_event.get() { + let window = window_from_node(self); + window + .task_manager() + .media_element_task_source() + .queue_simple_event(self.upcast(), atom!("timeupdate"), &window); + self.next_timeupdate_event + .set(time::get_time() + Duration::milliseconds(350)); } - - let task = box Task { - elem: Trusted::new(self), - }; - let win = window_from_node(self); - let _ = win.dom_manipulation_task_source().queue(task, win.upcast()); } - // https://html.spec.whatwg.org/multipage/#internal-pause-steps step 2.2 - fn queue_internal_pause_steps_task(&self) { - struct Task { - elem: Trusted<HTMLMediaElement>, - } + /// <https://html.spec.whatwg.org/multipage/#internal-pause-steps> + fn internal_pause_steps(&self) { + // Step 1. + self.autoplaying.set(false); - impl Runnable for Task { - fn handler(self: Box<Task>) { - let elem = self.elem.root(); - // 2.2.1 - elem.fire_simple_event("timeupdate"); - // 2.2.2 - elem.fire_simple_event("pause"); - // TODO 2.2.3 - } - } + // Step 2. + if !self.Paused() { + // Step 2.1. + self.paused.set(true); - let task = box Task { - elem: Trusted::new(self), - }; - let win = window_from_node(self); - let _ = win.dom_manipulation_task_source().queue(task, win.upcast()); - } + // Step 2.2. + self.take_pending_play_promises(Err(Error::Abort)); - fn queue_fire_simple_event(&self, type_: &'static str) { - let win = window_from_node(self); - let task = box FireSimpleEventTask::new(self, type_); - let _ = win.dom_manipulation_task_source().queue(task, win.upcast()); + // Step 2.3. + let window = window_from_node(self); + let this = Trusted::new(self); + let generation_id = self.generation_id.get(); + let _ = window.task_manager().media_element_task_source().queue( + task!(internal_pause_steps: move || { + let this = this.root(); + if generation_id != this.generation_id.get() { + return; + } + + this.fulfill_in_flight_play_promises(|| { + // Step 2.3.1. + this.upcast::<EventTarget>().fire_event(atom!("timeupdate")); + + // Step 2.3.2. + this.upcast::<EventTarget>().fire_event(atom!("pause")); + + if let Some(ref player) = *this.player.borrow() { + if let Err(e) = player.lock().unwrap().pause() { + eprintln!("Could not pause player {:?}", e); + } + } + + // Step 2.3.3. + // Done after running this closure in + // `fulfill_in_flight_play_promises`. + }); + }), + window.upcast(), + ); + + // Step 2.4. + // FIXME(nox): Set the official playback position to the current + // playback position. + } + } + // https://html.spec.whatwg.org/multipage/#allowed-to-play + fn is_allowed_to_play(&self) -> bool { + true } - fn fire_simple_event(&self, type_: &str) { + // https://html.spec.whatwg.org/multipage/#notify-about-playing + fn notify_about_playing(&self) { + // Step 1. + self.take_pending_play_promises(Ok(())); + + // Step 2. let window = window_from_node(self); - let event = Event::new(window.upcast(), - Atom::from(type_), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable); - event.fire(self.upcast()); + let this = Trusted::new(self); + let generation_id = self.generation_id.get(); + // FIXME(nox): Why are errors silenced here? + let _ = window.task_manager().media_element_task_source().queue( + task!(notify_about_playing: move || { + let this = this.root(); + if generation_id != this.generation_id.get() { + return; + } + + this.fulfill_in_flight_play_promises(|| { + // Step 2.1. + this.upcast::<EventTarget>().fire_event(atom!("playing")); + this.play_media(); + + // Step 2.2. + // Done after running this closure in + // `fulfill_in_flight_play_promises`. + }); + + }), + window.upcast(), + ); } // https://html.spec.whatwg.org/multipage/#ready-states - fn change_ready_state(&self, ready_state: u16) { + fn change_ready_state(&self, ready_state: ReadyState) { let old_ready_state = self.ready_state.get(); self.ready_state.set(ready_state); - if self.network_state.get() == NETWORK_EMPTY { + if self.network_state.get() == NetworkState::Empty { return; } - // Step 1 - match (old_ready_state, ready_state) { - // previous ready state was HAVE_NOTHING, and the new ready state is - // HAVE_METADATA - (HAVE_NOTHING, HAVE_METADATA) => { - self.queue_fire_simple_event("loadedmetadata"); - } + let window = window_from_node(self); + let task_source = window.task_manager().media_element_task_source(); - // previous ready state was HAVE_METADATA and the new ready state is - // HAVE_CURRENT_DATA or greater - (HAVE_METADATA, HAVE_CURRENT_DATA) | - (HAVE_METADATA, HAVE_FUTURE_DATA) | - (HAVE_METADATA, HAVE_ENOUGH_DATA) => { - if self.first_data_load.get() { - self.first_data_load.set(false); - self.queue_fire_simple_event("loadeddata"); + // Step 1. + match (old_ready_state, ready_state) { + (ReadyState::HaveNothing, ReadyState::HaveMetadata) => { + task_source.queue_simple_event(self.upcast(), atom!("loadedmetadata"), &window); + // No other steps are applicable in this case. + return; + }, + (ReadyState::HaveMetadata, new) if new >= ReadyState::HaveCurrentData => { + if !self.fired_loadeddata_event.get() { + self.fired_loadeddata_event.set(true); + let this = Trusted::new(self); + // FIXME(nox): Why are errors silenced here? + let _ = task_source.queue( + task!(media_reached_current_data: move || { + let this = this.root(); + this.upcast::<EventTarget>().fire_event(atom!("loadeddata")); + this.delay_load_event(false); + }), + window.upcast(), + ); } - } - // previous ready state was HAVE_FUTURE_DATA or more, and the new ready - // state is HAVE_CURRENT_DATA or less - (HAVE_FUTURE_DATA, HAVE_CURRENT_DATA) | - (HAVE_ENOUGH_DATA, HAVE_CURRENT_DATA) | - (HAVE_FUTURE_DATA, HAVE_METADATA) | - (HAVE_ENOUGH_DATA, HAVE_METADATA) | - (HAVE_FUTURE_DATA, HAVE_NOTHING) | - (HAVE_ENOUGH_DATA, HAVE_NOTHING) => { - // TODO: timeupdate event logic + waiting - } + // Steps for the transition from HaveMetadata to HaveCurrentData + // or HaveFutureData also apply here, as per the next match + // expression. + }, + (ReadyState::HaveFutureData, new) if new <= ReadyState::HaveCurrentData => { + // FIXME(nox): Queue a task to fire timeupdate and waiting + // events if the conditions call from the spec are met. + + // No other steps are applicable in this case. + return; + }, _ => (), } - // Step 1 - // If the new ready state is HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, - // then the relevant steps below must then be run also. - match (old_ready_state, ready_state) { - // previous ready state was HAVE_CURRENT_DATA or less, and the new ready - // state is HAVE_FUTURE_DATA - (HAVE_CURRENT_DATA, HAVE_FUTURE_DATA) | - (HAVE_METADATA, HAVE_FUTURE_DATA) | - (HAVE_NOTHING, HAVE_FUTURE_DATA) => { - self.queue_fire_simple_event("canplay"); + if old_ready_state <= ReadyState::HaveCurrentData && + ready_state >= ReadyState::HaveFutureData + { + task_source.queue_simple_event(self.upcast(), atom!("canplay"), &window); - if !self.Paused() { - self.queue_notify_about_playing(); - } + if !self.Paused() { + self.notify_about_playing(); } + } - // new ready state is HAVE_ENOUGH_DATA - (_, HAVE_ENOUGH_DATA) => { - if old_ready_state <= HAVE_CURRENT_DATA { - self.queue_fire_simple_event("canplay"); - - if !self.Paused() { - self.queue_notify_about_playing(); - } - } + if ready_state == ReadyState::HaveEnoughData { + // TODO: Check sandboxed automatic features browsing context flag. + // FIXME(nox): I have no idea what this TODO is about. - //TODO: check sandboxed automatic features browsing context flag - if self.autoplaying.get() && - self.Paused() && - self.Autoplay() { - // Step 1 - self.paused.set(false); - // TODO step 2: show poster - // Step 3 - self.queue_fire_simple_event("play"); - // Step 4 - self.queue_notify_about_playing(); - // Step 5 - self.autoplaying.set(false); + // FIXME(nox): Review this block. + if self.autoplaying.get() && self.Paused() && self.Autoplay() { + // Step 1 + self.paused.set(false); + // Step 2 + if self.show_poster.get() { + self.show_poster.set(false); + self.time_marches_on(); } - - self.queue_fire_simple_event("canplaythrough"); + // Step 3 + task_source.queue_simple_event(self.upcast(), atom!("play"), &window); + // Step 4 + self.notify_about_playing(); + // Step 5 + self.autoplaying.set(false); } - _ => (), + // FIXME(nox): According to the spec, this should come *before* the + // "play" event. + task_source.queue_simple_event(self.upcast(), atom!("canplaythrough"), &window); } - - // TODO Step 2: media controller } // https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm fn invoke_resource_selection_algorithm(&self) { - // Step 1 - self.network_state.set(NETWORK_NO_SOURCE); + // Step 1. + self.network_state.set(NetworkState::NoSource); - // TODO step 2 (show poster) - // TODO step 3 (delay load event) + // Step 2. + self.show_poster.set(true); - // Step 4 + // Step 3. + self.delay_load_event(true); + + // Step 4. + // If the resource selection mode in the synchronous section is + // "attribute", the URL of the resource to fetch is relative to the + // media element's node document when the src attribute was last + // changed, which is why we need to pass the base URL in the task + // right here. let doc = document_from_node(self); - ScriptThread::await_stable_state(ResourceSelectionTask::new(self, doc.base_url())); + let task = MediaElementMicrotask::ResourceSelectionTask { + elem: DomRoot::from_ref(self), + generation_id: self.generation_id.get(), + base_url: doc.base_url(), + }; + + // FIXME(nox): This will later call the resource_selection_algorithm_sync + // method from below, if microtasks were trait objects, we would be able + // to put the code directly in this method, without the boilerplate + // indirections. + ScriptThread::await_stable_state(Microtask::MediaElement(task)); } // https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm - #[allow(unreachable_code)] fn resource_selection_algorithm_sync(&self, base_url: ServoUrl) { - // TODO step 5 (populate pending text tracks) - - // Step 6 - let mode = if false { - // TODO media provider object - ResourceSelectionMode::Object - } else if let Some(attr) = self.upcast::<Element>().get_attribute(&ns!(), &local_name!("src")) { - ResourceSelectionMode::Attribute(attr.Value().to_string()) - } else if false { // TODO: when implementing this remove #[allow(unreachable_code)] above. - // TODO <source> child - ResourceSelectionMode::Children(panic!()) + // Step 5. + // FIXME(ferjm): Implement blocked_on_parser logic + // https://html.spec.whatwg.org/multipage/#blocked-on-parser + // FIXME(nox): Maybe populate the list of pending text tracks. + + // Step 6. + enum Mode { + Object, + Attribute(String), + Children(DomRoot<HTMLSourceElement>), + } + fn mode(media: &HTMLMediaElement) -> Option<Mode> { + if media.src_object.borrow().is_some() { + return Some(Mode::Object); + } + if let Some(attr) = media + .upcast::<Element>() + .get_attribute(&ns!(), &local_name!("src")) + { + return Some(Mode::Attribute(attr.Value().into())); + } + let source_child_element = media + .upcast::<Node>() + .children() + .filter_map(DomRoot::downcast::<HTMLSourceElement>) + .next(); + if let Some(element) = source_child_element { + return Some(Mode::Children(element)); + } + None + } + let mode = if let Some(mode) = mode(self) { + mode } else { - self.network_state.set(NETWORK_EMPTY); + self.network_state.set(NetworkState::Empty); + // https://github.com/whatwg/html/issues/3065 + self.delay_load_event(false); return; }; - // Step 7 - self.network_state.set(NETWORK_LOADING); + // Step 7. + self.network_state.set(NetworkState::Loading); - // Step 8 - self.queue_fire_simple_event("loadstart"); + // Step 8. + let window = window_from_node(self); + window + .task_manager() + .media_element_task_source() + .queue_simple_event(self.upcast(), atom!("loadstart"), &window); - // Step 9 + // Step 9. match mode { - ResourceSelectionMode::Object => { - // Step 1 + // Step 9.obj. + Mode::Object => { + // Step 9.obj.1. *self.current_src.borrow_mut() = "".to_owned(); - // Step 4 - self.resource_fetch_algorithm(Resource::Object); - } + // Step 9.obj.2. + // FIXME(nox): The rest of the steps should be ran in parallel. - ResourceSelectionMode::Attribute(src) => { - // Step 1 + // Step 9.obj.3. + // Note that the resource fetch algorithm itself takes care + // of the cleanup in case of failure itself. + self.resource_fetch_algorithm(Resource::Object); + }, + Mode::Attribute(src) => { + // Step 9.attr.1. if src.is_empty() { self.queue_dedicated_media_source_failure_steps(); return; } - // Step 2 - let absolute_url = base_url.join(&src).map_err(|_| ()); - - // Step 3 - if let Ok(url) = absolute_url { - *self.current_src.borrow_mut() = url.as_str().into(); - // Step 4 - self.resource_fetch_algorithm(Resource::Url(url)); - } else { + // Step 9.attr.2. + let url_record = match base_url.join(&src) { + Ok(url) => url, + Err(_) => { + self.queue_dedicated_media_source_failure_steps(); + return; + }, + }; + + // Step 9.attr.3. + *self.current_src.borrow_mut() = url_record.as_str().into(); + + // Step 9.attr.4. + // Note that the resource fetch algorithm itself takes care + // of the cleanup in case of failure itself. + self.resource_fetch_algorithm(Resource::Url(url_record)); + }, + // Step 9.children. + Mode::Children(source) => { + // This is only a partial implementation + // FIXME: https://github.com/servo/servo/issues/21481 + let src = source.Src(); + // Step 9.attr.2. + if src.is_empty() { + source.upcast::<EventTarget>().fire_event(atom!("error")); self.queue_dedicated_media_source_failure_steps(); + return; } - } + // Step 9.attr.3. + let url_record = match base_url.join(&src) { + Ok(url) => url, + Err(_) => { + source.upcast::<EventTarget>().fire_event(atom!("error")); + self.queue_dedicated_media_source_failure_steps(); + return; + }, + }; + // Step 9.attr.8. + self.resource_fetch_algorithm(Resource::Url(url_record)); + }, + } + } - ResourceSelectionMode::Children(_child) => { - // TODO - self.queue_dedicated_media_source_failure_steps() - } + fn fetch_request(&self, offset: Option<u64>, seek_lock: Option<SeekLock>) { + if self.resource_url.borrow().is_none() && self.blob_url.borrow().is_none() { + eprintln!("Missing request url"); + self.queue_dedicated_media_source_failure_steps(); + return; + } + + let document = document_from_node(self); + let destination = match self.media_type_id() { + HTMLMediaElementTypeId::HTMLAudioElement => Destination::Audio, + HTMLMediaElementTypeId::HTMLVideoElement => Destination::Video, + }; + let mut headers = HeaderMap::new(); + // FIXME(eijebong): Use typed headers once we have a constructor for the range header + headers.insert( + header::RANGE, + HeaderValue::from_str(&format!("bytes={}-", offset.unwrap_or(0))).unwrap(), + ); + let url = match self.resource_url.borrow().as_ref() { + Some(url) => url.clone(), + None => self.blob_url.borrow().as_ref().unwrap().clone(), + }; + + let cors_setting = cors_setting_for_element(self.upcast()); + let request = create_a_potential_cors_request( + url.clone(), + destination, + cors_setting, + None, + self.global().get_referrer(), + ) + .headers(headers) + .origin(document.origin().immutable().clone()) + .pipeline_id(Some(self.global().pipeline_id())) + .referrer_policy(document.get_referrer_policy()); + + let mut current_fetch_context = self.current_fetch_context.borrow_mut(); + if let Some(ref mut current_fetch_context) = *current_fetch_context { + current_fetch_context.cancel(CancelReason::Overridden); } + let (fetch_context, cancel_receiver) = HTMLMediaElementFetchContext::new(); + *current_fetch_context = Some(fetch_context); + let fetch_listener = Arc::new(Mutex::new(HTMLMediaElementFetchListener::new( + self, + url.clone(), + offset.unwrap_or(0), + seek_lock, + ))); + let (action_sender, action_receiver) = ipc::channel().unwrap(); + let window = window_from_node(self); + let (task_source, canceller) = window + .task_manager() + .networking_task_source_with_canceller(); + let network_listener = NetworkListener { + context: fetch_listener, + task_source, + canceller: Some(canceller), + }; + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + network_listener.notify_fetch(message.to().unwrap()); + }), + ); + let global = self.global(); + global + .core_resource_thread() + .send(CoreResourceMsg::Fetch( + request, + FetchChannels::ResponseMsg(action_sender, Some(cancel_receiver)), + )) + .unwrap(); } // https://html.spec.whatwg.org/multipage/#concept-media-load-resource fn resource_fetch_algorithm(&self, resource: Resource) { - // TODO step 3 (remove text tracks) + if let Err(e) = self.setup_media_player(&resource) { + eprintln!("Setup media player error {:?}", e); + self.queue_dedicated_media_source_failure_steps(); + return; + } - // Step 4 - if let Resource::Url(url) = resource { - // 4.1 - if self.Preload() == "none" && !self.autoplaying.get() { - // 4.1.1 - self.network_state.set(NETWORK_IDLE); + // Steps 1-2. + // Unapplicable, the `resource` variable already conveys which mode + // is in use. + + // Step 3. + // FIXME(nox): Remove all media-resource-specific text tracks. + + // Step 4. + match resource { + Resource::Url(url) => { + // Step 4.remote.1. + if self.Preload() == "none" && !self.autoplaying.get() { + // Step 4.remote.1.1. + self.network_state.set(NetworkState::Idle); + + // Step 4.remote.1.2. + let window = window_from_node(self); + window + .task_manager() + .media_element_task_source() + .queue_simple_event(self.upcast(), atom!("suspend"), &window); + + // Step 4.remote.1.3. + let this = Trusted::new(self); + window + .task_manager() + .media_element_task_source() + .queue( + task!(set_media_delay_load_event_flag_to_false: move || { + this.root().delay_load_event(false); + }), + window.upcast(), + ) + .unwrap(); + + // Steps 4.remote.1.4. + // FIXME(nox): Somehow we should wait for the task from previous + // step to be ran before continuing. + + // Steps 4.remote.1.5-4.remote.1.7. + // FIXME(nox): Wait for an implementation-defined event and + // then continue with the normal set of steps instead of just + // returning. + return; + } - // 4.1.2 - self.queue_fire_simple_event("suspend"); + // Step 4.remote.2. + *self.resource_url.borrow_mut() = Some(url); + self.fetch_request(None, None); + }, + Resource::Object => { + if let Some(ref src_object) = *self.src_object.borrow() { + match src_object { + SrcObject::Blob(blob) => { + let blob_url = URL::CreateObjectURL(&self.global(), &*blob); + *self.blob_url.borrow_mut() = + Some(ServoUrl::parse(&blob_url).expect("infallible")); + self.fetch_request(None, None); + }, + SrcObject::MediaStream(ref stream) => { + let tracks = &*stream.get_tracks(); + for (pos, track) in tracks.iter().enumerate() { + if let Err(_) = self + .player + .borrow() + .as_ref() + .unwrap() + .lock() + .unwrap() + .set_stream(&track.id(), pos == tracks.len() - 1) + { + self.queue_dedicated_media_source_failure_steps(); + } + } + }, + } + } + }, + } + } - // TODO 4.1.3 (delay load flag) + /// Queues a task to run the [dedicated media source failure steps][steps]. + /// + /// [steps]: https://html.spec.whatwg.org/multipage/#dedicated-media-source-failure-steps + fn queue_dedicated_media_source_failure_steps(&self) { + let window = window_from_node(self); + let this = Trusted::new(self); + let generation_id = self.generation_id.get(); + self.take_pending_play_promises(Err(Error::NotSupported)); + // FIXME(nox): Why are errors silenced here? + let _ = window.task_manager().media_element_task_source().queue( + task!(dedicated_media_source_failure_steps: move || { + let this = this.root(); + if generation_id != this.generation_id.get() { + return; + } - // TODO 4.1.5-7 (state for load that initiates later) - return; - } + this.fulfill_in_flight_play_promises(|| { + // Step 1. + this.error.set(Some(&*MediaError::new( + &window_from_node(&*this), + MEDIA_ERR_SRC_NOT_SUPPORTED, + ))); - // 4.2 - let context = Arc::new(Mutex::new(HTMLMediaElementContext::new(self, url.clone()))); - let (action_sender, action_receiver) = ipc::channel().unwrap(); - let window = window_from_node(self); - let listener = NetworkListener { - context: context, - task_source: window.networking_task_source(), - wrapper: Some(window.get_runnable_wrapper()) - }; + // Step 2. + this.AudioTracks().clear(); + this.VideoTracks().clear(); - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_fetch(message.to().unwrap()); - }); + // Step 3. + this.network_state.set(NetworkState::NoSource); - // FIXME: we're supposed to block the load event much earlier than now - let document = document_from_node(self); + // Step 4. + this.show_poster.set(true); - let ty = if self.is::<HTMLAudioElement>() { - RequestType::Audio - } else if self.is::<HTMLVideoElement>() { - RequestType::Video - } else { - unreachable!("Unexpected HTMLMediaElement") - }; + // Step 5. + this.upcast::<EventTarget>().fire_event(atom!("error")); - let request = RequestInit { - url: url.clone(), - type_: ty, - destination: Destination::Media, - credentials_mode: CredentialsMode::Include, - use_url_credentials: true, - origin: document.url(), - pipeline_id: Some(self.global().pipeline_id()), - referrer_url: Some(document.url()), - referrer_policy: document.get_referrer_policy(), - .. RequestInit::default() - }; + if let Some(ref player) = *this.player.borrow() { + if let Err(e) = player.lock().unwrap().stop() { + eprintln!("Could not stop player {:?}", e); + } + } - document.fetch_async(LoadType::Media(url), request, action_sender); - } else { - // TODO local resource fetch - self.queue_dedicated_media_source_failure_steps(); - } + // Step 6. + // Done after running this closure in + // `fulfill_in_flight_play_promises`. + }); + + // Step 7. + this.delay_load_event(false); + }), + window.upcast(), + ); } - fn queue_dedicated_media_source_failure_steps(&self) { + fn queue_ratechange_event(&self) { let window = window_from_node(self); - let _ = window.dom_manipulation_task_source().queue( - box DedicatedMediaSourceFailureTask::new(self), window.upcast()); + let task_source = window.task_manager().media_element_task_source(); + task_source.queue_simple_event(self.upcast(), atom!("ratechange"), &window); } - // https://html.spec.whatwg.org/multipage/#dedicated-media-source-failure-steps - fn dedicated_media_source_failure(&self) { - // Step 1 - self.error.set(Some(&*MediaError::new(&*window_from_node(self), - MEDIA_ERR_SRC_NOT_SUPPORTED))); - - // TODO step 2 (forget resource tracks) - - // Step 3 - self.network_state.set(NETWORK_NO_SOURCE); + // https://html.spec.whatwg.org/multipage/#potentially-playing + fn is_potentially_playing(&self) -> bool { + !self.paused.get() && + // FIXME: We need https://github.com/servo/servo/pull/22348 + // to know whether playback has ended or not + // !self.Ended() && + self.error.get().is_none() && + !self.is_blocked_media_element() + } - // TODO step 4 (show poster) + // https://html.spec.whatwg.org/multipage/#blocked-media-element + fn is_blocked_media_element(&self) -> bool { + self.ready_state.get() <= ReadyState::HaveCurrentData || + self.is_paused_for_user_interaction() || + self.is_paused_for_in_band_content() + } - // Step 5 - self.fire_simple_event("error"); + // https://html.spec.whatwg.org/multipage/#paused-for-user-interaction + fn is_paused_for_user_interaction(&self) -> bool { + // FIXME: we will likely be able to fill this placeholder once (if) we + // implement the MediaSession API. + false + } - // TODO step 6 (resolve pending play promises) - // TODO step 7 (delay load event) + // https://html.spec.whatwg.org/multipage/#paused-for-in-band-content + fn is_paused_for_in_band_content(&self) -> bool { + // FIXME: we will likely be able to fill this placeholder once (if) we + // implement https://github.com/servo/servo/issues/22314 + false } // https://html.spec.whatwg.org/multipage/#media-element-load-algorithm fn media_element_load_algorithm(&self) { - self.first_data_load.set(true); + // Reset the flag that signals whether loadeddata was ever fired for + // this invokation of the load algorithm. + self.fired_loadeddata_event.set(false); - // TODO Step 1 (abort resource selection algorithm instances) - - // Step 2 + // Step 1-2. self.generation_id.set(self.generation_id.get() + 1); - // TODO reject pending play promises - // Step 3 - let network_state = self.NetworkState(); - if network_state == NETWORK_LOADING || network_state == NETWORK_IDLE { - self.queue_fire_simple_event("abort"); + // Steps 3-4. + while !self.in_flight_play_promises_queue.borrow().is_empty() { + self.fulfill_in_flight_play_promises(|| ()); } - // Step 4 - if network_state != NETWORK_EMPTY { - // 4.1 - self.queue_fire_simple_event("emptied"); + let window = window_from_node(self); + let task_source = window.task_manager().media_element_task_source(); - // TODO 4.2 (abort in-progress fetch) + // Step 5. + let network_state = self.network_state.get(); + if network_state == NetworkState::Loading || network_state == NetworkState::Idle { + task_source.queue_simple_event(self.upcast(), atom!("abort"), &window); + } - // TODO 4.3 (detach media provider object) - // TODO 4.4 (forget resource tracks) + // Step 6. + if network_state != NetworkState::Empty { + // Step 6.1. + task_source.queue_simple_event(self.upcast(), atom!("emptied"), &window); - // 4.5 - if self.ready_state.get() != HAVE_NOTHING { - self.change_ready_state(HAVE_NOTHING); + // Step 6.2. + if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() { + current_fetch_context.cancel(CancelReason::Error); } - // 4.6 + // Step 6.3. + // FIXME(nox): Detach MediaSource media provider object. + + // Step 6.4. + self.AudioTracks().clear(); + self.VideoTracks().clear(); + + // Step 6.5. + if self.ready_state.get() != ReadyState::HaveNothing { + self.change_ready_state(ReadyState::HaveNothing); + } + + // Step 6.6. if !self.Paused() { + // Step 6.6.1. self.paused.set(true); + + // Step 6.6.2. + self.take_pending_play_promises(Err(Error::Abort)); + self.fulfill_in_flight_play_promises(|| ()); + } + + // Step 6.7. + if !self.seeking.get() { + self.seeking.set(false); + } + + // Step 6.8. + let queue_timeupdate_event = self.playback_position.get() != 0.; + self.playback_position.set(0.); + if queue_timeupdate_event { + task_source.queue_simple_event(self.upcast(), atom!("timeupdate"), &window); } - // TODO 4.7 (seeking) - // TODO 4.8 (playback position) - // TODO 4.9 (timeline offset) - // TODO 4.10 (duration) + + // Step 6.9. + // FIXME(nox): Set timeline offset to NaN. + + // Step 6.10. + self.duration.set(f64::NAN); } - // TODO step 5 (playback rate) - // Step 6 + // Step 7. + self.playbackRate.set(self.defaultPlaybackRate.get()); + + // Step 8. self.error.set(None); self.autoplaying.set(true); - // Step 7 + // Step 9. self.invoke_resource_selection_algorithm(); - // TODO step 8 (stop previously playing resource) + // Step 10. + // FIXME(nox): Stop playback of any previously running media resource. + } + + /// Appends a promise to the list of pending play promises. + fn push_pending_play_promise(&self, promise: &Rc<Promise>) { + self.pending_play_promises + .borrow_mut() + .push(promise.clone()); + } + + /// Takes the pending play promises. + /// + /// The result with which these promises will be fulfilled is passed here + /// and this method returns nothing because we actually just move the + /// current list of pending play promises to the + /// `in_flight_play_promises_queue` field. + /// + /// Each call to this method must be followed by a call to + /// `fulfill_in_flight_play_promises`, to actually fulfill the promises + /// which were taken and moved to the in-flight queue. + fn take_pending_play_promises(&self, result: ErrorResult) { + let pending_play_promises = + mem::replace(&mut *self.pending_play_promises.borrow_mut(), vec![]); + self.in_flight_play_promises_queue + .borrow_mut() + .push_back((pending_play_promises.into(), result)); + } + + /// Fulfills the next in-flight play promises queue after running a closure. + /// + /// See the comment on `take_pending_play_promises` for why this method + /// does not take a list of promises to fulfill. Callers cannot just pop + /// the front list off of `in_flight_play_promises_queue` and later fulfill + /// the promises because that would mean putting + /// `#[allow(unrooted_must_root)]` on even more functions, potentially + /// hiding actual safety bugs. + #[allow(unrooted_must_root)] + fn fulfill_in_flight_play_promises<F>(&self, f: F) + where + F: FnOnce(), + { + let (promises, result) = self + .in_flight_play_promises_queue + .borrow_mut() + .pop_front() + .expect("there should be at least one list of in flight play promises"); + f(); + for promise in &*promises { + match result { + Ok(ref value) => promise.resolve_native(value), + Err(ref error) => promise.reject_error(error.clone()), + } + } + } + + /// Handles insertion of `source` children. + /// + /// <https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted> + pub fn handle_source_child_insertion(&self) { + if self.upcast::<Element>().has_attribute(&local_name!("src")) { + return; + } + if self.network_state.get() != NetworkState::Empty { + return; + } + self.media_element_load_algorithm(); + } + + // https://html.spec.whatwg.org/multipage/#dom-media-seek + fn seek(&self, time: f64, _approximate_for_speed: bool) { + // Step 1. + self.show_poster.set(false); + + // Step 2. + if self.ready_state.get() == ReadyState::HaveNothing { + return; + } + + // Step 3. + // The fetch request associated with this seek already takes + // care of cancelling any previous requests. + + // Step 4. + // The flag will be cleared when the media engine tells us the seek was done. + self.seeking.set(true); + + // Step 5. + // XXX(ferjm) The rest of the steps should be run in parallel, so seeking cancelation + // can be done properly. No other browser does it yet anyway. + + // Step 6. + let time = f64::min(time, self.Duration()); + + // Step 7. + let time = f64::max(time, 0.); + + // Step 8. + // XXX(ferjm) seekable attribute: we need to get the information about + // what's been decoded and buffered so far from servo-media + // and add the seekable attribute as a TimeRange. + if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() { + if !current_fetch_context.is_seekable() { + self.seeking.set(false); + return; + } + } + + // Step 9. + // servo-media with gstreamer does not support inaccurate seeking for now. + + // Step 10. + let window = window_from_node(self); + let task_source = window.task_manager().media_element_task_source(); + task_source.queue_simple_event(self.upcast(), atom!("seeking"), &window); + + // Step 11. + if let Some(ref player) = *self.player.borrow() { + if let Err(e) = player.lock().unwrap().seek(time) { + eprintln!("Seek error {:?}", e); + } + } + + // The rest of the steps are handled when the media engine signals a + // ready state change or otherwise satisfies seek completion and signals + // a position change. + } + + // https://html.spec.whatwg.org/multipage/#dom-media-seek + fn seek_end(&self) { + // Step 14. + self.seeking.set(false); + + // Step 15. + self.time_marches_on(); + + // Step 16. + let window = window_from_node(self); + let task_source = window.task_manager().media_element_task_source(); + task_source.queue_simple_event(self.upcast(), atom!("timeupdate"), &window); + + // Step 17. + task_source.queue_simple_event(self.upcast(), atom!("seeked"), &window); + } + + /// https://html.spec.whatwg.org/multipage/#poster-frame + pub fn process_poster_response(&self, image: ImageResponse) { + if !self.show_poster.get() { + return; + } + + // Step 6. + if let ImageResponse::Loaded(image, _) = image { + self.video_renderer + .lock() + .unwrap() + .render_poster_frame(image); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + if pref!(media.testing.enabled) { + let window = window_from_node(self); + let task_source = window.task_manager().media_element_task_source(); + task_source.queue_simple_event(self.upcast(), atom!("postershown"), &window); + } else { + return; + } + } + } + + fn setup_media_player(&self, resource: &Resource) -> Result<(), ()> { + let stream_type = match *resource { + Resource::Object => { + if let Some(ref src_object) = *self.src_object.borrow() { + match src_object { + SrcObject::MediaStream(_) => StreamType::Stream, + _ => StreamType::Seekable, + } + } else { + return Err(()); + } + }, + _ => StreamType::Seekable, + }; + + let window = window_from_node(self); + let (action_sender, action_receiver) = ipc::channel::<PlayerEvent>().unwrap(); + let video_renderer: Option<Arc<Mutex<dyn VideoFrameRenderer>>> = match self.media_type_id() + { + HTMLMediaElementTypeId::HTMLAudioElement => None, + HTMLMediaElementTypeId::HTMLVideoElement => Some(self.video_renderer.clone()), + }; + + let audio_renderer = self.audio_renderer.borrow().as_ref().map(|r| r.clone()); + + let pipeline_id = window.pipeline_id(); + let client_context_id = + ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get()); + let player = ServoMedia::get().unwrap().create_player( + &client_context_id, + stream_type, + action_sender, + video_renderer, + audio_renderer, + Box::new(window.get_player_context()), + ); + + *self.player.borrow_mut() = Some(player); + + let trusted_node = Trusted::new(self); + let (task_source, canceller) = window + .task_manager() + .media_element_task_source_with_canceller(); + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + let event = message.to().unwrap(); + trace!("Player event {:?}", event); + let this = trusted_node.clone(); + if let Err(err) = task_source.queue_with_canceller( + task!(handle_player_event: move || { + this.root().handle_player_event(&event); + }), + &canceller, + ) { + warn!("Could not queue player event handler task {:?}", err); + } + }), + ); + + // GLPlayer thread setup + let (player_id, image_receiver) = window + .get_player_context() + .glplayer_chan + .map(|pipeline| { + let (image_sender, image_receiver) = + glplayer_channel::<GLPlayerMsgForward>().unwrap(); + pipeline + .channel() + .send(GLPlayerMsg::RegisterPlayer(image_sender)) + .unwrap(); + match image_receiver.recv().unwrap() { + GLPlayerMsgForward::PlayerId(id) => (id, Some(image_receiver)), + _ => unreachable!(), + } + }) + .unwrap_or((0, None)); + + self.id.set(player_id); + self.video_renderer.lock().unwrap().player_id = Some(player_id); + + if let Some(image_receiver) = image_receiver { + let trusted_node = Trusted::new(self); + let (task_source, canceller) = window + .task_manager() + .media_element_task_source_with_canceller(); + ROUTER.add_route( + image_receiver.to_opaque(), + Box::new(move |message| { + let msg = message.to().unwrap(); + let this = trusted_node.clone(); + if let Err(err) = task_source.queue_with_canceller( + task!(handle_glplayer_message: move || { + trace!("GLPlayer message {:?}", msg); + let video_renderer = this.root().video_renderer.clone(); + + match msg { + GLPlayerMsgForward::Lock(sender) => { + video_renderer + .lock() + .unwrap() + .current_frame_holder + .as_mut() + .map(|holder| { + holder.lock(); + sender.send(holder.get()).unwrap(); + }); + }, + GLPlayerMsgForward::Unlock() => { + video_renderer + .lock() + .unwrap() + .current_frame_holder + .as_mut() + .map(|holder| holder.unlock()); + }, + _ => (), + } + }), + &canceller, + ) { + warn!("Could not queue GL player message handler task {:?}", err); + } + }), + ); + } + + Ok(()) + } + + pub fn set_audio_track(&self, idx: usize, enabled: bool) { + if let Some(ref player) = *self.player.borrow() { + if let Err(err) = player.lock().unwrap().set_audio_track(idx as i32, enabled) { + warn!("Could not set audio track {:#?}", err); + } + } + } + + pub fn set_video_track(&self, idx: usize, enabled: bool) { + if let Some(ref player) = *self.player.borrow() { + if let Err(err) = player.lock().unwrap().set_video_track(idx as i32, enabled) { + warn!("Could not set video track {:#?}", err); + } + } + } + + fn handle_player_event(&self, event: &PlayerEvent) { + match *event { + PlayerEvent::EndOfStream => { + // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list + // => "If the media data can be fetched but is found by inspection to be in + // an unsupported format, or can otherwise not be rendered at all" + if self.ready_state.get() < ReadyState::HaveMetadata { + self.queue_dedicated_media_source_failure_steps(); + } else { + // https://html.spec.whatwg.org/multipage/#reaches-the-end + match self.direction_of_playback() { + PlaybackDirection::Forwards => { + // Step 1. + if self.Loop() { + self.seek( + self.earliest_possible_position(), + /* approximate_for_speed*/ false, + ); + } else { + // Step 2. + // The **ended playback** condition is implemented inside of + // the HTMLMediaElementMethods::Ended method + + // Step 3. + let window = window_from_node(self); + let this = Trusted::new(self); + + let _ = window.task_manager().media_element_task_source().queue( + task!(reaches_the_end_steps: move || { + let this = this.root(); + // Step 3.1. + this.upcast::<EventTarget>().fire_event(atom!("timeupdate")); + + // Step 3.2. + if this.Ended() && !this.Paused() { + // Step 3.2.1. + this.paused.set(true); + + // Step 3.2.2. + this.upcast::<EventTarget>().fire_event(atom!("pause")); + + // Step 3.2.3. + this.take_pending_play_promises(Err(Error::Abort)); + this.fulfill_in_flight_play_promises(|| ()); + } + + // Step 3.3. + this.upcast::<EventTarget>().fire_event(atom!("ended")); + }), + window.upcast(), + ); + } + }, + + PlaybackDirection::Backwards => { + if self.playback_position.get() <= self.earliest_possible_position() { + let window = window_from_node(self); + + window + .task_manager() + .media_element_task_source() + .queue_simple_event(self.upcast(), atom!("ended"), &window); + } + }, + } + } + }, + PlayerEvent::Error(ref error) => { + error!("Player error: {:?}", error); + self.error.set(Some(&*MediaError::new( + &*window_from_node(self), + MEDIA_ERR_DECODE, + ))); + self.upcast::<EventTarget>().fire_event(atom!("error")); + }, + PlayerEvent::VideoFrameUpdated => { + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + }, + PlayerEvent::MetadataUpdated(ref metadata) => { + // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list + // => If the media resource is found to have an audio track + if !metadata.audio_tracks.is_empty() { + for (i, _track) in metadata.audio_tracks.iter().enumerate() { + // Step 1. + let kind = match i { + 0 => DOMString::from("main"), + _ => DOMString::new(), + }; + let window = window_from_node(self); + let audio_track = AudioTrack::new( + &window, + DOMString::new(), + kind, + DOMString::new(), + DOMString::new(), + Some(&*self.AudioTracks()), + ); + + // Steps 2. & 3. + self.AudioTracks().add(&audio_track); + + // Step 4 + if let Some(servo_url) = self.resource_url.borrow().as_ref() { + let fragment = MediaFragmentParser::from(servo_url); + if let Some(id) = fragment.id() { + if audio_track.id() == DOMString::from(id) { + self.AudioTracks() + .set_enabled(self.AudioTracks().len() - 1, true); + } + } + + if fragment.tracks().contains(&audio_track.kind().into()) { + self.AudioTracks() + .set_enabled(self.AudioTracks().len() - 1, true); + } + } + + // Step 5. & 6, + if self.AudioTracks().enabled_index().is_none() { + self.AudioTracks() + .set_enabled(self.AudioTracks().len() - 1, true); + } + + // Steps 7. + let event = TrackEvent::new( + &self.global(), + atom!("addtrack"), + false, + false, + &Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(audio_track)), + ); + + event.upcast::<Event>().fire(self.upcast::<EventTarget>()); + } + } + + // => If the media resource is found to have a video track + if !metadata.video_tracks.is_empty() { + for (i, _track) in metadata.video_tracks.iter().enumerate() { + // Step 1. + let kind = match i { + 0 => DOMString::from("main"), + _ => DOMString::new(), + }; + let window = window_from_node(self); + let video_track = VideoTrack::new( + &window, + DOMString::new(), + kind, + DOMString::new(), + DOMString::new(), + Some(&*self.VideoTracks()), + ); + + // Steps 2. & 3. + self.VideoTracks().add(&video_track); + + // Step 4. + if let Some(track) = self.VideoTracks().item(0) { + if let Some(servo_url) = self.resource_url.borrow().as_ref() { + let fragment = MediaFragmentParser::from(servo_url); + if let Some(id) = fragment.id() { + if track.id() == DOMString::from(id) { + self.VideoTracks().set_selected(0, true); + } + } else if fragment.tracks().contains(&track.kind().into()) { + self.VideoTracks().set_selected(0, true); + } + } + } + + // Step 5. & 6. + if self.VideoTracks().selected_index().is_none() { + self.VideoTracks() + .set_selected(self.VideoTracks().len() - 1, true); + } + + // Steps 7. + let event = TrackEvent::new( + &self.global(), + atom!("addtrack"), + false, + false, + &Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(video_track)), + ); + + event.upcast::<Event>().fire(self.upcast::<EventTarget>()); + } + } + + // => "Once enough of the media data has been fetched to determine the duration..." + // Step 1. + // servo-media owns the media timeline. + + // Step 2. + // XXX(ferjm) Update the timeline offset. + + // Step 3. + self.playback_position.set(0.); + + // Step 4. + let previous_duration = self.duration.get(); + if let Some(duration) = metadata.duration { + self.duration.set(duration.as_secs() as f64); + } else { + self.duration.set(f64::INFINITY); + } + if previous_duration != self.duration.get() { + let window = window_from_node(self); + let task_source = window.task_manager().media_element_task_source(); + task_source.queue_simple_event(self.upcast(), atom!("durationchange"), &window); + } + + // Step 5. + if self.is::<HTMLVideoElement>() { + let video_elem = self.downcast::<HTMLVideoElement>().unwrap(); + if video_elem.get_video_width() != metadata.width || + video_elem.get_video_height() != metadata.height + { + video_elem.set_video_width(metadata.width); + video_elem.set_video_height(metadata.height); + let window = window_from_node(self); + let task_source = window.task_manager().media_element_task_source(); + task_source.queue_simple_event(self.upcast(), atom!("resize"), &window); + } + } + + // Step 6. + self.change_ready_state(ReadyState::HaveMetadata); + + // Step 7. + let mut jumped = false; + + // Step 8. + if self.default_playback_start_position.get() > 0. { + self.seek( + self.default_playback_start_position.get(), + /* approximate_for_speed*/ false, + ); + jumped = true; + } + + // Step 9. + self.default_playback_start_position.set(0.); + + // Steps 10 and 11. + if let Some(servo_url) = self.resource_url.borrow().as_ref() { + let fragment = MediaFragmentParser::from(servo_url); + if let Some(start) = fragment.start() { + if start > 0. && start < self.duration.get() { + self.playback_position.set(start); + if !jumped { + self.seek(self.playback_position.get(), false) + } + } + } + } + + // Step 12 & 13 are already handled by the earlier media track processing. + + // We wait until we have metadata to render the controls, so we render them + // with the appropriate size. + if self.Controls() { + self.render_controls(); + } + + let global = self.global(); + let window = global.as_window(); + + // Update the media session metadata title with the obtained metadata. + window.Navigator().MediaSession().update_title( + metadata + .title + .clone() + .unwrap_or(window.get_url().into_string()), + ); + }, + PlayerEvent::NeedData => { + // The player needs more data. + // If we already have a valid fetch request, we do nothing. + // Otherwise, if we have no request and the previous request was + // cancelled because we got an EnoughData event, we restart + // fetching where we left. + if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() { + match current_fetch_context.cancel_reason() { + Some(ref reason) if *reason == CancelReason::Backoff => { + // XXX(ferjm) Ideally we should just create a fetch request from + // where we left. But keeping track of the exact next byte that the + // media backend expects is not the easiest task, so I'm simply + // seeking to the current playback position for now which will create + // a new fetch request for the last rendered frame. + self.seek(self.playback_position.get(), false) + }, + _ => (), + } + } + }, + PlayerEvent::EnoughData => { + // The player has enough data and it is asking us to stop pushing + // bytes, so we cancel the ongoing fetch request iff we are able + // to restart it from where we left. Otherwise, we continue the + // current fetch request, assuming that some frames will be dropped. + if let Some(ref mut current_fetch_context) = + *self.current_fetch_context.borrow_mut() + { + if current_fetch_context.is_seekable() { + current_fetch_context.cancel(CancelReason::Backoff); + } + } + }, + PlayerEvent::PositionChanged(position) => { + let position = position as f64; + let _ = self + .played + .borrow_mut() + .add(self.playback_position.get(), position); + self.playback_position.set(position); + self.time_marches_on(); + let media_position_state = + MediaPositionState::new(self.duration.get(), self.playbackRate.get(), position); + debug!( + "Sending media session event set position state {:?}", + media_position_state + ); + self.send_media_session_event(MediaSessionEvent::SetPositionState( + media_position_state, + )); + }, + PlayerEvent::SeekData(p, ref seek_lock) => { + self.fetch_request(Some(p), Some(seek_lock.clone())); + }, + PlayerEvent::SeekDone(_) => { + // Continuation of + // https://html.spec.whatwg.org/multipage/#dom-media-seek + + // Step 13. + let task = MediaElementMicrotask::SeekedTask { + elem: DomRoot::from_ref(self), + generation_id: self.generation_id.get(), + }; + ScriptThread::await_stable_state(Microtask::MediaElement(task)); + }, + PlayerEvent::StateChanged(ref state) => { + let mut media_session_playback_state = MediaSessionPlaybackState::None_; + match *state { + PlaybackState::Paused => { + media_session_playback_state = MediaSessionPlaybackState::Paused; + if self.ready_state.get() == ReadyState::HaveMetadata { + self.change_ready_state(ReadyState::HaveEnoughData); + } + }, + PlaybackState::Playing => { + media_session_playback_state = MediaSessionPlaybackState::Playing; + }, + PlaybackState::Buffering => { + // Do not send the media session playback state change event + // in this case as a None_ state is expected to clean up the + // session. + return; + }, + _ => {}, + }; + debug!( + "Sending media session event playback state changed to {:?}", + media_session_playback_state + ); + self.send_media_session_event(MediaSessionEvent::PlaybackStateChange( + media_session_playback_state, + )); + }, + } + } + + // https://html.spec.whatwg.org/multipage/#earliest-possible-position + fn earliest_possible_position(&self) -> f64 { + self.played + .borrow() + .start(0) + .unwrap_or_else(|_| self.playback_position.get()) + } + + fn render_controls(&self) { + let element = self.htmlelement.upcast::<Element>(); + if self.ready_state.get() < ReadyState::HaveMetadata || element.is_shadow_host() { + // Bail out if we have no metadata yet or + // if we are already showing the controls. + return; + } + let shadow_root = element.attach_shadow(IsUserAgentWidget::Yes).unwrap(); + let document = document_from_node(self); + let script = HTMLScriptElement::new( + local_name!("script"), + None, + &document, + ElementCreator::ScriptCreated, + ); + let mut media_controls_script = resources::read_string(EmbedderResource::MediaControlsJS); + // This is our hacky way to temporarily workaround the lack of a privileged + // JS context. + // The media controls UI accesses the document.servoGetMediaControls(id) API + // to get an instance to the media controls ShadowRoot. + // `id` needs to match the internally generated UUID assigned to a media element. + let id = document.register_media_controls(&shadow_root); + let media_controls_script = media_controls_script.as_mut_str().replace("@@@id@@@", &id); + *self.media_controls_id.borrow_mut() = Some(id); + script + .upcast::<Node>() + .SetTextContent(Some(DOMString::from(media_controls_script))); + if let Err(e) = shadow_root + .upcast::<Node>() + .AppendChild(&*script.upcast::<Node>()) + { + warn!("Could not render media controls {:?}", e); + return; + } + + let media_controls_style = resources::read_string(EmbedderResource::MediaControlsCSS); + let style = HTMLStyleElement::new( + local_name!("script"), + None, + &document, + ElementCreator::ScriptCreated, + ); + style + .upcast::<Node>() + .SetTextContent(Some(DOMString::from(media_controls_style))); + + if let Err(e) = shadow_root + .upcast::<Node>() + .AppendChild(&*style.upcast::<Node>()) + { + warn!("Could not render media controls {:?}", e); + } + + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + + fn remove_controls(&self) { + if let Some(id) = self.media_controls_id.borrow_mut().take() { + document_from_node(self).unregister_media_controls(&id); + } + } + + pub fn get_current_frame(&self) -> Option<VideoFrame> { + match self.video_renderer.lock().unwrap().current_frame_holder { + Some(ref holder) => Some(holder.get_frame()), + None => return None, + } + } + + /// By default the audio is rendered through the audio sink automatically + /// selected by the servo-media Player instance. However, in some cases, like + /// the WebAudio MediaElementAudioSourceNode, we need to set a custom audio + /// renderer. + pub fn set_audio_renderer(&self, audio_renderer: Arc<Mutex<dyn AudioRenderer>>) { + *self.audio_renderer.borrow_mut() = Some(audio_renderer); + if let Some(ref player) = *self.player.borrow() { + if let Err(e) = player.lock().unwrap().stop() { + eprintln!("Could not stop player {:?}", e); + } + self.media_element_load_algorithm(); + } + } + + fn send_media_session_event(&self, event: MediaSessionEvent) { + let global = self.global(); + let media_session = global.as_window().Navigator().MediaSession(); + + media_session.register_media_instance(&self); + + media_session.send_event(event); + } + + pub fn set_duration(&self, duration: f64) { + self.duration.set(duration); + } + + pub fn reset(&self) { + if let Some(ref player) = *self.player.borrow() { + if let Err(e) = player.lock().unwrap().stop() { + eprintln!("Could not stop player {:?}", e); + } + } + } +} + +// XXX Placeholder for [https://github.com/servo/servo/issues/22293] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +enum PlaybackDirection { + Forwards, + #[allow(dead_code)] + Backwards, +} + +// XXX Placeholder implementations for: +// +// - https://github.com/servo/servo/issues/22293 +impl HTMLMediaElement { + // https://github.com/servo/servo/issues/22293 + fn direction_of_playback(&self) -> PlaybackDirection { + PlaybackDirection::Forwards + } +} + +impl Drop for HTMLMediaElement { + fn drop(&mut self) { + if let Some(ref pipeline) = self.player_context.glplayer_chan { + if let Err(err) = pipeline + .channel() + .send(GLPlayerMsg::UnregisterPlayer(self.id.get())) + { + warn!("GLPlayer disappeared!: {:?}", err); + } + } + + self.remove_controls(); } } impl HTMLMediaElementMethods for HTMLMediaElement { // https://html.spec.whatwg.org/multipage/#dom-media-networkstate fn NetworkState(&self) -> u16 { - self.network_state.get() + self.network_state.get() as u16 } // https://html.spec.whatwg.org/multipage/#dom-media-readystate fn ReadyState(&self) -> u16 { - self.ready_state.get() + self.ready_state.get() as u16 } // https://html.spec.whatwg.org/multipage/#dom-media-autoplay @@ -654,10 +2009,80 @@ impl HTMLMediaElementMethods for HTMLMediaElement { // https://html.spec.whatwg.org/multipage/#dom-media-autoplay make_bool_setter!(SetAutoplay, "autoplay"); + // https://html.spec.whatwg.org/multipage/#attr-media-loop + make_bool_getter!(Loop, "loop"); + // https://html.spec.whatwg.org/multipage/#attr-media-loop + make_bool_setter!(SetLoop, "loop"); + + // https://html.spec.whatwg.org/multipage/#dom-media-defaultmuted + make_bool_getter!(DefaultMuted, "muted"); + // https://html.spec.whatwg.org/multipage/#dom-media-defaultmuted + make_bool_setter!(SetDefaultMuted, "muted"); + + // https://html.spec.whatwg.org/multipage/#dom-media-controls + make_bool_getter!(Controls, "controls"); + // https://html.spec.whatwg.org/multipage/#dom-media-controls + make_bool_setter!(SetControls, "controls"); + // https://html.spec.whatwg.org/multipage/#dom-media-src make_url_getter!(Src, "src"); + // https://html.spec.whatwg.org/multipage/#dom-media-src - make_setter!(SetSrc, "src"); + make_url_setter!(SetSrc, "src"); + + // https://html.spec.whatwg.org/multipage/#dom-media-crossOrigin + fn GetCrossOrigin(&self) -> Option<DOMString> { + reflect_cross_origin_attribute(self.upcast::<Element>()) + } + // https://html.spec.whatwg.org/multipage/#dom-media-crossOrigin + fn SetCrossOrigin(&self, value: Option<DOMString>) { + set_cross_origin_attribute(self.upcast::<Element>(), value); + } + + // https://html.spec.whatwg.org/multipage/#dom-media-muted + fn Muted(&self) -> bool { + self.muted.get() + } + + // https://html.spec.whatwg.org/multipage/#dom-media-muted + fn SetMuted(&self, value: bool) { + if self.muted.get() == value { + return; + } + + if let Some(ref player) = *self.player.borrow() { + let _ = player.lock().unwrap().set_mute(value); + } + + self.muted.set(value); + let window = window_from_node(self); + window + .task_manager() + .media_element_task_source() + .queue_simple_event(self.upcast(), atom!("volumechange"), &window); + if !self.is_allowed_to_play() { + self.internal_pause_steps(); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-media-srcobject + fn GetSrcObject(&self) -> Option<MediaStreamOrBlob> { + match *self.src_object.borrow() { + Some(ref src_object) => Some(match src_object { + SrcObject::Blob(blob) => MediaStreamOrBlob::Blob(DomRoot::from_ref(&*blob)), + SrcObject::MediaStream(stream) => { + MediaStreamOrBlob::MediaStream(DomRoot::from_ref(&*stream)) + }, + }), + None => None, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-media-srcobject + fn SetSrcObject(&self, value: Option<MediaStreamOrBlob>) { + *self.src_object.borrow_mut() = value.map(|value| value.into()); + self.media_element_load_algorithm(); + } // https://html.spec.whatwg.org/multipage/#attr-media-preload // Missing value default is user-agent defined. @@ -666,8 +2091,8 @@ impl HTMLMediaElementMethods for HTMLMediaElement { make_setter!(SetPreload, "preload"); // https://html.spec.whatwg.org/multipage/#dom-media-currentsrc - fn CurrentSrc(&self) -> DOMString { - DOMString::from(self.current_src.borrow().clone()) + fn CurrentSrc(&self) -> USVString { + USVString(self.current_src.borrow().clone()) } // https://html.spec.whatwg.org/multipage/#dom-media-load @@ -676,75 +2101,113 @@ impl HTMLMediaElementMethods for HTMLMediaElement { } // https://html.spec.whatwg.org/multipage/#dom-navigator-canplaytype - fn CanPlayType(&self, _type_: DOMString) -> CanPlayTypeResult { - // TODO: application/octet-stream - CanPlayTypeResult::Maybe + fn CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult { + match ServoMedia::get().unwrap().can_play_type(&type_) { + SupportsMediaType::No => CanPlayTypeResult::_empty, + SupportsMediaType::Maybe => CanPlayTypeResult::Maybe, + SupportsMediaType::Probably => CanPlayTypeResult::Probably, + } } // https://html.spec.whatwg.org/multipage/#dom-media-error - fn GetError(&self) -> Option<Root<MediaError>> { + fn GetError(&self) -> Option<DomRoot<MediaError>> { self.error.get() } // https://html.spec.whatwg.org/multipage/#dom-media-play - fn Play(&self) { - // TODO step 1 - - // Step 2 - if self.error.get().map_or(false, |e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) { - // TODO return rejected promise - return; + fn Play(&self, comp: InRealm) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&self.global(), comp); + // Step 1. + // FIXME(nox): Reject promise if not allowed to play. + + // Step 2. + if self + .error + .get() + .map_or(false, |e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) + { + promise.reject_error(Error::NotSupported); + return promise; } - // TODO step 3 + // Step 3. + self.push_pending_play_promise(&promise); - // Step 4 - if self.network_state.get() == NETWORK_EMPTY { + // Step 4. + if self.network_state.get() == NetworkState::Empty { self.invoke_resource_selection_algorithm(); } - // TODO step 5 (seek backwards) - - // TODO step 6 (media controller) + // Step 5. + if self.Ended() && self.direction_of_playback() == PlaybackDirection::Forwards { + self.seek( + self.earliest_possible_position(), + /* approximate_for_speed */ false, + ); + } let state = self.ready_state.get(); - // Step 7 + let window = window_from_node(self); + // FIXME(nox): Why are errors silenced here? + let task_source = window.task_manager().media_element_task_source(); if self.Paused() { - // 7.1 + // Step 6.1. self.paused.set(false); - // TODO 7.2 (show poster) - - // 7.3 - self.queue_fire_simple_event("play"); + // Step 6.2. + if self.show_poster.get() { + self.show_poster.set(false); + self.time_marches_on(); + } - // 7.4 - if state == HAVE_NOTHING || - state == HAVE_METADATA || - state == HAVE_CURRENT_DATA { - self.queue_fire_simple_event("waiting"); - } else { - self.queue_notify_about_playing(); + // Step 6.3. + task_source.queue_simple_event(self.upcast(), atom!("play"), &window); + + // Step 6.4. + match state { + ReadyState::HaveNothing | + ReadyState::HaveMetadata | + ReadyState::HaveCurrentData => { + task_source.queue_simple_event(self.upcast(), atom!("waiting"), &window); + }, + ReadyState::HaveFutureData | ReadyState::HaveEnoughData => { + self.notify_about_playing(); + }, } - } - // Step 8 - else if state == HAVE_FUTURE_DATA || state == HAVE_ENOUGH_DATA { - // TODO resolve pending play promises + } else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData { + // Step 7. + self.take_pending_play_promises(Ok(())); + let this = Trusted::new(self); + let generation_id = self.generation_id.get(); + task_source + .queue( + task!(resolve_pending_play_promises: move || { + let this = this.root(); + if generation_id != this.generation_id.get() { + return; + } + + this.fulfill_in_flight_play_promises(|| { + this.play_media(); + }); + }), + window.upcast(), + ) + .unwrap(); } - // Step 9 + // Step 8. self.autoplaying.set(false); - // TODO step 10 (media controller) - - // TODO return promise + // Step 9. + promise } // https://html.spec.whatwg.org/multipage/#dom-media-pause fn Pause(&self) { // Step 1 - if self.network_state.get() == NETWORK_EMPTY { + if self.network_state.get() == NetworkState::Empty { self.invoke_resource_selection_algorithm(); } @@ -756,22 +2219,223 @@ impl HTMLMediaElementMethods for HTMLMediaElement { fn Paused(&self) -> bool { self.paused.get() } + + /// https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate + fn GetDefaultPlaybackRate(&self) -> Fallible<Finite<f64>> { + Ok(Finite::wrap(self.defaultPlaybackRate.get())) + } + + /// https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate + fn SetDefaultPlaybackRate(&self, value: Finite<f64>) -> ErrorResult { + let min_allowed = -64.0; + let max_allowed = 64.0; + if *value < min_allowed || *value > max_allowed { + return Err(Error::NotSupported); + } + + if *value != self.defaultPlaybackRate.get() { + self.defaultPlaybackRate.set(*value); + self.queue_ratechange_event(); + } + + Ok(()) + } + + /// https://html.spec.whatwg.org/multipage/#dom-media-playbackrate + fn GetPlaybackRate(&self) -> Fallible<Finite<f64>> { + Ok(Finite::wrap(self.playbackRate.get())) + } + + /// https://html.spec.whatwg.org/multipage/#dom-media-playbackrate + fn SetPlaybackRate(&self, value: Finite<f64>) -> ErrorResult { + let min_allowed = -64.0; + let max_allowed = 64.0; + if *value < min_allowed || *value > max_allowed { + return Err(Error::NotSupported); + } + + if *value != self.playbackRate.get() { + self.playbackRate.set(*value); + self.queue_ratechange_event(); + if self.is_potentially_playing() { + if let Some(ref player) = *self.player.borrow() { + if let Err(e) = player.lock().unwrap().set_rate(*value) { + warn!("Could not set the playback rate {:?}", e); + } + } + } + } + + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-media-duration + fn Duration(&self) -> f64 { + self.duration.get() + } + + // https://html.spec.whatwg.org/multipage/#dom-media-currenttime + fn CurrentTime(&self) -> Finite<f64> { + Finite::wrap(if self.default_playback_start_position.get() != 0. { + self.default_playback_start_position.get() + } else { + self.playback_position.get() + }) + } + + // https://html.spec.whatwg.org/multipage/#dom-media-currenttime + fn SetCurrentTime(&self, time: Finite<f64>) { + if self.ready_state.get() == ReadyState::HaveNothing { + self.default_playback_start_position.set(*time); + } else { + self.playback_position.set(*time); + self.seek(*time, /* approximate_for_speed */ false); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-media-seeking + fn Seeking(&self) -> bool { + self.seeking.get() + } + + // https://html.spec.whatwg.org/multipage/#ended-playback + fn Ended(&self) -> bool { + if self.ready_state.get() < ReadyState::HaveMetadata { + return false; + } + + let playback_pos = self.playback_position.get(); + + match self.direction_of_playback() { + PlaybackDirection::Forwards => playback_pos >= self.Duration() && !self.Loop(), + PlaybackDirection::Backwards => playback_pos <= self.earliest_possible_position(), + } + } + + // https://html.spec.whatwg.org/multipage/#dom-media-fastseek + fn FastSeek(&self, time: Finite<f64>) { + self.seek(*time, /* approximate_for_speed */ true); + } + + // https://html.spec.whatwg.org/multipage/#dom-media-played + fn Played(&self) -> DomRoot<TimeRanges> { + TimeRanges::new(self.global().as_window(), self.played.borrow().clone()) + } + + // https://html.spec.whatwg.org/multipage/#dom-media-buffered + fn Buffered(&self) -> DomRoot<TimeRanges> { + let mut buffered = TimeRangesContainer::new(); + if let Some(ref player) = *self.player.borrow() { + if let Ok(ranges) = player.lock().unwrap().buffered() { + for range in ranges { + let _ = buffered.add(range.start as f64, range.end as f64); + } + } + } + TimeRanges::new(self.global().as_window(), buffered) + } + + // https://html.spec.whatwg.org/multipage/#dom-media-audiotracks + fn AudioTracks(&self) -> DomRoot<AudioTrackList> { + let window = window_from_node(self); + self.audio_tracks_list + .or_init(|| AudioTrackList::new(&window, &[], Some(self))) + } + + // https://html.spec.whatwg.org/multipage/#dom-media-videotracks + fn VideoTracks(&self) -> DomRoot<VideoTrackList> { + let window = window_from_node(self); + self.video_tracks_list + .or_init(|| VideoTrackList::new(&window, &[], Some(self))) + } + + // https://html.spec.whatwg.org/multipage/#dom-media-texttracks + fn TextTracks(&self) -> DomRoot<TextTrackList> { + let window = window_from_node(self); + self.text_tracks_list + .or_init(|| TextTrackList::new(&window, &[])) + } + + // https://html.spec.whatwg.org/multipage/#dom-media-addtexttrack + fn AddTextTrack( + &self, + kind: TextTrackKind, + label: DOMString, + language: DOMString, + ) -> DomRoot<TextTrack> { + let window = window_from_node(self); + // Step 1 & 2 + // FIXME(#22314, dlrobertson) set the ready state to Loaded + let track = TextTrack::new( + &window, + "".into(), + kind, + label, + language, + TextTrackMode::Hidden, + None, + ); + // Step 3 & 4 + self.TextTracks().add(&track); + // Step 5 + DomRoot::from_ref(&track) + } + + // https://html.spec.whatwg.org/multipage/#dom-media-volume + fn GetVolume(&self) -> Fallible<Finite<f64>> { + Ok(Finite::wrap(self.volume.get())) + } + + // https://html.spec.whatwg.org/multipage/#dom-media-volume + fn SetVolume(&self, value: Finite<f64>) -> ErrorResult { + let minimum_volume = 0.0; + let maximum_volume = 1.0; + if *value < minimum_volume || *value > maximum_volume { + return Err(Error::IndexSize); + } + + if *value != self.volume.get() { + self.volume.set(*value); + + let window = window_from_node(self); + window + .task_manager() + .media_element_task_source() + .queue_simple_event(self.upcast(), atom!("volumechange"), &window); + if !self.is_allowed_to_play() { + self.internal_pause_steps(); + } + } + + Ok(()) + } } impl VirtualMethods for HTMLMediaElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); match attr.local_name() { + &local_name!("muted") => { + self.SetMuted(mutation.new_value(attr).is_some()); + }, &local_name!("src") => { + if mutation.new_value(attr).is_none() { + return; + } + self.media_element_load_algorithm(); + }, + &local_name!("controls") => { if mutation.new_value(attr).is_some() { - self.media_element_load_algorithm(); + self.render_controls(); + } else { + self.remove_controls(); } - } + }, _ => (), }; } @@ -780,107 +2444,434 @@ impl VirtualMethods for HTMLMediaElement { fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); - if context.tree_in_doc { - ScriptThread::await_stable_state(PauseIfNotInDocumentTask::new(self)); + if context.tree_connected { + let task = MediaElementMicrotask::PauseIfNotInDocumentTask { + elem: DomRoot::from_ref(self), + }; + ScriptThread::await_stable_state(Microtask::MediaElement(task)); } } } -struct FireSimpleEventTask { - elem: Trusted<HTMLMediaElement>, - type_: &'static str, +pub trait LayoutHTMLMediaElementHelpers { + fn data(self) -> HTMLMediaData; } -impl FireSimpleEventTask { - fn new(target: &HTMLMediaElement, type_: &'static str) -> FireSimpleEventTask { - FireSimpleEventTask { - elem: Trusted::new(target), - type_: type_, +impl LayoutHTMLMediaElementHelpers for LayoutDom<'_, HTMLMediaElement> { + #[allow(unsafe_code)] + fn data(self) -> HTMLMediaData { + let media = unsafe { &*self.unsafe_get() }; + HTMLMediaData { + current_frame: media.video_renderer.lock().unwrap().current_frame.clone(), } } } -impl Runnable for FireSimpleEventTask { - fn name(&self) -> &'static str { "FireSimpleEventTask" } +#[derive(JSTraceable, MallocSizeOf)] +pub enum MediaElementMicrotask { + ResourceSelectionTask { + elem: DomRoot<HTMLMediaElement>, + generation_id: u32, + base_url: ServoUrl, + }, + PauseIfNotInDocumentTask { + elem: DomRoot<HTMLMediaElement>, + }, + SeekedTask { + elem: DomRoot<HTMLMediaElement>, + generation_id: u32, + }, +} - fn handler(self: Box<FireSimpleEventTask>) { - let elem = self.elem.root(); - elem.fire_simple_event(self.type_); +impl MicrotaskRunnable for MediaElementMicrotask { + fn handler(&self) { + match self { + &MediaElementMicrotask::ResourceSelectionTask { + ref elem, + generation_id, + ref base_url, + } => { + if generation_id == elem.generation_id.get() { + elem.resource_selection_algorithm_sync(base_url.clone()); + } + }, + &MediaElementMicrotask::PauseIfNotInDocumentTask { ref elem } => { + if !elem.upcast::<Node>().is_connected() { + elem.internal_pause_steps(); + } + }, + &MediaElementMicrotask::SeekedTask { + ref elem, + generation_id, + } => { + if generation_id == elem.generation_id.get() { + elem.seek_end(); + } + }, + } } } -struct ResourceSelectionTask { - elem: Trusted<HTMLMediaElement>, - base_url: ServoUrl, +enum Resource { + Object, + Url(ServoUrl), } -impl ResourceSelectionTask { - fn new(elem: &HTMLMediaElement, url: ServoUrl) -> ResourceSelectionTask { - ResourceSelectionTask { - elem: Trusted::new(elem), - base_url: url, - } - } +/// Indicates the reason why a fetch request was cancelled. +#[derive(Debug, MallocSizeOf, PartialEq)] +enum CancelReason { + /// We were asked to stop pushing data to the player. + Backoff, + /// An error ocurred while fetching the media data. + Error, + /// A new request overrode this one. + Overridden, } -impl Runnable for ResourceSelectionTask { - fn name(&self) -> &'static str { "ResourceSelectionTask" } +#[derive(MallocSizeOf)] +pub struct HTMLMediaElementFetchContext { + /// Some if the request has been cancelled. + cancel_reason: Option<CancelReason>, + /// Indicates whether the fetched stream is seekable. + is_seekable: bool, + /// Fetch canceller. Allows cancelling the current fetch request by + /// manually calling its .cancel() method or automatically on Drop. + fetch_canceller: FetchCanceller, +} - fn handler(self: Box<ResourceSelectionTask>) { - self.elem.root().resource_selection_algorithm_sync(self.base_url); +impl HTMLMediaElementFetchContext { + fn new() -> (HTMLMediaElementFetchContext, ipc::IpcReceiver<()>) { + let mut fetch_canceller = FetchCanceller::new(); + let cancel_receiver = fetch_canceller.initialize(); + ( + HTMLMediaElementFetchContext { + cancel_reason: None, + is_seekable: false, + fetch_canceller, + }, + cancel_receiver, + ) } -} -struct DedicatedMediaSourceFailureTask { - elem: Trusted<HTMLMediaElement>, -} + fn is_seekable(&self) -> bool { + self.is_seekable + } -impl DedicatedMediaSourceFailureTask { - fn new(elem: &HTMLMediaElement) -> DedicatedMediaSourceFailureTask { - DedicatedMediaSourceFailureTask { - elem: Trusted::new(elem), - } + fn set_seekable(&mut self, seekable: bool) { + self.is_seekable = seekable; } -} -impl Runnable for DedicatedMediaSourceFailureTask { - fn name(&self) -> &'static str { "DedicatedMediaSourceFailureTask" } + fn cancel(&mut self, reason: CancelReason) { + if self.cancel_reason.is_some() { + return; + } + self.cancel_reason = Some(reason); + self.fetch_canceller.cancel(); + } - fn handler(self: Box<DedicatedMediaSourceFailureTask>) { - self.elem.root().dedicated_media_source_failure(); + fn cancel_reason(&self) -> &Option<CancelReason> { + &self.cancel_reason } } -struct PauseIfNotInDocumentTask { +struct HTMLMediaElementFetchListener { + /// The element that initiated the request. elem: Trusted<HTMLMediaElement>, + /// The response metadata received to date. + metadata: Option<Metadata>, + /// The generation of the media element when this fetch started. + generation_id: u32, + /// Time of last progress notification. + next_progress_event: Timespec, + /// Timing data for this resource. + resource_timing: ResourceFetchTiming, + /// Url for the resource. + url: ServoUrl, + /// Expected content length of the media asset being fetched or played. + expected_content_length: Option<u64>, + /// Number of the last byte fetched from the network for the ongoing + /// request. It is only reset to 0 if we reach EOS. Seek requests + /// set it to the requested position. Requests triggered after an + /// EnoughData event uses this value to restart the download from + /// the last fetched position. + latest_fetched_content: u64, + /// The media player discards all data pushes until the seek block + /// is released right before pushing the data from the offset requested + /// by a seek request. + seek_lock: Option<SeekLock>, } -impl PauseIfNotInDocumentTask { - fn new(elem: &HTMLMediaElement) -> PauseIfNotInDocumentTask { - PauseIfNotInDocumentTask { - elem: Trusted::new(elem), +// https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list +impl FetchResponseListener for HTMLMediaElementFetchListener { + fn process_request_body(&mut self) {} + + fn process_request_eof(&mut self) {} + + fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { + let elem = self.elem.root(); + + if elem.generation_id.get() != self.generation_id || elem.player.borrow().is_none() { + // A new fetch request was triggered, so we ignore this response. + return; + } + + self.metadata = metadata.ok().map(|m| match m { + FetchMetadata::Unfiltered(m) => m, + FetchMetadata::Filtered { unsafe_, .. } => unsafe_, + }); + + if let Some(metadata) = self.metadata.as_ref() { + if let Some(headers) = metadata.headers.as_ref() { + // For range requests we get the size of the media asset from the Content-Range + // header. Otherwise, we get it from the Content-Length header. + let content_length = + if let Some(content_range) = headers.typed_get::<ContentRange>() { + content_range.bytes_len() + } else if let Some(content_length) = headers.typed_get::<ContentLength>() { + Some(content_length.0) + } else { + None + }; + + // We only set the expected input size if it changes. + if content_length != self.expected_content_length { + if let Some(content_length) = content_length { + if let Err(e) = elem + .player + .borrow() + .as_ref() + .unwrap() + .lock() + .unwrap() + .set_input_size(content_length) + { + warn!("Could not set player input size {:?}", e); + } else { + self.expected_content_length = Some(content_length); + } + } + } + } + } + + let (status_is_ok, is_seekable) = self + .metadata + .as_ref() + .and_then(|m| m.status.as_ref()) + .map_or((true, false), |s| { + (s.0 >= 200 && s.0 < 300, s.0 == 206 || s.0 == 416) + }); + + if is_seekable { + // The server supports range requests, + if let Some(ref mut current_fetch_context) = *elem.current_fetch_context.borrow_mut() { + current_fetch_context.set_seekable(true); + } + } + + // => "If the media data cannot be fetched at all..." + if !status_is_ok { + // Ensure that the element doesn't receive any further notifications + // of the aborted fetch. + if let Some(ref mut current_fetch_context) = *elem.current_fetch_context.borrow_mut() { + current_fetch_context.cancel(CancelReason::Error); + } + elem.queue_dedicated_media_source_failure_steps(); + } + } + + fn process_response_chunk(&mut self, payload: Vec<u8>) { + let elem = self.elem.root(); + // If an error was received previously or if we triggered a new fetch request, + // we skip processing the payload. + if elem.generation_id.get() != self.generation_id || elem.player.borrow().is_none() { + return; + } + if let Some(ref current_fetch_context) = *elem.current_fetch_context.borrow() { + if current_fetch_context.cancel_reason().is_some() { + return; + } + } + + let payload_len = payload.len() as u64; + + if let Some(seek_lock) = self.seek_lock.take() { + seek_lock.unlock(/* successful seek */ true); + } + + // Push input data into the player. + if let Err(e) = elem + .player + .borrow() + .as_ref() + .unwrap() + .lock() + .unwrap() + .push_data(payload) + { + // If we are pushing too much data and we know that we can + // restart the download later from where we left, we cancel + // the current request. Otherwise, we continue the request + // assuming that we may drop some frames. + match e { + PlayerError::EnoughData => { + if let Some(ref mut current_fetch_context) = + *elem.current_fetch_context.borrow_mut() + { + current_fetch_context.cancel(CancelReason::Backoff); + } + }, + _ => (), + } + warn!("Could not push input data to player {:?}", e); + return; + } + + self.latest_fetched_content += payload_len; + + // https://html.spec.whatwg.org/multipage/#concept-media-load-resource step 4, + // => "If mode is remote" step 2 + if time::get_time() > self.next_progress_event { + let window = window_from_node(&*elem); + window + .task_manager() + .media_element_task_source() + .queue_simple_event(elem.upcast(), atom!("progress"), &window); + self.next_progress_event = time::get_time() + Duration::milliseconds(350); } } -} -impl Runnable for PauseIfNotInDocumentTask { - fn name(&self) -> &'static str { "PauseIfNotInDocumentTask" } + // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list + fn process_response_eof(&mut self, status: Result<ResourceFetchTiming, NetworkError>) { + if let Some(seek_lock) = self.seek_lock.take() { + seek_lock.unlock(/* successful seek */ false); + } - fn handler(self: Box<PauseIfNotInDocumentTask>) { let elem = self.elem.root(); - if !elem.upcast::<Node>().is_in_doc() { - elem.internal_pause_steps(); + + if elem.player.borrow().is_none() { + return; + } + + // If an error was previously received and no new fetch request was triggered, + // we skip processing the payload and notify the media backend that we are done + // pushing data. + if elem.generation_id.get() == self.generation_id { + if let Some(ref current_fetch_context) = *elem.current_fetch_context.borrow() { + if let Some(CancelReason::Error) = current_fetch_context.cancel_reason() { + if let Err(e) = elem + .player + .borrow() + .as_ref() + .unwrap() + .lock() + .unwrap() + .end_of_stream() + { + warn!("Could not signal EOS to player {:?}", e); + } + return; + } + } } + + if status.is_ok() && self.latest_fetched_content != 0 { + if elem.ready_state.get() == ReadyState::HaveNothing { + // Make sure that we don't skip the HaveMetadata and HaveCurrentData + // states for short streams. + elem.change_ready_state(ReadyState::HaveMetadata); + } + elem.change_ready_state(ReadyState::HaveEnoughData); + + elem.upcast::<EventTarget>().fire_event(atom!("progress")); + + elem.network_state.set(NetworkState::Idle); + + elem.upcast::<EventTarget>().fire_event(atom!("suspend")); + + elem.delay_load_event(false); + } + // => "If the connection is interrupted after some media data has been received..." + else if elem.ready_state.get() != ReadyState::HaveNothing { + // Step 1 + if let Some(ref mut current_fetch_context) = *elem.current_fetch_context.borrow_mut() { + current_fetch_context.cancel(CancelReason::Error); + } + + // Step 2 + elem.error.set(Some(&*MediaError::new( + &*window_from_node(&*elem), + MEDIA_ERR_NETWORK, + ))); + + // Step 3 + elem.network_state.set(NetworkState::Idle); + + // Step 4. + elem.delay_load_event(false); + + // Step 5 + elem.upcast::<EventTarget>().fire_event(atom!("error")); + } else { + // => "If the media data cannot be fetched at all..." + elem.queue_dedicated_media_source_failure_steps(); + } + } + + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } + + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } + + fn submit_resource_timing(&mut self) { + network_listener::submit_timing(self) } } -enum ResourceSelectionMode { - Object, - Attribute(String), - Children(Root<HTMLSourceElement>), +impl ResourceTimingListener for HTMLMediaElementFetchListener { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + let initiator_type = InitiatorType::LocalName( + self.elem + .root() + .upcast::<Element>() + .local_name() + .to_string(), + ); + (initiator_type, self.url.clone()) + } + + fn resource_timing_global(&self) -> DomRoot<GlobalScope> { + document_from_node(&*self.elem.root()).global() + } } -enum Resource { - Object, - Url(ServoUrl), +impl PreInvoke for HTMLMediaElementFetchListener { + fn should_invoke(&self) -> bool { + //TODO: finish_load needs to run at some point if the generation changes. + self.elem.root().generation_id.get() == self.generation_id + } +} + +impl HTMLMediaElementFetchListener { + fn new( + elem: &HTMLMediaElement, + url: ServoUrl, + offset: u64, + seek_lock: Option<SeekLock>, + ) -> Self { + Self { + elem: Trusted::new(elem), + metadata: None, + generation_id: elem.generation_id.get(), + next_progress_event: time::get_time() + Duration::milliseconds(350), + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), + url, + expected_content_length: None, + latest_fetched_content: offset, + seek_lock, + } + } } diff --git a/components/script/dom/htmlmenuelement.rs b/components/script/dom/htmlmenuelement.rs new file mode 100644 index 00000000000..43e0ff07ed9 --- /dev/null +++ b/components/script/dom/htmlmenuelement.rs @@ -0,0 +1,52 @@ +/* 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 crate::dom::bindings::codegen::Bindings::HTMLMenuElementBinding::HTMLMenuElementMethods; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use dom_struct::dom_struct; +use html5ever::{LocalName, Prefix}; + +#[dom_struct] +pub struct HTMLMenuElement { + htmlelement: HTMLElement, +} + +impl HTMLMenuElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLMenuElement { + HTMLMenuElement { + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + } + } + + #[allow(unrooted_must_root)] + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLMenuElement> { + Node::reflect_node( + Box::new(HTMLMenuElement::new_inherited(local_name, prefix, document)), + document, + ) + } +} + +impl HTMLMenuElementMethods for HTMLMenuElement { + // spec just mandates that compact reflects the content attribute, + // with no other semantics. Layout could use it to + // change line spacing, but nothing requires it to do so. + + // https://html.spec.whatwg.org/multipage/#dom-menu-compact + make_bool_setter!(SetCompact, "compact"); + + // https://html.spec.whatwg.org/multipage/#dom-menu-compact + make_bool_getter!(Compact, "compact"); +} diff --git a/components/script/dom/htmlmetaelement.rs b/components/script/dom/htmlmetaelement.rs index 3c4e817a132..f91e4339f1d 100644 --- a/components/script/dom/htmlmetaelement.rs +++ b/components/script/dom/htmlmetaelement.rs @@ -1,83 +1,90 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::HTMLMetaElementBinding; -use dom::bindings::codegen::Bindings::HTMLMetaElementBinding::HTMLMetaElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root, RootedReference}; -use dom::bindings::str::DOMString; -use dom::cssstylesheet::CSSStyleSheet; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::htmlelement::HTMLElement; -use dom::htmlheadelement::HTMLHeadElement; -use dom::node::{Node, UnbindContext, document_from_node, window_from_node}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLMetaElementBinding::HTMLMetaElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlheadelement::HTMLHeadElement; +use crate::dom::node::{ + document_from_node, stylesheets_owner_from_node, window_from_node, BindContext, Node, + UnbindContext, +}; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use servo_config::prefs::PREFS; -use std::ascii::AsciiExt; -use std::sync::Arc; +use html5ever::{LocalName, Prefix}; +use parking_lot::RwLock; +use servo_arc::Arc; +use servo_config::pref; use std::sync::atomic::AtomicBool; -use style::attr::AttrValue; use style::media_queries::MediaList; use style::str::HTML_SPACE_CHARACTERS; -use style::stylesheets::{Stylesheet, CssRule, CssRules, Origin}; -use style::viewport::ViewportRule; +use style::stylesheets::{CssRule, CssRules, Origin, Stylesheet, StylesheetContents, ViewportRule}; #[dom_struct] pub struct HTMLMetaElement { htmlelement: HTMLElement, - #[ignore_heap_size_of = "Arc"] - stylesheet: DOMRefCell<Option<Arc<Stylesheet>>>, - cssom_stylesheet: MutNullableJS<CSSStyleSheet>, + #[ignore_malloc_size_of = "Arc"] + stylesheet: DomRefCell<Option<Arc<Stylesheet>>>, + cssom_stylesheet: MutNullableDom<CSSStyleSheet>, } impl HTMLMetaElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLMetaElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLMetaElement { HTMLMetaElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), - stylesheet: DOMRefCell::new(None), - cssom_stylesheet: MutNullableJS::new(None), + stylesheet: DomRefCell::new(None), + cssom_stylesheet: MutNullableDom::new(None), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLMetaElement> { - Node::reflect_node(box HTMLMetaElement::new_inherited(local_name, prefix, document), - document, - HTMLMetaElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLMetaElement> { + Node::reflect_node( + Box::new(HTMLMetaElement::new_inherited(local_name, prefix, document)), + document, + ) } pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> { self.stylesheet.borrow().clone() } - pub fn get_cssom_stylesheet(&self) -> Option<Root<CSSStyleSheet>> { + pub fn get_cssom_stylesheet(&self) -> Option<DomRoot<CSSStyleSheet>> { self.get_stylesheet().map(|sheet| { self.cssom_stylesheet.or_init(|| { - CSSStyleSheet::new(&window_from_node(self), - self.upcast::<Element>(), - "text/css".into(), - None, // todo handle location - None, // todo handle title - sheet) + CSSStyleSheet::new( + &window_from_node(self), + self.upcast::<Element>(), + "text/css".into(), + None, // todo handle location + None, // todo handle title + sheet, + ) }) }) } fn process_attributes(&self) { let element = self.upcast::<Element>(); - if let Some(name) = element.get_attribute(&ns!(), &local_name!("name")).r() { - let name = name.value().to_ascii_lowercase(); + if let Some(ref name) = element.get_name() { + let name = name.to_ascii_lowercase(); let name = name.trim_matches(HTML_SPACE_CHARACTERS); if name == "viewport" { @@ -90,32 +97,36 @@ impl HTMLMetaElement { } } + #[allow(unrooted_must_root)] fn apply_viewport(&self) { - if !PREFS.get("layout.viewport.enabled").as_boolean().unwrap_or(false) { + if !pref!(layout.viewport.enabled) { return; } let element = self.upcast::<Element>(); - if let Some(content) = element.get_attribute(&ns!(), &local_name!("content")).r() { + if let Some(ref content) = element.get_attribute(&ns!(), &local_name!("content")) { let content = content.value(); if !content.is_empty() { if let Some(translated_rule) = ViewportRule::from_meta(&**content) { - let document = self.upcast::<Node>().owner_doc(); + let stylesheets_owner = stylesheets_owner_from_node(self); + let document = document_from_node(self); let shared_lock = document.style_shared_lock(); let rule = CssRule::Viewport(Arc::new(shared_lock.wrap(translated_rule))); - *self.stylesheet.borrow_mut() = Some(Arc::new(Stylesheet { - rules: CssRules::new(vec![rule], shared_lock), - origin: Origin::Author, - shared_lock: shared_lock.clone(), - url_data: window_from_node(self).get_url(), - namespaces: Default::default(), + let sheet = Arc::new(Stylesheet { + contents: StylesheetContents { + rules: CssRules::new(vec![rule], shared_lock), + origin: Origin::Author, + namespaces: Default::default(), + quirks_mode: document.quirks_mode(), + url_data: RwLock::new(window_from_node(self).get_url()), + source_map_url: RwLock::new(None), + source_url: RwLock::new(None), + }, media: Arc::new(shared_lock.wrap(MediaList::empty())), - // Viewport constraints are always recomputed on resize; they don't need to - // force all styles to be recomputed. - dirty_on_viewport_size_change: AtomicBool::new(false), + shared_lock: shared_lock.clone(), disabled: AtomicBool::new(false), - })); - let doc = document_from_node(self); - doc.invalidate_stylesheets(); + }); + *self.stylesheet.borrow_mut() = Some(sheet.clone()); + stylesheets_owner.add_stylesheet(self.upcast(), sheet); } } } @@ -123,8 +134,8 @@ impl HTMLMetaElement { fn process_referrer_attribute(&self) { let element = self.upcast::<Element>(); - if let Some(name) = element.get_attribute(&ns!(), &local_name!("name")).r() { - let name = name.value().to_ascii_lowercase(); + if let Some(ref name) = element.get_name() { + let name = name.to_ascii_lowercase(); let name = name.trim_matches(HTML_SPACE_CHARACTERS); if name == "referrer" { @@ -133,7 +144,7 @@ impl HTMLMetaElement { } } - /// https://html.spec.whatwg.org/multipage/#meta-referrer + /// <https://html.spec.whatwg.org/multipage/#meta-referrer> fn apply_referrer(&self) { if let Some(parent) = self.upcast::<Node>().GetParentElement() { if let Some(head) = parent.downcast::<HTMLHeadElement>() { @@ -158,27 +169,20 @@ impl HTMLMetaElementMethods for HTMLMetaElement { } impl VirtualMethods for HTMLMetaElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - if tree_in_doc { + if context.tree_connected { self.process_attributes(); } } - fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { - match name { - &local_name!("name") => AttrValue::from_atomic(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), - } - } - fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { if let Some(s) = self.super_type() { s.attribute_mutated(attr, mutation); @@ -192,8 +196,12 @@ impl VirtualMethods for HTMLMetaElement { s.unbind_from_tree(context); } - if context.tree_in_doc { + if context.tree_connected { self.process_referrer_attribute(); + + if let Some(s) = self.stylesheet.borrow_mut().take() { + stylesheets_owner_from_node(self).remove_stylesheet(self.upcast(), &s); + } } } } diff --git a/components/script/dom/htmlmeterelement.rs b/components/script/dom/htmlmeterelement.rs index b57951270a4..01f3b0dc94c 100644 --- a/components/script/dom/htmlmeterelement.rs +++ b/components/script/dom/htmlmeterelement.rs @@ -1,45 +1,51 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLMeterElementBinding::{self, HTMLMeterElementMethods}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; -use dom::nodelist::NodeList; +use crate::dom::bindings::codegen::Bindings::HTMLMeterElementBinding::HTMLMeterElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use crate::dom::nodelist::NodeList; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLMeterElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, + labels_node_list: MutNullableDom<NodeList>, } impl HTMLMeterElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLMeterElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLMeterElement { HTMLMeterElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + labels_node_list: MutNullableDom::new(None), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLMeterElement> { - Node::reflect_node(box HTMLMeterElement::new_inherited(local_name, prefix, document), - document, - HTMLMeterElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLMeterElement> { + Node::reflect_node( + Box::new(HTMLMeterElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } impl HTMLMeterElementMethods for HTMLMeterElement { // https://html.spec.whatwg.org/multipage/#dom-lfe-labels - fn Labels(&self) -> Root<NodeList> { - self.upcast::<HTMLElement>().labels() - } + make_labels_getter!(Labels, labels_node_list); } diff --git a/components/script/dom/htmlmodelement.rs b/components/script/dom/htmlmodelement.rs index 7724640642c..d54156bbbc8 100644 --- a/components/script/dom/htmlmodelement.rs +++ b/components/script/dom/htmlmodelement.rs @@ -1,37 +1,39 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLModElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLModElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLModElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLModElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLModElement { HTMLModElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLModElement> { - Node::reflect_node(box HTMLModElement::new_inherited(local_name, prefix, document), - document, - HTMLModElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLModElement> { + Node::reflect_node( + Box::new(HTMLModElement::new_inherited(local_name, prefix, document)), + document, + ) } } diff --git a/components/script/dom/htmlobjectelement.rs b/components/script/dom/htmlobjectelement.rs index c80a7838198..928c593cec6 100755 --- a/components/script/dom/htmlobjectelement.rs +++ b/components/script/dom/htmlobjectelement.rs @@ -1,55 +1,62 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::HTMLObjectElementBinding; -use dom::bindings::codegen::Bindings::HTMLObjectElementBinding::HTMLObjectElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::htmlelement::HTMLElement; -use dom::htmlformelement::{FormControl, HTMLFormElement}; -use dom::node::{Node, window_from_node}; -use dom::validation::Validatable; -use dom::validitystate::{ValidityState, ValidationFlags}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLObjectElementBinding::HTMLObjectElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; +use crate::dom::node::{window_from_node, Node}; +use crate::dom::validation::Validatable; +use crate::dom::validitystate::ValidityState; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use net_traits::image::base::Image; +use servo_arc::Arc; use std::default::Default; -use std::sync::Arc; #[dom_struct] pub struct HTMLObjectElement { htmlelement: HTMLElement, - #[ignore_heap_size_of = "Arc"] - image: DOMRefCell<Option<Arc<Image>>>, - form_owner: MutNullableJS<HTMLFormElement>, + #[ignore_malloc_size_of = "Arc"] + image: DomRefCell<Option<Arc<Image>>>, + form_owner: MutNullableDom<HTMLFormElement>, + validity_state: MutNullableDom<ValidityState>, } impl HTMLObjectElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLObjectElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLObjectElement { HTMLObjectElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), - image: DOMRefCell::new(None), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + image: DomRefCell::new(None), form_owner: Default::default(), + validity_state: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLObjectElement> { - Node::reflect_node(box HTMLObjectElement::new_inherited(local_name, prefix, document), - document, - HTMLObjectElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLObjectElement> { + Node::reflect_node( + Box::new(HTMLObjectElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } @@ -64,23 +71,19 @@ impl<'a> ProcessDataURL for &'a HTMLObjectElement { let elem = self.upcast::<Element>(); // TODO: support other values - match (elem.get_attribute(&ns!(), &local_name!("type")), - elem.get_attribute(&ns!(), &local_name!("data"))) { + match ( + elem.get_attribute(&ns!(), &local_name!("type")), + elem.get_attribute(&ns!(), &local_name!("data")), + ) { (None, Some(_uri)) => { // TODO(gw): Prefetch the image here. - } - _ => { } + }, + _ => {}, } } } impl HTMLObjectElementMethods for HTMLObjectElement { - // https://html.spec.whatwg.org/multipage/#dom-cva-validity - fn Validity(&self) -> Root<ValidityState> { - let window = window_from_node(self); - ValidityState::new(&window, self.upcast()) - } - // https://html.spec.whatwg.org/multipage/#dom-object-type make_getter!(Type, "type"); @@ -88,25 +91,60 @@ impl HTMLObjectElementMethods for HTMLObjectElement { make_setter!(SetType, "type"); // https://html.spec.whatwg.org/multipage/#dom-fae-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner() } + + // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate + fn WillValidate(&self) -> bool { + self.is_instance_validatable() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validity + fn Validity(&self) -> DomRoot<ValidityState> { + self.validity_state() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity + fn CheckValidity(&self) -> bool { + self.check_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity + fn ReportValidity(&self) -> bool { + self.report_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage + fn ValidationMessage(&self) -> DOMString { + self.validation_message() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity + fn SetCustomValidity(&self, error: DOMString) { + self.validity_state().set_custom_error_message(error); + } } impl Validatable for HTMLObjectElement { - fn is_instance_validatable(&self) -> bool { - true + fn as_element(&self) -> &Element { + self.upcast() + } + + fn validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), self.upcast())) } - fn validate(&self, validate_flags: ValidationFlags) -> bool { - if validate_flags.is_empty() {} - // Need more flag check for different validation types later - true + + fn is_instance_validatable(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#the-object-element%3Abarred-from-constraint-validation + false } } impl VirtualMethods for HTMLObjectElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -126,7 +164,7 @@ impl VirtualMethods for HTMLObjectElement { } impl FormControl for HTMLObjectElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } diff --git a/components/script/dom/htmlolistelement.rs b/components/script/dom/htmlolistelement.rs index a36e97404e4..456625d6447 100644 --- a/components/script/dom/htmlolistelement.rs +++ b/components/script/dom/htmlolistelement.rs @@ -1,15 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLOListElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLOListElement { @@ -17,20 +15,27 @@ pub struct HTMLOListElement { } impl HTMLOListElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLOListElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLOListElement { HTMLOListElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLOListElement> { - Node::reflect_node(box HTMLOListElement::new_inherited(local_name, prefix, document), - document, - HTMLOListElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLOListElement> { + Node::reflect_node( + Box::new(HTMLOListElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmloptgroupelement.rs b/components/script/dom/htmloptgroupelement.rs index 13a68d7ff41..4ee3263c9ac 100644 --- a/components/script/dom/htmloptgroupelement.rs +++ b/components/script/dom/htmloptgroupelement.rs @@ -1,46 +1,54 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding; -use dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding::HTMLOptGroupElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::htmlelement::HTMLElement; -use dom::htmloptionelement::HTMLOptionElement; -use dom::node::Node; -use dom::virtualmethods::VirtualMethods; +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding::HTMLOptGroupElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmloptionelement::HTMLOptionElement; +use crate::dom::node::Node; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use style::element_state::*; +use html5ever::{LocalName, Prefix}; +use style::element_state::ElementState; #[dom_struct] pub struct HTMLOptGroupElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLOptGroupElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLOptGroupElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLOptGroupElement { HTMLOptGroupElement { - htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, - local_name, prefix, document) + htmlelement: HTMLElement::new_inherited_with_state( + ElementState::IN_ENABLED_STATE, + local_name, + prefix, + document, + ), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLOptGroupElement> { - Node::reflect_node(box HTMLOptGroupElement::new_inherited(local_name, prefix, document), - document, - HTMLOptGroupElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLOptGroupElement> { + Node::reflect_node( + Box::new(HTMLOptGroupElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } @@ -53,8 +61,8 @@ impl HTMLOptGroupElementMethods for HTMLOptGroupElement { } impl VirtualMethods for HTMLOptGroupElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -72,9 +80,11 @@ impl VirtualMethods for HTMLOptGroupElement { let el = self.upcast::<Element>(); el.set_disabled_state(disabled_state); el.set_enabled_state(!disabled_state); - let options = el.upcast::<Node>().children().filter(|child| { - child.is::<HTMLOptionElement>() - }).map(|child| Root::from_ref(child.downcast::<HTMLOptionElement>().unwrap())); + let options = el + .upcast::<Node>() + .children() + .filter(|child| child.is::<HTMLOptionElement>()) + .map(|child| DomRoot::from_ref(child.downcast::<HTMLOptionElement>().unwrap())); if disabled_state { for option in options { let el = option.upcast::<Element>(); diff --git a/components/script/dom/htmloptionelement.rs b/components/script/dom/htmloptionelement.rs index e54b9b563e4..0ba7eadcf11 100644 --- a/components/script/dom/htmloptionelement.rs +++ b/components/script/dom/htmloptionelement.rs @@ -1,64 +1,109 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; -use dom::bindings::codegen::Bindings::HTMLOptionElementBinding; -use dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods; -use dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementBinding::HTMLSelectElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::characterdata::CharacterData; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::htmlelement::HTMLElement; -use dom::htmlformelement::HTMLFormElement; -use dom::htmloptgroupelement::HTMLOptGroupElement; -use dom::htmlscriptelement::HTMLScriptElement; -use dom::htmlselectelement::HTMLSelectElement; -use dom::node::{Node, UnbindContext}; -use dom::text::Text; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; +use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementBinding::HTMLSelectElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::characterdata::CharacterData; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlformelement::HTMLFormElement; +use crate::dom::htmloptgroupelement::HTMLOptGroupElement; +use crate::dom::htmlscriptelement::HTMLScriptElement; +use crate::dom::htmlselectelement::HTMLSelectElement; +use crate::dom::node::{BindContext, Node, ShadowIncluding, UnbindContext}; +use crate::dom::text::Text; +use crate::dom::virtualmethods::VirtualMethods; +use crate::dom::window::Window; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix, QualName}; use std::cell::Cell; -use style::element_state::*; +use std::convert::TryInto; +use style::element_state::ElementState; use style::str::{split_html_space_chars, str_join}; #[dom_struct] pub struct HTMLOptionElement { htmlelement: HTMLElement, - /// https://html.spec.whatwg.org/multipage/#attr-option-selected + /// <https://html.spec.whatwg.org/multipage/#attr-option-selected> selectedness: Cell<bool>, - /// https://html.spec.whatwg.org/multipage/#concept-option-dirtiness + /// <https://html.spec.whatwg.org/multipage/#concept-option-dirtiness> dirtiness: Cell<bool>, } impl HTMLOptionElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLOptionElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLOptionElement { HTMLOptionElement { - htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, - local_name, prefix, document), + htmlelement: HTMLElement::new_inherited_with_state( + ElementState::IN_ENABLED_STATE, + local_name, + prefix, + document, + ), selectedness: Cell::new(false), dirtiness: Cell::new(false), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLOptionElement> { - Node::reflect_node(box HTMLOptionElement::new_inherited(local_name, prefix, document), - document, - HTMLOptionElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLOptionElement> { + Node::reflect_node( + Box::new(HTMLOptionElement::new_inherited( + local_name, prefix, document, + )), + document, + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-option + #[allow(non_snake_case)] + pub fn Option( + window: &Window, + text: DOMString, + value: Option<DOMString>, + default_selected: bool, + selected: bool, + ) -> Fallible<DomRoot<HTMLOptionElement>> { + let element = Element::create( + QualName::new(None, ns!(html), local_name!("option")), + None, + &window.Document(), + ElementCreator::ScriptCreated, + CustomElementCreationMode::Synchronous, + ); + + let option = DomRoot::downcast::<HTMLOptionElement>(element).unwrap(); + + if !text.is_empty() { + option.upcast::<Node>().SetTextContent(Some(text)) + } + + if let Some(val) = value { + option.SetValue(val) + } + + option.SetDefaultSelected(default_selected); + option.set_selectedness(selected); + Ok(option) } pub fn set_selectedness(&self, selected: bool) { @@ -70,20 +115,59 @@ impl HTMLOptionElement { } fn pick_if_selected_and_reset(&self) { - if let Some(select) = self.upcast::<Node>().ancestors() - .filter_map(Root::downcast::<HTMLSelectElement>) - .next() { + if let Some(select) = self + .upcast::<Node>() + .ancestors() + .filter_map(DomRoot::downcast::<HTMLSelectElement>) + .next() + { if self.Selected() { select.pick_option(self); } select.ask_for_reset(); } } + + // https://html.spec.whatwg.org/multipage/#concept-option-index + fn index(&self) -> i32 { + if let Some(parent) = self.upcast::<Node>().GetParentNode() { + if let Some(select_parent) = parent.downcast::<HTMLSelectElement>() { + // return index in parent select's list of options + return self.index_in_select(select_parent); + } else if parent.is::<HTMLOptGroupElement>() { + if let Some(grandparent) = parent.GetParentNode() { + if let Some(select_grandparent) = grandparent.downcast::<HTMLSelectElement>() { + // return index in grandparent select's list of options + return self.index_in_select(select_grandparent); + } + } + } + } + // "If the option element is not in a list of options, + // then the option element's index is zero." + // self is neither a child of a select, nor a grandchild of a select + // via an optgroup, so it is not in a list of options + 0 + } + + fn index_in_select(&self, select: &HTMLSelectElement) -> i32 { + match select.list_of_options().position(|n| &*n == self) { + Some(index) => index.try_into().unwrap_or(0), + None => { + // shouldn't happen but not worth a browser panic + warn!( + "HTMLOptionElement called index_in_select at a select that did not contain it" + ); + 0 + }, + } + } } // 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 svg_script = + *element.namespace() == ns!(svg) && element.local_name() == &local_name!("script"); let html_script = element.is::<HTMLScriptElement>(); if svg_script || html_script { return; @@ -119,14 +203,14 @@ impl HTMLOptionElementMethods for HTMLOptionElement { } // https://html.spec.whatwg.org/multipage/#dom-option-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { - let parent = self.upcast::<Node>().GetParentNode().and_then(|p| + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { + let parent = self.upcast::<Node>().GetParentNode().and_then(|p| { if p.is::<HTMLOptGroupElement>() { p.upcast::<Node>().GetParentNode() } else { Some(p) } - ); + }); parent.and_then(|p| p.downcast::<HTMLSelectElement>().and_then(|s| s.GetForm())) } @@ -176,11 +260,16 @@ impl HTMLOptionElementMethods for HTMLOptionElement { self.selectedness.set(selected); self.pick_if_selected_and_reset(); } + + // https://html.spec.whatwg.org/multipage/#dom-option-index + fn Index(&self) -> i32 { + self.index() + } } impl VirtualMethods for HTMLOptionElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -197,7 +286,7 @@ impl VirtualMethods for HTMLOptionElement { el.set_disabled_state(false); el.set_enabled_state(true); el.check_parent_disabled_state_for_option(); - } + }, } }, &local_name!("selected") => { @@ -220,12 +309,13 @@ impl VirtualMethods for HTMLOptionElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - self.upcast::<Element>().check_parent_disabled_state_for_option(); + self.upcast::<Element>() + .check_parent_disabled_state_for_option(); self.pick_if_selected_and_reset(); } @@ -233,9 +323,12 @@ impl VirtualMethods for HTMLOptionElement { fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); - if let Some(select) = context.parent.inclusive_ancestors() - .filter_map(Root::downcast::<HTMLSelectElement>) - .next() { + if let Some(select) = context + .parent + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::<HTMLSelectElement>) + .next() + { select.ask_for_reset(); } diff --git a/components/script/dom/htmloptionscollection.rs b/components/script/dom/htmloptionscollection.rs index 42d6aebde35..2a79c83d55e 100644 --- a/components/script/dom/htmloptionscollection.rs +++ b/components/script/dom/htmloptionscollection.rs @@ -1,25 +1,26 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; -use dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding; -use dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods; -use dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; -use dom::bindings::codegen::UnionTypes::{HTMLOptionElementOrHTMLOptGroupElement, HTMLElementOrLong}; -use dom::bindings::error::{Error, ErrorResult}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{Root, RootedReference}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::element::Element; -use dom::htmlcollection::{CollectionFilter, HTMLCollection}; -use dom::htmloptionelement::HTMLOptionElement; -use dom::htmlselectelement::HTMLSelectElement; -use dom::node::{document_from_node, Node}; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; +use crate::dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods; +use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::UnionTypes::{ + HTMLElementOrLong, HTMLOptionElementOrHTMLOptGroupElement, +}; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::element::Element; +use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; +use crate::dom::htmloptionelement::HTMLOptionElement; +use crate::dom::htmlselectelement::HTMLSelectElement; +use crate::dom::node::{document_from_node, Node}; +use crate::dom::window::Window; use dom_struct::dom_struct; #[dom_struct] @@ -28,18 +29,24 @@ pub struct HTMLOptionsCollection { } impl HTMLOptionsCollection { - fn new_inherited(select: &HTMLSelectElement, filter: Box<CollectionFilter + 'static>) -> HTMLOptionsCollection { + fn new_inherited( + select: &HTMLSelectElement, + filter: Box<dyn CollectionFilter + 'static>, + ) -> HTMLOptionsCollection { HTMLOptionsCollection { collection: HTMLCollection::new_inherited(select.upcast(), filter), } } - pub fn new(window: &Window, select: &HTMLSelectElement, filter: Box<CollectionFilter + 'static>) - -> Root<HTMLOptionsCollection> - { - reflect_dom_object(box HTMLOptionsCollection::new_inherited(select, filter), - window, - HTMLOptionsCollectionBinding::Wrap) + pub fn new( + window: &Window, + select: &HTMLSelectElement, + filter: Box<dyn CollectionFilter + 'static>, + ) -> DomRoot<HTMLOptionsCollection> { + reflect_dom_object( + Box::new(HTMLOptionsCollection::new_inherited(select, filter)), + window, + ) } fn add_new_elements(&self, count: u32) -> ErrorResult { @@ -49,8 +56,8 @@ impl HTMLOptionsCollection { for _ in 0..count { let element = HTMLOptionElement::new(local_name!("option"), None, &document); let node = element.upcast::<Node>(); - try!(root.AppendChild(node)); - }; + root.AppendChild(node)?; + } Ok(()) } } @@ -61,7 +68,7 @@ impl HTMLOptionsCollectionMethods for HTMLOptionsCollection { // https://github.com/servo/servo/issues/5875 // // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem - fn NamedGetter(&self, name: DOMString) -> Option<Root<Element>> { + fn NamedGetter(&self, name: DOMString) -> Option<DomRoot<Element>> { self.upcast().NamedItem(name) } @@ -75,7 +82,7 @@ impl HTMLOptionsCollectionMethods for HTMLOptionsCollection { // https://github.com/servo/servo/issues/5875 // // https://dom.spec.whatwg.org/#dom-htmlcollection-item - fn IndexedGetter(&self, index: u32) -> Option<Root<Element>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> { self.upcast().IndexedGetter(index) } @@ -90,7 +97,7 @@ impl HTMLOptionsCollectionMethods for HTMLOptionsCollection { // Step 4 if n > 0 { - try!(self.add_new_elements(n as u32)); + self.add_new_elements(n as u32)?; } // Step 5 @@ -132,12 +139,20 @@ impl HTMLOptionsCollectionMethods for HTMLOptionsCollection { } // https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-add - fn Add(&self, element: HTMLOptionElementOrHTMLOptGroupElement, before: Option<HTMLElementOrLong>) -> ErrorResult { + fn Add( + &self, + element: HTMLOptionElementOrHTMLOptGroupElement, + before: Option<HTMLElementOrLong>, + ) -> ErrorResult { let root = self.upcast().root_node(); let node: &Node = match element { - HTMLOptionElementOrHTMLOptGroupElement::HTMLOptionElement(ref element) => element.upcast(), - HTMLOptionElementOrHTMLOptGroupElement::HTMLOptGroupElement(ref element) => element.upcast(), + HTMLOptionElementOrHTMLOptGroupElement::HTMLOptionElement(ref element) => { + element.upcast() + }, + HTMLOptionElementOrHTMLOptGroupElement::HTMLOptGroupElement(ref element) => { + element.upcast() + }, }; // Step 1 @@ -159,24 +174,23 @@ impl HTMLOptionsCollectionMethods for HTMLOptionsCollection { } // Step 4 - let reference_node = before.and_then(|before| { - match before { - HTMLElementOrLong::HTMLElement(element) => Some(Root::upcast::<Node>(element)), - HTMLElementOrLong::Long(index) => { - self.upcast().IndexedGetter(index as u32).map(Root::upcast::<Node>) - } - } + let reference_node = before.and_then(|before| match before { + HTMLElementOrLong::HTMLElement(element) => Some(DomRoot::upcast::<Node>(element)), + HTMLElementOrLong::Long(index) => self + .upcast() + .IndexedGetter(index as u32) + .map(DomRoot::upcast::<Node>), }); // Step 5 - let parent = if let Some(reference_node) = reference_node.r() { + let parent = if let Some(ref reference_node) = reference_node { reference_node.GetParentNode().unwrap() } else { root }; // Step 6 - Node::pre_insert(node, &parent, reference_node.r()).map(|_| ()) + Node::pre_insert(node, &parent, reference_node.as_deref()).map(|_| ()) } // https://html.spec.whatwg.org/multipage/#dom-htmloptionscollection-remove diff --git a/components/script/dom/htmloutputelement.rs b/components/script/dom/htmloutputelement.rs index 719efad95d3..e20dbcc29b2 100644 --- a/components/script/dom/htmloutputelement.rs +++ b/components/script/dom/htmloutputelement.rs @@ -1,72 +1,155 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::HTMLOutputElementBinding; -use dom::bindings::codegen::Bindings::HTMLOutputElementBinding::HTMLOutputElementMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::htmlelement::HTMLElement; -use dom::htmlformelement::{FormControl, HTMLFormElement}; -use dom::node::{Node, window_from_node}; -use dom::nodelist::NodeList; -use dom::validitystate::ValidityState; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLOutputElementBinding::HTMLOutputElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; +use crate::dom::node::{window_from_node, Node}; +use crate::dom::nodelist::NodeList; +use crate::dom::validation::Validatable; +use crate::dom::validitystate::ValidityState; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLOutputElement { htmlelement: HTMLElement, - form_owner: MutNullableJS<HTMLFormElement>, + form_owner: MutNullableDom<HTMLFormElement>, + labels_node_list: MutNullableDom<NodeList>, + default_value_override: DomRefCell<Option<DOMString>>, + validity_state: MutNullableDom<ValidityState>, } impl HTMLOutputElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLOutputElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLOutputElement { HTMLOutputElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), form_owner: Default::default(), + labels_node_list: Default::default(), + default_value_override: DomRefCell::new(None), + validity_state: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLOutputElement> { - Node::reflect_node(box HTMLOutputElement::new_inherited(local_name, prefix, document), - document, - HTMLOutputElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLOutputElement> { + Node::reflect_node( + Box::new(HTMLOutputElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } -} -impl HTMLOutputElementMethods for HTMLOutputElement { - // https://html.spec.whatwg.org/multipage/#dom-cva-validity - fn Validity(&self) -> Root<ValidityState> { - let window = window_from_node(self); - ValidityState::new(&window, self.upcast()) + pub fn reset(&self) { + Node::string_replace_all(self.DefaultValue(), self.upcast::<Node>()); + *self.default_value_override.borrow_mut() = None; } +} +impl HTMLOutputElementMethods for HTMLOutputElement { // https://html.spec.whatwg.org/multipage/#dom-fae-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner() } // https://html.spec.whatwg.org/multipage/#dom-lfe-labels - fn Labels(&self) -> Root<NodeList> { - self.upcast::<HTMLElement>().labels() + make_labels_getter!(Labels, labels_node_list); + + // https://html.spec.whatwg.org/multipage/#dom-output-defaultvaleu + fn DefaultValue(&self) -> DOMString { + let dvo = self.default_value_override.borrow(); + if let Some(ref dv) = *dvo { + dv.clone() + } else { + self.upcast::<Node>().descendant_text_content() + } + } + + // https://html.spec.whatwg.org/multipage/#dom-output-defaultvalue + fn SetDefaultValue(&self, value: DOMString) { + if self.default_value_override.borrow().is_none() { + // Step 1 ("and return") + Node::string_replace_all(value.clone(), self.upcast::<Node>()); + } else { + // Step 2, if not returned from step 1 + *self.default_value_override.borrow_mut() = Some(value); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-output-value + fn Value(&self) -> DOMString { + self.upcast::<Node>().descendant_text_content() + } + + // https://html.spec.whatwg.org/multipage/#dom-output-value + fn SetValue(&self, value: DOMString) { + *self.default_value_override.borrow_mut() = Some(self.DefaultValue()); + Node::string_replace_all(value, self.upcast::<Node>()); + } + + // https://html.spec.whatwg.org/multipage/#dom-output-type + fn Type(&self) -> DOMString { + return DOMString::from("output"); + } + + // https://html.spec.whatwg.org/multipage/#dom-fe-name + make_atomic_setter!(SetName, "name"); + + // https://html.spec.whatwg.org/multipage/#dom-fe-name + make_getter!(Name, "name"); + + // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate + fn WillValidate(&self) -> bool { + self.is_instance_validatable() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validity + fn Validity(&self) -> DomRoot<ValidityState> { + self.validity_state() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity + fn CheckValidity(&self) -> bool { + self.check_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity + fn ReportValidity(&self) -> bool { + self.report_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage + fn ValidationMessage(&self) -> DOMString { + self.validation_message() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity + fn SetCustomValidity(&self, error: DOMString) { + self.validity_state().set_custom_error_message(error); } } impl VirtualMethods for HTMLOutputElement { - fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type<'b>(&'b self) -> Option<&'b dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -81,7 +164,7 @@ impl VirtualMethods for HTMLOutputElement { } impl FormControl for HTMLOutputElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } @@ -93,3 +176,19 @@ impl FormControl for HTMLOutputElement { self.upcast::<Element>() } } + +impl Validatable for HTMLOutputElement { + fn as_element(&self) -> &Element { + self.upcast() + } + + fn validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), self.upcast())) + } + + fn is_instance_validatable(&self) -> bool { + // output is not a submittable element (https://html.spec.whatwg.org/multipage/#category-submit) + false + } +} diff --git a/components/script/dom/htmlparagraphelement.rs b/components/script/dom/htmlparagraphelement.rs index 1a7936352bc..101f7058138 100644 --- a/components/script/dom/htmlparagraphelement.rs +++ b/components/script/dom/htmlparagraphelement.rs @@ -1,37 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLParagraphElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLParagraphElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLParagraphElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLParagraphElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLParagraphElement { HTMLParagraphElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLParagraphElement> { - Node::reflect_node(box HTMLParagraphElement::new_inherited(local_name, prefix, document), - document, - HTMLParagraphElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLParagraphElement> { + Node::reflect_node( + Box::new(HTMLParagraphElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmlparamelement.rs b/components/script/dom/htmlparamelement.rs index 103faa0a641..fd1a139a84d 100644 --- a/components/script/dom/htmlparamelement.rs +++ b/components/script/dom/htmlparamelement.rs @@ -1,37 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLParamElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLParamElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLParamElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLParamElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLParamElement { HTMLParamElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLParamElement> { - Node::reflect_node(box HTMLParamElement::new_inherited(local_name, prefix, document), - document, - HTMLParamElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLParamElement> { + Node::reflect_node( + Box::new(HTMLParamElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmlpictureelement.rs b/components/script/dom/htmlpictureelement.rs new file mode 100644 index 00000000000..726f7afd5f8 --- /dev/null +++ b/components/script/dom/htmlpictureelement.rs @@ -0,0 +1,41 @@ +/* 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 crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use dom_struct::dom_struct; +use html5ever::{LocalName, Prefix}; + +#[dom_struct] +pub struct HTMLPictureElement { + htmlelement: HTMLElement, +} + +impl HTMLPictureElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLPictureElement { + HTMLPictureElement { + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + } + } + + #[allow(unrooted_must_root)] + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLPictureElement> { + Node::reflect_node( + Box::new(HTMLPictureElement::new_inherited( + local_name, prefix, document, + )), + document, + ) + } +} diff --git a/components/script/dom/htmlpreelement.rs b/components/script/dom/htmlpreelement.rs index 7d3575d1eae..36e2706e168 100644 --- a/components/script/dom/htmlpreelement.rs +++ b/components/script/dom/htmlpreelement.rs @@ -1,15 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLPreElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLPreElement { @@ -17,21 +15,25 @@ pub struct HTMLPreElement { } impl HTMLPreElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLPreElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLPreElement { HTMLPreElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLPreElement> { - Node::reflect_node(box HTMLPreElement::new_inherited(local_name, prefix, document), - document, - HTMLPreElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLPreElement> { + Node::reflect_node( + Box::new(HTMLPreElement::new_inherited(local_name, prefix, document)), + document, + ) } } diff --git a/components/script/dom/htmlprogresselement.rs b/components/script/dom/htmlprogresselement.rs index fe8eb2775e9..dcd2f018734 100644 --- a/components/script/dom/htmlprogresselement.rs +++ b/components/script/dom/htmlprogresselement.rs @@ -1,46 +1,51 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLProgressElementBinding::{self, HTMLProgressElementMethods}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; -use dom::nodelist::NodeList; +use crate::dom::bindings::codegen::Bindings::HTMLProgressElementBinding::HTMLProgressElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use crate::dom::nodelist::NodeList; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLProgressElement { htmlelement: HTMLElement, + labels_node_list: MutNullableDom<NodeList>, } impl HTMLProgressElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLProgressElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLProgressElement { HTMLProgressElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + labels_node_list: MutNullableDom::new(None), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLProgressElement> { - Node::reflect_node(box HTMLProgressElement::new_inherited(local_name, prefix, document), - document, - HTMLProgressElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLProgressElement> { + Node::reflect_node( + Box::new(HTMLProgressElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } impl HTMLProgressElementMethods for HTMLProgressElement { // https://html.spec.whatwg.org/multipage/#dom-lfe-labels - fn Labels(&self) -> Root<NodeList> { - self.upcast::<HTMLElement>().labels() - } + make_labels_getter!(Labels, labels_node_list); } diff --git a/components/script/dom/htmlquoteelement.rs b/components/script/dom/htmlquoteelement.rs index de3e473434b..4ad38f78338 100644 --- a/components/script/dom/htmlquoteelement.rs +++ b/components/script/dom/htmlquoteelement.rs @@ -1,15 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLQuoteElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLQuoteElement { @@ -17,21 +15,27 @@ pub struct HTMLQuoteElement { } impl HTMLQuoteElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLQuoteElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLQuoteElement { HTMLQuoteElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLQuoteElement> { - Node::reflect_node(box HTMLQuoteElement::new_inherited(local_name, prefix, document), - document, - HTMLQuoteElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLQuoteElement> { + Node::reflect_node( + Box::new(HTMLQuoteElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index d6679c5168c..d27fee98fb5 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -1,100 +1,222 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use document_loader::LoadType; -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::HTMLScriptElementBinding; -use dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::js::RootedReference; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element, ElementCreator}; -use dom::element::{cors_setting_for_element, reflect_cross_origin_attribute, set_cross_origin_attribute}; -use dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::node::{ChildrenMutation, CloneChildrenFlag, Node}; -use dom::node::{document_from_node, window_from_node}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::document_loader::LoadType; +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; +use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::settings_stack::AutoEntryScript; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::document::Document; +use crate::dom::element::{ + cors_setting_for_element, referrer_policy_for_element, reflect_cross_origin_attribute, + reflect_referrer_policy_attribute, set_cross_origin_attribute, +}; +use crate::dom::element::{AttributeMutation, Element, ElementCreator}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{document_from_node, window_from_node}; +use crate::dom::node::{BindContext, ChildrenMutation, CloneChildrenFlag, Node}; +use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::virtualmethods::VirtualMethods; +use crate::fetch::create_a_potential_cors_request; +use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; +use crate::realms::enter_realm; +use crate::script_module::fetch_inline_module_script; +use crate::script_module::{fetch_external_module_script, ModuleOwner, ScriptFetchOptions}; +use crate::task::TaskCanceller; +use crate::task_source::dom_manipulation::DOMManipulationTaskSource; +use crate::task_source::TaskSource; +use crate::task_source::TaskSourceName; +use content_security_policy as csp; +use core::ffi::c_void; use dom_struct::dom_struct; -use encoding::label::encoding_from_whatwg_label; -use encoding::types::{DecoderTrap, EncodingRef}; -use html5ever_atoms::LocalName; +use encoding_rs::Encoding; +use html5ever::{LocalName, Prefix}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; +use js::jsapi::{ + CanCompileOffThread, CompileOffThread1, FinishOffThreadScript, Heap, JSScript, OffThreadToken, +}; use js::jsval::UndefinedValue; -use net_traits::{FetchMetadata, FetchResponseListener, Metadata, NetworkError}; -use net_traits::request::{CorsSettings, CredentialsMode, Destination, RequestInit, RequestMode, Type as RequestType}; -use network_listener::{NetworkListener, PreInvoke}; +use js::rust::{transform_str_to_source_text, CompileOptionsWrapper}; +use msg::constellation_msg::PipelineId; +use net_traits::request::{ + CorsSettings, CredentialsMode, Destination, ParserMetadata, RequestBuilder, +}; +use net_traits::{ + FetchMetadata, FetchResponseListener, Metadata, NetworkError, ResourceFetchTiming, + ResourceTimingType, +}; use servo_atoms::Atom; -use servo_config::opts; +use servo_config::pref; +use servo_url::ImmutableOrigin; use servo_url::ServoUrl; -use std::ascii::AsciiExt; use std::cell::Cell; -use std::fs::File; -use std::io::{Read, Write}; +use std::fs::{create_dir_all, read_to_string, File}; +use std::io::{Read, Seek, Write}; +use std::mem::replace; use std::path::PathBuf; -use std::process::{Command, Stdio}; +use std::process::Command; +use std::rc::Rc; use std::sync::{Arc, Mutex}; -use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec}; +use style::str::{StaticStringVec, HTML_SPACE_CHARACTERS}; use uuid::Uuid; +pub struct OffThreadCompilationContext { + script_element: Trusted<HTMLScriptElement>, + script_kind: ExternalScriptKind, + final_url: ServoUrl, + url: ServoUrl, + task_source: DOMManipulationTaskSource, + canceller: TaskCanceller, + script_text: String, + fetch_options: ScriptFetchOptions, +} + +/// A wrapper to mark OffThreadToken as Send, +/// which should be safe according to +/// mozjs/js/public/OffThreadScriptCompilation.h +struct OffThreadCompilationToken(*mut OffThreadToken); + +#[allow(unsafe_code)] +unsafe impl Send for OffThreadCompilationToken {} + +#[allow(unsafe_code)] +unsafe extern "C" fn off_thread_compilation_callback( + token: *mut OffThreadToken, + callback_data: *mut c_void, +) { + let mut context = Box::from_raw(callback_data as *mut OffThreadCompilationContext); + let token = OffThreadCompilationToken(token); + + let url = context.url.clone(); + let final_url = context.final_url.clone(); + let script_element = context.script_element.clone(); + let script_kind = context.script_kind.clone(); + let script = replace(&mut context.script_text, String::new()); + let fetch_options = context.fetch_options.clone(); + + // Continue with <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> + let _ = context.task_source.queue_with_canceller( + task!(off_thread_compile_continue: move || { + let elem = script_element.root(); + let global = elem.global(); + let cx = global.get_cx(); + let _ar = enter_realm(&*global); + + rooted!(in(*cx) + let compiled_script = FinishOffThreadScript(*cx, token.0) + ); + + let load = if compiled_script.get().is_null() { + Err(NetworkError::Internal( + "Off-thread compilation failed.".into(), + )) + } else { + let script_text = DOMString::from(script); + let heap = Heap::default(); + let source_code = RootedTraceableBox::new(heap); + source_code.set(compiled_script.get()); + let code = SourceCode::Compiled(CompiledSourceCode { + source_code: source_code, + original_text: Rc::new(script_text), + }); + + Ok(ScriptOrigin { + code, + url: final_url, + external: true, + fetch_options: fetch_options, + type_: ScriptType::Classic, + }) + }; + + finish_fetching_a_classic_script(&*elem, script_kind, url, load); + }), + &context.canceller, + ); +} + +/// An unique id for script element. +#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)] +pub struct ScriptId(Uuid); + #[dom_struct] pub struct HTMLScriptElement { htmlelement: HTMLElement, - /// https://html.spec.whatwg.org/multipage/#already-started + /// <https://html.spec.whatwg.org/multipage/#already-started> already_started: Cell<bool>, - /// https://html.spec.whatwg.org/multipage/#parser-inserted + /// <https://html.spec.whatwg.org/multipage/#parser-inserted> parser_inserted: Cell<bool>, - /// https://html.spec.whatwg.org/multipage/#non-blocking + /// <https://html.spec.whatwg.org/multipage/#non-blocking> /// /// (currently unused) non_blocking: Cell<bool>, /// Document of the parser that created this element - parser_document: JS<Document>, + parser_document: Dom<Document>, /// Track line line_number line_number: u64, + + /// Unique id for each script element + #[ignore_malloc_size_of = "Defined in uuid"] + id: ScriptId, } impl HTMLScriptElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document, - creator: ElementCreator) -> HTMLScriptElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + creator: ElementCreator, + ) -> HTMLScriptElement { HTMLScriptElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), + id: ScriptId(Uuid::new_v4()), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), already_started: Cell::new(false), parser_inserted: Cell::new(creator.is_parser_created()), non_blocking: Cell::new(!creator.is_parser_created()), - parser_document: JS::from_ref(document), + parser_document: Dom::from_ref(document), line_number: creator.return_line_number(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, prefix: Option<DOMString>, document: &Document, - creator: ElementCreator) -> Root<HTMLScriptElement> { - Node::reflect_node(box HTMLScriptElement::new_inherited(local_name, prefix, document, creator), - document, - HTMLScriptElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + creator: ElementCreator, + ) -> DomRoot<HTMLScriptElement> { + Node::reflect_node( + Box::new(HTMLScriptElement::new_inherited( + local_name, prefix, document, creator, + )), + document, + ) } -} + pub fn get_script_id(&self) -> ScriptId { + self.id.clone() + } +} /// Supported script types as defined by /// <https://html.spec.whatwg.org/multipage/#javascript-mime-type>. -static SCRIPT_JS_MIMES: StaticStringVec = &[ +pub static SCRIPT_JS_MIMES: StaticStringVec = &[ "application/ecmascript", "application/javascript", "application/x-ecmascript", @@ -113,42 +235,110 @@ static SCRIPT_JS_MIMES: StaticStringVec = &[ "text/x-javascript", ]; -#[derive(HeapSizeOf, JSTraceable)] -pub struct ClassicScript { - text: DOMString, +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +pub enum ScriptType { + Classic, + Module, +} + +#[derive(JSTraceable, MallocSizeOf)] +pub struct CompiledSourceCode { + #[ignore_malloc_size_of = "SM handles JS values"] + pub source_code: RootedTraceableBox<Heap<*mut JSScript>>, + #[ignore_malloc_size_of = "Rc is hard"] + pub original_text: Rc<DOMString>, +} + +#[derive(JSTraceable)] +pub enum SourceCode { + Text(Rc<DOMString>), + Compiled(CompiledSourceCode), +} + +#[derive(JSTraceable, MallocSizeOf)] +pub struct ScriptOrigin { + #[ignore_malloc_size_of = "Rc is hard"] + code: SourceCode, url: ServoUrl, external: bool, + fetch_options: ScriptFetchOptions, + type_: ScriptType, } -impl ClassicScript { - fn internal(text: DOMString, url: ServoUrl) -> ClassicScript { - ClassicScript { - text: text, +impl ScriptOrigin { + pub fn internal( + text: Rc<DOMString>, + url: ServoUrl, + fetch_options: ScriptFetchOptions, + type_: ScriptType, + ) -> ScriptOrigin { + ScriptOrigin { + code: SourceCode::Text(text), url: url, external: false, + fetch_options, + type_, } } - fn external(text: DOMString, url: ServoUrl) -> ClassicScript { - ClassicScript { - text: text, + pub fn external( + text: Rc<DOMString>, + url: ServoUrl, + fetch_options: ScriptFetchOptions, + type_: ScriptType, + ) -> ScriptOrigin { + ScriptOrigin { + code: SourceCode::Text(text), url: url, external: true, + fetch_options, + type_, + } + } + + pub fn text(&self) -> Rc<DOMString> { + match &self.code { + SourceCode::Text(text) => Rc::clone(&text), + SourceCode::Compiled(compiled_script) => Rc::clone(&compiled_script.original_text), } } } -pub type ScriptResult = Result<ClassicScript, NetworkError>; +/// Final steps of <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> +fn finish_fetching_a_classic_script( + elem: &HTMLScriptElement, + script_kind: ExternalScriptKind, + url: ServoUrl, + load: ScriptResult, +) { + // Step 11, Asynchronously complete this algorithm with script, + // which refers to step 26.6 "When the chosen algorithm asynchronously completes", + // of https://html.spec.whatwg.org/multipage/#prepare-a-script + let document = document_from_node(&*elem); + + match script_kind { + ExternalScriptKind::Asap => document.asap_script_loaded(&elem, load), + ExternalScriptKind::AsapInOrder => document.asap_in_order_script_loaded(&elem, load), + ExternalScriptKind::Deferred => document.deferred_script_loaded(&elem, load), + ExternalScriptKind::ParsingBlocking => { + document.pending_parsing_blocking_script_loaded(&elem, load) + }, + } + + document.finish_load(LoadType::Script(url)); +} + +pub type ScriptResult = Result<ScriptOrigin, NetworkError>; /// The context required for asynchronously loading an external script source. -struct ScriptContext { +struct ClassicContext { /// The element that initiated the request. elem: Trusted<HTMLScriptElement>, /// The kind of external script. kind: ExternalScriptKind, /// The (fallback) character encoding argument to the "fetch a classic /// script" algorithm. - character_encoding: EncodingRef, + character_encoding: &'static Encoding, /// The response body received to date. data: Vec<u8>, /// The response metadata received to date. @@ -156,32 +346,42 @@ struct ScriptContext { /// The initial URL requested. url: ServoUrl, /// Indicates whether the request failed, and why - status: Result<(), NetworkError> + status: Result<(), NetworkError>, + /// The fetch options of the script + fetch_options: ScriptFetchOptions, + /// Timing object for this resource + resource_timing: ResourceFetchTiming, } -impl FetchResponseListener for ScriptContext { +impl FetchResponseListener for ClassicContext { fn process_request_body(&mut self) {} // TODO(KiChjang): Perhaps add custom steps to perform fetch here? fn process_request_eof(&mut self) {} // TODO(KiChjang): Perhaps add custom steps to perform fetch here? - fn process_response(&mut self, - metadata: Result<FetchMetadata, NetworkError>) { + fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { self.metadata = metadata.ok().map(|meta| match meta { FetchMetadata::Unfiltered(m) => m, - FetchMetadata::Filtered { unsafe_, .. } => unsafe_ + FetchMetadata::Filtered { unsafe_, .. } => unsafe_, }); - let status_code = self.metadata.as_ref().and_then(|m| { - match m.status { + let status_code = self + .metadata + .as_ref() + .and_then(|m| match m.status { Some((c, _)) => Some(c), _ => None, - } - }).unwrap_or(0); + }) + .unwrap_or(0); self.status = match status_code { - 0 => Err(NetworkError::Internal("No http status code received".to_owned())), - 200...299 => Ok(()), // HTTP ok status codes - _ => Err(NetworkError::Internal(format!("HTTP error code {}", status_code))) + 0 => Err(NetworkError::Internal( + "No http status code received".to_owned(), + )), + 200..=299 => Ok(()), // HTTP ok status codes + _ => Err(NetworkError::Internal(format!( + "HTTP error code {}", + status_code + ))), }; } @@ -191,103 +391,194 @@ impl FetchResponseListener for ScriptContext { } } - /// https://html.spec.whatwg.org/multipage/#fetch-a-classic-script + /// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> /// step 4-9 - fn process_response_eof(&mut self, response: Result<(), NetworkError>) { - // Step 5. - let load = response.and(self.status.clone()).map(|_| { - let metadata = self.metadata.take().unwrap(); + #[allow(unsafe_code)] + fn process_response_eof(&mut self, response: Result<ResourceFetchTiming, NetworkError>) { + let (source_text, final_url) = match (response.as_ref(), self.status.as_ref()) { + (Err(err), _) | (_, Err(err)) => { + // Step 6, response is an error. + finish_fetching_a_classic_script( + &*self.elem.root(), + self.kind.clone(), + self.url.clone(), + Err(err.clone()), + ); + return; + }, + (Ok(_), Ok(_)) => { + let metadata = self.metadata.take().unwrap(); + + // Step 7. + let encoding = metadata + .charset + .and_then(|encoding| Encoding::for_label(encoding.as_bytes())) + .unwrap_or(self.character_encoding); + + // Step 8. + let (source_text, _, _) = encoding.decode(&self.data); + (source_text, metadata.final_url) + }, + }; - // Step 6. - let encoding = metadata.charset - .and_then(|encoding| encoding_from_whatwg_label(&encoding)) - .unwrap_or(self.character_encoding); + let elem = self.elem.root(); + let global = elem.global(); + let cx = global.get_cx(); + let _ar = enter_realm(&*global); + + let options = unsafe { CompileOptionsWrapper::new(*cx, final_url.as_str(), 1) }; + + let can_compile_off_thread = pref!(dom.script.asynch) && + unsafe { CanCompileOffThread(*cx, options.ptr as *const _, source_text.len()) }; + + if can_compile_off_thread { + let source_string = source_text.to_string(); + + let context = Box::new(OffThreadCompilationContext { + script_element: self.elem.clone(), + script_kind: self.kind.clone(), + final_url, + url: self.url.clone(), + task_source: global.dom_manipulation_task_source(), + canceller: global.task_canceller(TaskSourceName::DOMManipulation), + script_text: source_string, + fetch_options: self.fetch_options.clone(), + }); + + unsafe { + assert!(!CompileOffThread1( + *cx, + options.ptr as *const _, + &mut transform_str_to_source_text(&context.script_text) as *mut _, + Some(off_thread_compilation_callback), + Box::into_raw(context) as *mut c_void, + ) + .is_null()); + } + } else { + let load = ScriptOrigin::external( + Rc::new(DOMString::from(source_text)), + final_url.clone(), + self.fetch_options.clone(), + ScriptType::Classic, + ); + finish_fetching_a_classic_script(&*elem, self.kind.clone(), self.url.clone(), Ok(load)); + } + } - // Step 7. - let source_text = encoding.decode(&self.data, DecoderTrap::Replace).unwrap(); - ClassicScript::external(DOMString::from(source_text), metadata.final_url) - }); + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } - // Step 9. - // https://html.spec.whatwg.org/multipage/#prepare-a-script - // Step 18.6 (When the chosen algorithm asynchronously completes). - let elem = self.elem.root(); - let document = document_from_node(&*elem); + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } - match self.kind { - ExternalScriptKind::Asap => document.asap_script_loaded(&elem, load), - ExternalScriptKind::AsapInOrder => document.asap_in_order_script_loaded(&elem, load), - ExternalScriptKind::Deferred => document.deferred_script_loaded(&elem, load), - ExternalScriptKind::ParsingBlocking => document.pending_parsing_blocking_script_loaded(&elem, load), - } + fn submit_resource_timing(&mut self) { + network_listener::submit_timing(self) + } +} - document.finish_load(LoadType::Script(self.url.clone())); +impl ResourceTimingListener for ClassicContext { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + let initiator_type = InitiatorType::LocalName( + self.elem + .root() + .upcast::<Element>() + .local_name() + .to_string(), + ); + (initiator_type, self.url.clone()) + } + + fn resource_timing_global(&self) -> DomRoot<GlobalScope> { + document_from_node(&*self.elem.root()).global() } } -impl PreInvoke for ScriptContext {} +impl PreInvoke for ClassicContext {} + +/// Steps 1-2 of <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> +// This function is also used to prefetch a script in `script::dom::servoparser::prefetch`. +pub(crate) fn script_fetch_request( + url: ServoUrl, + cors_setting: Option<CorsSettings>, + origin: ImmutableOrigin, + pipeline_id: PipelineId, + options: ScriptFetchOptions, +) -> RequestBuilder { + // We intentionally ignore options' credentials_mode member for classic scripts. + // The mode is initialized by create_a_potential_cors_request. + create_a_potential_cors_request( + url, + Destination::Script, + cors_setting, + None, + options.referrer, + ) + .origin(origin) + .pipeline_id(Some(pipeline_id)) + .parser_metadata(options.parser_metadata) + .integrity_metadata(options.integrity_metadata.clone()) + .referrer_policy(options.referrer_policy) +} -/// https://html.spec.whatwg.org/multipage/#fetch-a-classic-script -fn fetch_a_classic_script(script: &HTMLScriptElement, - kind: ExternalScriptKind, - url: ServoUrl, - cors_setting: Option<CorsSettings>, - integrity_metadata: String, - character_encoding: EncodingRef) { +/// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> +fn fetch_a_classic_script( + script: &HTMLScriptElement, + kind: ExternalScriptKind, + url: ServoUrl, + cors_setting: Option<CorsSettings>, + options: ScriptFetchOptions, + character_encoding: &'static Encoding, +) { let doc = document_from_node(script); // Step 1, 2. - let request = RequestInit { - url: url.clone(), - type_: RequestType::Script, - destination: Destination::Script, - // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request - // Step 1 - mode: match cors_setting { - Some(_) => RequestMode::CorsMode, - None => RequestMode::NoCors, - }, - // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request - // Step 3-4 - credentials_mode: match cors_setting { - Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin, - _ => CredentialsMode::Include, - }, - origin: doc.url(), - pipeline_id: Some(script.global().pipeline_id()), - referrer_url: Some(doc.url()), - referrer_policy: doc.get_referrer_policy(), - integrity_metadata: integrity_metadata, - .. RequestInit::default() - }; + let request = script_fetch_request( + url.clone(), + cors_setting, + doc.origin().immutable().clone(), + script.global().pipeline_id(), + options.clone(), + ); // TODO: Step 3, Add custom steps to perform fetch - let context = Arc::new(Mutex::new(ScriptContext { + let context = Arc::new(Mutex::new(ClassicContext { elem: Trusted::new(script), kind: kind, character_encoding: character_encoding, - data: vec!(), + data: vec![], metadata: None, url: url.clone(), - status: Ok(()) + status: Ok(()), + fetch_options: options, + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), })); let (action_sender, action_receiver) = ipc::channel().unwrap(); + let (task_source, canceller) = doc + .window() + .task_manager() + .networking_task_source_with_canceller(); let listener = NetworkListener { - context: context, - task_source: doc.window().networking_task_source(), - wrapper: Some(doc.window().get_runnable_wrapper()) + context, + task_source, + canceller: Some(canceller), }; - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_fetch(message.to().unwrap()); - }); + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + listener.notify_fetch(message.to().unwrap()); + }), + ); doc.fetch_async(LoadType::Script(url), request, action_sender); } impl HTMLScriptElement { - /// https://html.spec.whatwg.org/multipage/#prepare-a-script + /// <https://html.spec.whatwg.org/multipage/#prepare-a-script> pub fn prepare(&self) { // Step 1. if self.already_started.get() { @@ -298,112 +589,160 @@ impl HTMLScriptElement { let was_parser_inserted = self.parser_inserted.get(); self.parser_inserted.set(false); - // Step 3. + // Step 4. let element = self.upcast::<Element>(); - let async = element.has_attribute(&local_name!("async")); + let asynch = element.has_attribute(&local_name!("async")); // Note: confusingly, this is done if the element does *not* have an "async" attribute. - if was_parser_inserted && !async { + if was_parser_inserted && !asynch { self.non_blocking.set(true); } - // Step 4. + // Step 5-6. let text = self.Text(); if text.is_empty() && !element.has_attribute(&local_name!("src")) { return; } - // Step 5. - if !self.upcast::<Node>().is_in_doc() { + // Step 7. + if !self.upcast::<Node>().is_connected() { return; } - // Step 6. - if !self.is_javascript() { + let script_type = if let Some(ty) = self.get_script_type() { + ty + } else { + // Step 7. return; - } + }; - // Step 7. + // Step 8. if was_parser_inserted { self.parser_inserted.set(true); self.non_blocking.set(false); } - // Step 8. + // Step 10. self.already_started.set(true); - // Step 9. + // Step 12. let doc = document_from_node(self); if self.parser_inserted.get() && &*self.parser_document != &*doc { return; } - // Step 10. + // Step 13. if !doc.is_scripting_enabled() { return; } - // TODO(#4577): Step 11: CSP. + // Step 14 + if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic { + return; + } - // Step 12. - let for_attribute = element.get_attribute(&ns!(), &local_name!("for")); - let event_attribute = element.get_attribute(&ns!(), &local_name!("event")); - match (for_attribute.r(), event_attribute.r()) { - (Some(for_attribute), Some(event_attribute)) => { - let for_value = for_attribute.value().to_ascii_lowercase(); - let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS); - if for_value != "window" { - return; - } + // Step 15. + if !element.has_attribute(&local_name!("src")) && + doc.should_elements_inline_type_behavior_be_blocked( + &element, + csp::InlineCheckType::Script, + &text, + ) == csp::CheckResult::Blocked + { + warn!("Blocking inline script due to CSP"); + return; + } - let event_value = event_attribute.value().to_ascii_lowercase(); - let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS); - if event_value != "onload" && event_value != "onload()" { - return; - } - }, - (_, _) => (), + // Step 16. + if script_type == ScriptType::Classic { + let for_attribute = element.get_attribute(&ns!(), &local_name!("for")); + let event_attribute = element.get_attribute(&ns!(), &local_name!("event")); + match (for_attribute, event_attribute) { + (Some(ref for_attribute), Some(ref event_attribute)) => { + let for_value = for_attribute.value().to_ascii_lowercase(); + let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS); + if for_value != "window" { + return; + } + + let event_value = event_attribute.value().to_ascii_lowercase(); + let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS); + if event_value != "onload" && event_value != "onload()" { + return; + } + }, + (_, _) => (), + } } - // Step 13. - let encoding = element.get_attribute(&ns!(), &local_name!("charset")) - .and_then(|charset| encoding_from_whatwg_label(&charset.value())) - .unwrap_or_else(|| doc.encoding()); + // Step 17. + let encoding = element + .get_attribute(&ns!(), &local_name!("charset")) + .and_then(|charset| Encoding::for_label(charset.value().as_bytes())) + .unwrap_or_else(|| doc.encoding()); - // Step 14. + // Step 18. let cors_setting = cors_setting_for_element(element); - // TODO: Step 15: Module script credentials mode. + // Step 19. + let module_credentials_mode = match script_type { + ScriptType::Classic => CredentialsMode::CredentialsSameOrigin, + ScriptType::Module => reflect_cross_origin_attribute(element).map_or( + CredentialsMode::CredentialsSameOrigin, + |attr| match &*attr { + "use-credentials" => CredentialsMode::Include, + "anonymous" => CredentialsMode::CredentialsSameOrigin, + _ => CredentialsMode::CredentialsSameOrigin, + }, + ), + }; - // TODO: Step 16: Nonce. + // TODO: Step 20: Nonce. - // Step 17: Integrity metadata. + // Step 21: Integrity metadata. let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity")); - let integrity_val = im_attribute.r().map(|a| a.value()); + let integrity_val = im_attribute.as_ref().map(|a| a.value()); let integrity_metadata = match integrity_val { Some(ref value) => &***value, None => "", }; - // TODO: Step 18: parser state. + // TODO: Step 22: referrer policy + + // Step 23 + let parser_metadata = if self.parser_inserted.get() { + ParserMetadata::ParserInserted + } else { + ParserMetadata::NotParserInserted + }; + + // Step 24. + let options = ScriptFetchOptions { + cryptographic_nonce: "".into(), + integrity_metadata: integrity_metadata.to_owned(), + parser_metadata, + referrer: self.global().get_referrer(), + referrer_policy: referrer_policy_for_element(self.upcast::<Element>()), + credentials_mode: module_credentials_mode, + }; - // TODO: Step 19: environment settings object. + // TODO: Step 23: environment settings object. let base_url = doc.base_url(); if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) { - // Step 20. + // Step 26. - // Step 20.1. + // Step 26.1. let src = src.value(); - // Step 20.2. + // Step 26.2. if src.is_empty() { self.queue_error_event(); return; } - // Step 20.3: The "from an external file"" flag is stored in ClassicScript. + // Step 26.3: The "from an external file"" flag is stored in ScriptOrigin. - // Step 20.4-20.5. + // Step 26.4-26.5. let url = match base_url.join(&src) { Ok(url) => url, Err(_) => { @@ -413,94 +752,233 @@ impl HTMLScriptElement { }, }; - // Preparation for step 22. - let kind = if element.has_attribute(&local_name!("defer")) && was_parser_inserted && !async { - // Step 22.a: classic, has src, has defer, was parser-inserted, is not async. - ExternalScriptKind::Deferred - } else if was_parser_inserted && !async { - // Step 22.b: classic, has src, was parser-inserted, is not async. - ExternalScriptKind::ParsingBlocking - } else if !async && !self.non_blocking.get() { - // Step 22.c: classic, has src, is not async, is not non-blocking. - ExternalScriptKind::AsapInOrder - } else { - // Step 22.d: classic, has src. - ExternalScriptKind::Asap - }; - - // Step 20.6. - fetch_a_classic_script(self, kind, url, cors_setting, integrity_metadata.to_owned(), encoding); - - // Step 22. - match kind { - ExternalScriptKind::Deferred => doc.add_deferred_script(self), - ExternalScriptKind::ParsingBlocking => doc.set_pending_parsing_blocking_script(self, None), - ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self), - ExternalScriptKind::Asap => doc.add_asap_script(self), + // Step 26.6. + match script_type { + ScriptType::Classic => { + // Preparation for step 26. + let kind = if element.has_attribute(&local_name!("defer")) && + was_parser_inserted && + !asynch + { + // Step 26.a: classic, has src, has defer, was parser-inserted, is not async. + ExternalScriptKind::Deferred + } else if was_parser_inserted && !asynch { + // Step 26.c: classic, has src, was parser-inserted, is not async. + ExternalScriptKind::ParsingBlocking + } else if !asynch && !self.non_blocking.get() { + // Step 26.d: classic, has src, is not async, is not non-blocking. + ExternalScriptKind::AsapInOrder + } else { + // Step 26.f: classic, has src. + ExternalScriptKind::Asap + }; + + // Step 24.6. + fetch_a_classic_script(self, kind, url, cors_setting, options, encoding); + + // Step 23. + match kind { + ExternalScriptKind::Deferred => doc.add_deferred_script(self), + ExternalScriptKind::ParsingBlocking => { + doc.set_pending_parsing_blocking_script(self, None) + }, + ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self), + ExternalScriptKind::Asap => doc.add_asap_script(self), + } + }, + ScriptType::Module => { + fetch_external_module_script( + ModuleOwner::Window(Trusted::new(self)), + url.clone(), + Destination::Script, + options, + ); + + if !asynch && was_parser_inserted { + doc.add_deferred_script(self); + } else if !asynch && !self.non_blocking.get() { + doc.push_asap_in_order_script(self); + } else { + doc.add_asap_script(self); + }; + }, } } else { - // Step 21. + // Step 27. assert!(!text.is_empty()); - let result = Ok(ClassicScript::internal(text, base_url)); - - // Step 22. - if was_parser_inserted && - doc.get_current_parser().map_or(false, |parser| parser.script_nesting_level() <= 1) && - doc.get_script_blocking_stylesheets_count() > 0 { - // Step 22.e: classic, has no src, was parser-inserted, is blocked on stylesheet. - doc.set_pending_parsing_blocking_script(self, Some(result)); - } else { - // Step 22.f: otherwise. - self.execute(result); + + let text_rc = Rc::new(text); + + // Step 27-1. & 27-2. + let result = Ok(ScriptOrigin::internal( + Rc::clone(&text_rc), + base_url.clone(), + options.clone(), + script_type.clone(), + )); + + // Step 27-2. + match script_type { + ScriptType::Classic => { + if was_parser_inserted && + doc.get_current_parser() + .map_or(false, |parser| parser.script_nesting_level() <= 1) && + doc.get_script_blocking_stylesheets_count() > 0 + { + // Step 27.h: classic, has no src, was parser-inserted, is blocked on stylesheet. + doc.set_pending_parsing_blocking_script(self, Some(result)); + } else { + // Step 27.i: otherwise. + self.execute(result); + } + }, + ScriptType::Module => { + // We should add inline module script elements + // into those vectors in case that there's no + // descendants in the inline module script. + if !asynch && was_parser_inserted { + doc.add_deferred_script(self); + } else if !asynch && !self.non_blocking.get() { + doc.push_asap_in_order_script(self); + } else { + doc.add_asap_script(self); + }; + + fetch_inline_module_script( + ModuleOwner::Window(Trusted::new(self)), + text_rc, + base_url.clone(), + self.id.clone(), + options, + ); + }, } } } - fn unminify_js(&self, script: &mut ClassicScript) { - if !opts::get().unminify_js { + fn unminify_js(&self, script: &mut ScriptOrigin) { + if !self.parser_document.window().unminify_js() { return; } - match Command::new("js-beautify") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() { - Err(_) => { - warn!("Failed to execute js-beautify. Will store unmodified script"); - }, - Ok(process) => { - let mut script_content = String::from(script.text.clone()); - let _ = process.stdin.unwrap().write_all(script_content.as_bytes()); - script_content.clear(); - let _ = process.stdout.unwrap().read_to_string(&mut script_content); + // Write the minified code to a temporary file and pass its path as an argument + // to js-beautify to read from. Meanwhile, redirect the process' stdout into + // another temporary file and read that into a string. This avoids some hangs + // observed on macOS when using direct input/output pipes with very large + // unminified content. + let (input, output) = (tempfile::NamedTempFile::new(), tempfile::tempfile()); + if let (Ok(mut input), Ok(mut output)) = (input, output) { + match &script.code { + SourceCode::Text(text) => { + input.write_all(text.as_bytes()).unwrap(); + }, + SourceCode::Compiled(compiled_source_code) => { + input + .write_all(compiled_source_code.original_text.as_bytes()) + .unwrap(); + }, + } + match Command::new("js-beautify") + .arg(input.path()) + .stdout(output.try_clone().unwrap()) + .status() + { + Ok(status) if status.success() => { + let mut script_content = String::new(); + output.seek(std::io::SeekFrom::Start(0)).unwrap(); + output.read_to_string(&mut script_content).unwrap(); + script.code = SourceCode::Text(Rc::new(DOMString::from(script_content))); + }, + _ => { + warn!("Failed to execute js-beautify. Will store unmodified script"); + }, + } + } else { + warn!("Error creating input and output files for unminify"); + } - script.text = DOMString::from(script_content); + let path; + match window_from_node(self).unminified_js_dir() { + Some(unminified_js_dir) => path = PathBuf::from(unminified_js_dir), + None => { + warn!("Unminified script directory not found"); + return; }, } - - let path = PathBuf::from(window_from_node(self).unminified_js_dir().unwrap()); - let path = if script.external { + let (base, has_name) = match script.url.as_str().ends_with("/") { + true => ( + path.join(&script.url[url::Position::BeforeHost..]) + .as_path() + .to_owned(), + false, + ), + false => ( + path.join(&script.url[url::Position::BeforeHost..]) + .parent() + .unwrap() + .to_owned(), + true, + ), + }; + match create_dir_all(base.clone()) { + Ok(()) => debug!("Created base dir: {:?}", base), + Err(e) => { + debug!("Failed to create base dir: {:?}, {:?}", base, e); + return; + }, + } + let path = if script.external && has_name { // External script. - let path_parts = script.url.path_segments().unwrap(); - match path_parts.last() { - Some(script_name) => path.join(script_name), - None => path.join(Uuid::new_v4().to_string()), - } + path.join(&script.url[url::Position::BeforeHost..]) } else { - // Inline script. - path.join(Uuid::new_v4().to_string()) + // Inline script or url ends with '/' + base.join(Uuid::new_v4().to_string()) }; debug!("script will be stored in {:?}", path); match File::create(&path) { - Ok(mut file) => file.write_all(script.text.as_bytes()).unwrap(), + Ok(mut file) => match &script.code { + SourceCode::Text(text) => file.write_all(text.as_bytes()).unwrap(), + SourceCode::Compiled(compiled_source_code) => { + file.write_all(compiled_source_code.original_text.as_bytes()) + .unwrap(); + }, + }, Err(why) => warn!("Could not store script {:?}", why), } } - /// https://html.spec.whatwg.org/multipage/#execute-the-script-block - pub fn execute(&self, result: Result<ClassicScript, NetworkError>) { + fn substitute_with_local_script(&self, script: &mut ScriptOrigin) { + if self + .parser_document + .window() + .local_script_source() + .is_none() || + !script.external + { + return; + } + let mut path = PathBuf::from( + self.parser_document + .window() + .local_script_source() + .clone() + .unwrap(), + ); + path = path.join(&script.url[url::Position::BeforeHost..]); + debug!("Attempting to read script stored at: {:?}", path); + match read_to_string(path.clone()) { + Ok(local_script) => { + debug!("Found script stored at: {:?}", path); + script.code = SourceCode::Text(Rc::new(DOMString::from(local_script))); + }, + Err(why) => warn!("Could not restore script from file {:?}", why), + } + } + + /// <https://html.spec.whatwg.org/multipage/#execute-the-script-block> + pub fn execute(&self, result: ScriptResult) { // Step 1. let doc = document_from_node(self); if self.parser_inserted.get() && &*doc != &*self.parser_document { @@ -513,15 +991,18 @@ impl HTMLScriptElement { warn!("error loading script {:?}", e); self.dispatch_error_event(); return; - } + }, Ok(script) => script, }; - self.unminify_js(&mut script); + if script.type_ == ScriptType::Classic { + self.unminify_js(&mut script); + self.substitute_with_local_script(&mut script); + } // Step 3. - let neutralized_doc = if script.external { + let neutralized_doc = if script.external || script.type_ == ScriptType::Module { debug!("loading external script, url = {}", script.url); let doc = document_from_node(self); doc.incr_ignore_destructive_writes_counter(); @@ -534,28 +1015,35 @@ impl HTMLScriptElement { let document = document_from_node(self); let old_script = document.GetCurrentScript(); - // Step 5.a.1. - document.set_current_script(Some(self)); - - // Step 5.a.2. - self.run_a_classic_script(&script); + match script.type_ { + ScriptType::Classic => document.set_current_script(Some(self)), + ScriptType::Module => document.set_current_script(None), + } - // Step 6. - document.set_current_script(old_script.r()); + match script.type_ { + ScriptType::Classic => { + self.run_a_classic_script(&script); + document.set_current_script(old_script.as_deref()); + }, + ScriptType::Module => { + assert!(document.GetCurrentScript().is_none()); + self.run_a_module_script(&script, false); + }, + } - // Step 7. + // Step 5. if let Some(doc) = neutralized_doc { doc.decr_ignore_destructive_writes_counter(); } - // Step 8. + // Step 6. if script.external { self.dispatch_load_event(); } } // https://html.spec.whatwg.org/multipage/#run-a-classic-script - pub fn run_a_classic_script(&self, script: &ClassicScript) { + pub fn run_a_classic_script(&self, script: &ScriptOrigin) { // TODO use a settings object rather than this element's document/window // Step 2 let document = document_from_node(self); @@ -565,82 +1053,177 @@ impl HTMLScriptElement { // Steps 4-10 let window = window_from_node(self); - let line_number = if script.external { 1 } else { self.line_number as u32 }; - rooted!(in(window.get_cx()) let mut rval = UndefinedValue()); + let line_number = if script.external { + 1 + } else { + self.line_number as u32 + }; + rooted!(in(*window.get_cx()) let mut rval = UndefinedValue()); let global = window.upcast::<GlobalScope>(); global.evaluate_script_on_global_with_result( - &script.text, script.url.as_str(), rval.handle_mut(), line_number); + &script.code, + script.url.as_str(), + rval.handle_mut(), + line_number, + script.fetch_options.clone(), + script.url.clone(), + ); + } + + #[allow(unsafe_code)] + /// https://html.spec.whatwg.org/multipage/#run-a-module-script + pub fn run_a_module_script(&self, script: &ScriptOrigin, _rethrow_errors: bool) { + // TODO use a settings object rather than this element's document/window + // Step 2 + let document = document_from_node(self); + if !document.is_fully_active() || !document.is_scripting_enabled() { + return; + } + + // Step 4 + let window = window_from_node(self); + let global = window.upcast::<GlobalScope>(); + let _aes = AutoEntryScript::new(&global); + + let tree = if script.external { + global.get_module_map().borrow().get(&script.url).cloned() + } else { + global + .get_inline_module_map() + .borrow() + .get(&self.id.clone()) + .cloned() + }; + + if let Some(module_tree) = tree { + // Step 6. + { + let module_error = module_tree.get_rethrow_error().borrow(); + let network_error = module_tree.get_network_error().borrow(); + if module_error.is_some() && network_error.is_none() { + module_tree.report_error(&global); + return; + } + } + + let record = module_tree + .get_record() + .borrow() + .as_ref() + .map(|record| record.handle()); + + if let Some(record) = record { + let evaluated = module_tree.execute_module(global, record); + + if let Err(exception) = evaluated { + module_tree.set_rethrow_error(exception); + module_tree.report_error(&global); + return; + } + } + } } pub fn queue_error_event(&self) { let window = window_from_node(self); - window.dom_manipulation_task_source().queue_simple_event(self.upcast(), atom!("error"), &window); + window + .task_manager() + .dom_manipulation_task_source() + .queue_simple_event(self.upcast(), atom!("error"), &window); } pub fn dispatch_load_event(&self) { - self.dispatch_event(atom!("load"), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable); + self.dispatch_event( + atom!("load"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); } pub fn dispatch_error_event(&self) { - self.dispatch_event(atom!("error"), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable); + self.dispatch_event( + atom!("error"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); } - pub fn is_javascript(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#prepare-a-script Step 7. + pub fn get_script_type(&self) -> Option<ScriptType> { let element = self.upcast::<Element>(); + let type_attr = element.get_attribute(&ns!(), &local_name!("type")); - let is_js = match type_attr.as_ref().map(|s| s.value()) { - Some(ref s) if s.is_empty() => { - // type attr exists, but empty means js + let language_attr = element.get_attribute(&ns!(), &local_name!("language")); + + let script_type = match ( + type_attr.as_ref().map(|t| t.value()), + language_attr.as_ref().map(|l| l.value()), + ) { + (Some(ref ty), _) if ty.is_empty() => { debug!("script type empty, inferring js"); - true + Some(ScriptType::Classic) }, - Some(s) => { - debug!("script type={}", &**s); - SCRIPT_JS_MIMES.contains(&s.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS)) + (None, Some(ref lang)) if lang.is_empty() => { + debug!("script type empty, inferring js"); + Some(ScriptType::Classic) + }, + (None, None) => { + debug!("script type empty, inferring js"); + Some(ScriptType::Classic) + }, + (None, Some(ref lang)) => { + debug!("script language={}", &***lang); + let language = format!("text/{}", &***lang); + + if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) { + Some(ScriptType::Classic) + } else { + None + } + }, + (Some(ref ty), _) => { + debug!("script type={}", &***ty); + + if &***ty == String::from("module") { + return Some(ScriptType::Module); + } + + if SCRIPT_JS_MIMES + .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS)) + { + Some(ScriptType::Classic) + } else { + None + } }, - None => { - debug!("no script type"); - let language_attr = element.get_attribute(&ns!(), &local_name!("language")); - let is_js = match language_attr.as_ref().map(|s| s.value()) { - Some(ref s) if s.is_empty() => { - debug!("script language empty, inferring js"); - true - }, - Some(s) => { - debug!("script language={}", &**s); - let mut language = format!("text/{}", &**s); - language.make_ascii_lowercase(); - SCRIPT_JS_MIMES.contains(&&*language) - }, - None => { - debug!("no script type or language, inferring js"); - true - } - }; - // https://github.com/rust-lang/rust/issues/21114 - is_js - } }; + // https://github.com/rust-lang/rust/issues/21114 - is_js + script_type } pub fn set_parser_inserted(&self, parser_inserted: bool) { self.parser_inserted.set(parser_inserted); } + pub fn get_parser_inserted(&self) -> bool { + self.parser_inserted.get() + } + pub fn set_already_started(&self, already_started: bool) { self.already_started.set(already_started); } - fn dispatch_event(&self, - type_: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable) -> EventStatus { + pub fn get_non_blocking(&self) -> bool { + self.non_blocking.get() + } + + fn dispatch_event( + &self, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + ) -> EventStatus { let window = window_from_node(self); let event = Event::new(window.upcast(), type_, bubbles, cancelable); event.fire(self.upcast()) @@ -648,8 +1231,8 @@ impl HTMLScriptElement { } impl VirtualMethods for HTMLScriptElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -657,7 +1240,7 @@ impl VirtualMethods for HTMLScriptElement { match *attr.local_name() { local_name!("src") => { if let AttributeMutation::Set(_) = mutation { - if !self.parser_inserted.get() && self.upcast::<Node>().is_in_doc() { + if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() { self.prepare(); } } @@ -670,30 +1253,39 @@ impl VirtualMethods for HTMLScriptElement { if let Some(ref s) = self.super_type() { s.children_changed(mutation); } - if !self.parser_inserted.get() && self.upcast::<Node>().is_in_doc() { + if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() { self.prepare(); } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - if tree_in_doc && !self.parser_inserted.get() { - self.prepare(); + if context.tree_connected && !self.parser_inserted.get() { + let script = Trusted::new(self); + document_from_node(self).add_delayed_task(task!(ScriptDelayedInitialize: move || { + script.root().prepare(); + })); } } - fn cloning_steps(&self, copy: &Node, maybe_doc: Option<&Document>, - clone_children: CloneChildrenFlag) { + fn cloning_steps( + &self, + copy: &Node, + maybe_doc: Option<&Document>, + clone_children: CloneChildrenFlag, + ) { if let Some(ref s) = self.super_type() { s.cloning_steps(copy, maybe_doc, clone_children); } // https://html.spec.whatwg.org/multipage/#already-started if self.already_started.get() { - copy.downcast::<HTMLScriptElement>().unwrap().set_already_started(true); + copy.downcast::<HTMLScriptElement>() + .unwrap() + .set_already_started(true); } } } @@ -701,8 +1293,9 @@ impl VirtualMethods for HTMLScriptElement { impl HTMLScriptElementMethods for HTMLScriptElement { // https://html.spec.whatwg.org/multipage/#dom-script-src make_url_getter!(Src, "src"); + // https://html.spec.whatwg.org/multipage/#dom-script-src - make_setter!(SetSrc, "src"); + make_url_setter!(SetSrc, "src"); // https://html.spec.whatwg.org/multipage/#dom-script-type make_getter!(Type, "type"); @@ -716,13 +1309,16 @@ impl HTMLScriptElementMethods for HTMLScriptElement { // https://html.spec.whatwg.org/multipage/#dom-script-async fn Async(&self) -> bool { - self.non_blocking.get() || self.upcast::<Element>().has_attribute(&local_name!("async")) + self.non_blocking.get() || + self.upcast::<Element>() + .has_attribute(&local_name!("async")) } // https://html.spec.whatwg.org/multipage/#dom-script-async fn SetAsync(&self, value: bool) { self.non_blocking.set(false); - self.upcast::<Element>().set_bool_attribute(&local_name!("async"), value); + self.upcast::<Element>() + .set_bool_attribute(&local_name!("async"), value); } // https://html.spec.whatwg.org/multipage/#dom-script-defer @@ -730,6 +1326,11 @@ impl HTMLScriptElementMethods for HTMLScriptElement { // https://html.spec.whatwg.org/multipage/#dom-script-defer make_bool_setter!(SetDefer, "defer"); + // https://html.spec.whatwg.org/multipage/#dom-script-nomodule + make_bool_getter!(NoModule, "nomodule"); + // https://html.spec.whatwg.org/multipage/#dom-script-nomodule + make_bool_setter!(SetNoModule, "nomodule"); + // https://html.spec.whatwg.org/multipage/#dom-script-integrity make_getter!(Integrity, "integrity"); // https://html.spec.whatwg.org/multipage/#dom-script-integrity @@ -755,6 +1356,14 @@ impl HTMLScriptElementMethods for HTMLScriptElement { set_cross_origin_attribute(self.upcast::<Element>(), value); } + // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy + fn ReferrerPolicy(&self) -> DOMString { + reflect_referrer_policy_attribute(self.upcast::<Element>()) + } + + // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy + make_setter!(SetReferrerPolicy, "referrerpolicy"); + // https://html.spec.whatwg.org/multipage/#dom-script-text fn Text(&self) -> DOMString { self.upcast::<Node>().child_text_content() diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 2d4dc4758b6..9d36413e4b5 100755 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -1,43 +1,42 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; -use dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods; -use dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods; -use dom::bindings::codegen::Bindings::HTMLSelectElementBinding; -use dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::UnionTypes::HTMLElementOrLong; -use dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement; -//use dom::bindings::error::ErrorResult; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::htmlcollection::CollectionFilter; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlformelement::{FormDatumValue, FormControl, FormDatum, HTMLFormElement}; -use dom::htmloptgroupelement::HTMLOptGroupElement; -use dom::htmloptionelement::HTMLOptionElement; -use dom::htmloptionscollection::HTMLOptionsCollection; -use dom::node::{Node, UnbindContext, window_from_node}; -use dom::nodelist::NodeList; -use dom::validation::Validatable; -use dom::validitystate::{ValidityState, ValidationFlags}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; +use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLOptionsCollectionBinding::HTMLOptionsCollectionMethods; +use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::UnionTypes::HTMLElementOrLong; +use crate::dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::htmlcollection::CollectionFilter; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlformelement::{FormControl, FormDatum, FormDatumValue, HTMLFormElement}; +use crate::dom::htmloptgroupelement::HTMLOptGroupElement; +use crate::dom::htmloptionelement::HTMLOptionElement; +use crate::dom::htmloptionscollection::HTMLOptionsCollection; +use crate::dom::node::{window_from_node, BindContext, Node, UnbindContext}; +use crate::dom::nodelist::NodeList; +use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable}; +use crate::dom::validitystate::{ValidationFlags, ValidityState}; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use std::default::Default; use std::iter; use style::attr::AttrValue; -use style::element_state::*; +use style::element_state::ElementState; -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] struct OptionsFilter; impl CollectionFilter for OptionsFilter { fn filter<'a>(&self, elem: &'a Element, root: &'a Node) -> bool { @@ -51,8 +50,7 @@ impl CollectionFilter for OptionsFilter { } match node.GetParentNode() { - Some(optgroup) => - optgroup.is::<HTMLOptGroupElement>() && root.is_parent_of(&optgroup), + Some(optgroup) => optgroup.is::<HTMLOptGroupElement>() && root.is_parent_of(&optgroup), None => false, } } @@ -61,48 +59,75 @@ impl CollectionFilter for OptionsFilter { #[dom_struct] pub struct HTMLSelectElement { htmlelement: HTMLElement, - options: MutNullableJS<HTMLOptionsCollection>, - form_owner: MutNullableJS<HTMLFormElement>, + options: MutNullableDom<HTMLOptionsCollection>, + form_owner: MutNullableDom<HTMLFormElement>, + labels_node_list: MutNullableDom<NodeList>, + validity_state: MutNullableDom<ValidityState>, } static DEFAULT_SELECT_SIZE: u32 = 0; impl HTMLSelectElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLSelectElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLSelectElement { HTMLSelectElement { - htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE, - local_name, prefix, document), - options: Default::default(), - form_owner: Default::default(), + htmlelement: HTMLElement::new_inherited_with_state( + ElementState::IN_ENABLED_STATE, + local_name, + prefix, + document, + ), + options: Default::default(), + form_owner: Default::default(), + labels_node_list: Default::default(), + validity_state: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLSelectElement> { - Node::reflect_node(box HTMLSelectElement::new_inherited(local_name, prefix, document), - document, - HTMLSelectElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLSelectElement> { + let n = Node::reflect_node( + Box::new(HTMLSelectElement::new_inherited( + local_name, prefix, document, + )), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } // https://html.spec.whatwg.org/multipage/#concept-select-option-list - fn list_of_options(&self) -> impl Iterator<Item=Root<HTMLOptionElement>> { - self.upcast::<Node>() - .children() - .flat_map(|node| { - if node.is::<HTMLOptionElement>() { - let node = Root::downcast::<HTMLOptionElement>(node).unwrap(); - Choice3::First(iter::once(node)) - } else if node.is::<HTMLOptGroupElement>() { - Choice3::Second(node.children().filter_map(Root::downcast)) - } else { - Choice3::Third(iter::empty()) - } + pub fn list_of_options(&self) -> impl Iterator<Item = DomRoot<HTMLOptionElement>> { + self.upcast::<Node>().children().flat_map(|node| { + if node.is::<HTMLOptionElement>() { + let node = DomRoot::downcast::<HTMLOptionElement>(node).unwrap(); + Choice3::First(iter::once(node)) + } else if node.is::<HTMLOptGroupElement>() { + Choice3::Second(node.children().filter_map(DomRoot::downcast)) + } else { + Choice3::Third(iter::empty()) + } + }) + } + + // https://html.spec.whatwg.org/multipage/#placeholder-label-option + fn get_placeholder_label_option(&self) -> Option<DomRoot<HTMLOptionElement>> { + if self.Required() && !self.Multiple() && self.display_size() == 1 { + self.list_of_options().next().filter(|node| { + let parent = node.upcast::<Node>().GetParentNode(); + node.Value().is_empty() && parent.as_deref() == Some(self.upcast()) }) + } else { + None + } } // https://html.spec.whatwg.org/multipage/#the-select-element:concept-form-reset-control @@ -120,17 +145,17 @@ impl HTMLSelectElement { return; } - let mut first_enabled: Option<Root<HTMLOptionElement>> = None; - let mut last_selected: Option<Root<HTMLOptionElement>> = None; + let mut first_enabled: Option<DomRoot<HTMLOptionElement>> = None; + let mut last_selected: Option<DomRoot<HTMLOptionElement>> = None; for opt in self.list_of_options() { if opt.Selected() { opt.set_selectedness(false); - last_selected = Some(Root::from_ref(&opt)); + last_selected = Some(DomRoot::from_ref(&opt)); } let element = opt.upcast::<Element>(); if first_enabled.is_none() && !element.disabled_state() { - first_enabled = Some(Root::from_ref(&opt)); + first_enabled = Some(DomRoot::from_ref(&opt)); } } @@ -155,7 +180,7 @@ impl HTMLSelectElement { data_set.push(FormDatum { ty: self.Type(), name: self.Name(), - value: FormDatumValue::String(opt.Value()) + value: FormDatumValue::String(opt.Value()), }); } } @@ -175,28 +200,26 @@ impl HTMLSelectElement { // https://html.spec.whatwg.org/multipage/#concept-select-size fn display_size(&self) -> u32 { - if self.Size() == 0 { - if self.Multiple() { - 4 - } else { - 1 - } - } else { - self.Size() - } - } + if self.Size() == 0 { + if self.Multiple() { + 4 + } else { + 1 + } + } else { + self.Size() + } + } } impl HTMLSelectElementMethods for HTMLSelectElement { - // https://html.spec.whatwg.org/multipage/#dom-cva-validity - fn Validity(&self) -> Root<ValidityState> { - let window = window_from_node(self); - ValidityState::new(&window, self.upcast()) - } - - // Note: this function currently only exists for union.html. // https://html.spec.whatwg.org/multipage/#dom-select-add - fn Add(&self, _element: HTMLOptionElementOrHTMLOptGroupElement, _before: Option<HTMLElementOrLong>) { + fn Add( + &self, + element: HTMLOptionElementOrHTMLOptGroupElement, + before: Option<HTMLElementOrLong>, + ) -> ErrorResult { + self.Options().Add(element, before) } // https://html.spec.whatwg.org/multipage/#dom-fe-disabled @@ -206,7 +229,7 @@ impl HTMLSelectElementMethods for HTMLSelectElement { make_bool_setter!(SetDisabled, "disabled"); // https://html.spec.whatwg.org/multipage/#dom-fae-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner() } @@ -220,7 +243,13 @@ impl HTMLSelectElementMethods for HTMLSelectElement { make_getter!(Name, "name"); // https://html.spec.whatwg.org/multipage/#dom-fe-name - make_setter!(SetName, "name"); + make_atomic_setter!(SetName, "name"); + + // https://html.spec.whatwg.org/multipage/#dom-select-required + make_bool_getter!(Required, "required"); + + // https://html.spec.whatwg.org/multipage/#dom-select-required + make_bool_setter!(SetRequired, "required"); // https://html.spec.whatwg.org/multipage/#dom-select-size make_uint_getter!(Size, "size", DEFAULT_SELECT_SIZE); @@ -238,16 +267,13 @@ impl HTMLSelectElementMethods for HTMLSelectElement { } // https://html.spec.whatwg.org/multipage/#dom-lfe-labels - fn Labels(&self) -> Root<NodeList> { - self.upcast::<HTMLElement>().labels() - } + make_labels_getter!(Labels, labels_node_list); // https://html.spec.whatwg.org/multipage/#dom-select-options - fn Options(&self) -> Root<HTMLOptionsCollection> { + fn Options(&self) -> DomRoot<HTMLOptionsCollection> { self.options.or_init(|| { let window = window_from_node(self); - HTMLOptionsCollection::new( - &window, self, box OptionsFilter) + HTMLOptionsCollection::new(&window, self, Box::new(OptionsFilter)) }) } @@ -262,18 +288,25 @@ impl HTMLSelectElementMethods for HTMLSelectElement { } // https://html.spec.whatwg.org/multipage/#dom-select-item - fn Item(&self, index: u32) -> Option<Root<Element>> { + fn Item(&self, index: u32) -> Option<DomRoot<Element>> { self.Options().upcast().Item(index) } // https://html.spec.whatwg.org/multipage/#dom-select-item - fn IndexedGetter(&self, index: u32) -> Option<Root<Element>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> { self.Options().IndexedGetter(index) } + // https://html.spec.whatwg.org/multipage/#dom-select-setter + fn IndexedSetter(&self, index: u32, value: Option<&HTMLOptionElement>) -> ErrorResult { + self.Options().IndexedSetter(index, value) + } + // https://html.spec.whatwg.org/multipage/#dom-select-nameditem - fn NamedItem(&self, name: DOMString) -> Option<Root<HTMLOptionElement>> { - self.Options().NamedGetter(name).map_or(None, |e| Root::downcast::<HTMLOptionElement>(e)) + fn NamedItem(&self, name: DOMString) -> Option<DomRoot<HTMLOptionElement>> { + self.Options() + .NamedGetter(name) + .map_or(None, |e| DomRoot::downcast::<HTMLOptionElement>(e)) } // https://html.spec.whatwg.org/multipage/#dom-select-remove @@ -338,11 +371,41 @@ impl HTMLSelectElementMethods for HTMLSelectElement { } } } + + // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate + fn WillValidate(&self) -> bool { + self.is_instance_validatable() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validity + fn Validity(&self) -> DomRoot<ValidityState> { + self.validity_state() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity + fn CheckValidity(&self) -> bool { + self.check_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity + fn ReportValidity(&self) -> bool { + self.report_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage + fn ValidationMessage(&self) -> DOMString { + self.validation_message() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity + fn SetCustomValidity(&self, error: DOMString) { + self.validity_state().set_custom_error_message(error); + } } impl VirtualMethods for HTMLSelectElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -359,7 +422,7 @@ impl VirtualMethods for HTMLSelectElement { el.set_disabled_state(false); el.set_enabled_state(true); el.check_ancestors_disabled_state_for_form_control(); - } + }, } }, &local_name!("form") => { @@ -369,12 +432,13 @@ impl VirtualMethods for HTMLSelectElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - self.upcast::<Element>().check_ancestors_disabled_state_for_form_control(); + self.upcast::<Element>() + .check_ancestors_disabled_state_for_form_control(); } fn unbind_from_tree(&self, context: &UnbindContext) { @@ -382,7 +446,10 @@ impl VirtualMethods for HTMLSelectElement { let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); - if node.ancestors().any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) { + if node + .ancestors() + .any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) + { el.check_ancestors_disabled_state_for_form_control(); } else { el.check_disabled_attribute(); @@ -392,13 +459,16 @@ impl VirtualMethods for HTMLSelectElement { fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("size") => AttrValue::from_u32(value.into(), DEFAULT_SELECT_SIZE), - _ => self.super_type().unwrap().parse_plain_attribute(local_name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(local_name, value), } } } impl FormControl for HTMLSelectElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } @@ -412,13 +482,36 @@ impl FormControl for HTMLSelectElement { } impl Validatable for HTMLSelectElement { - fn is_instance_validatable(&self) -> bool { - true + fn as_element(&self) -> &Element { + self.upcast() } - fn validate(&self, validate_flags: ValidationFlags) -> bool { - if validate_flags.is_empty() {} - // Need more flag check for different validation types later - true + + fn validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), self.upcast())) + } + + fn is_instance_validatable(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation + !self.upcast::<Element>().disabled_state() && !is_barred_by_datalist_ancestor(self.upcast()) + } + + fn perform_validation(&self, validate_flags: ValidationFlags) -> ValidationFlags { + let mut failed_flags = ValidationFlags::empty(); + + // https://html.spec.whatwg.org/multipage/#suffering-from-being-missing + // https://html.spec.whatwg.org/multipage/#the-select-element%3Asuffering-from-being-missing + if validate_flags.contains(ValidationFlags::VALUE_MISSING) && self.Required() { + let placeholder = self.get_placeholder_label_option(); + let selected_option = self + .list_of_options() + .filter(|e| e.Selected() && placeholder.as_ref() != Some(e)) + .next(); + failed_flags.set(ValidationFlags::VALUE_MISSING, selected_option.is_none()); + } + + failed_flags } } @@ -429,7 +522,10 @@ enum Choice3<I, J, K> { } impl<I, J, K, T> Iterator for Choice3<I, J, K> - where I: Iterator<Item=T>, J: Iterator<Item=T>, K: Iterator<Item=T> +where + I: Iterator<Item = T>, + J: Iterator<Item = T>, + K: Iterator<Item = T>, { type Item = T; @@ -440,4 +536,12 @@ impl<I, J, K, T> Iterator for Choice3<I, J, K> Choice3::Third(ref mut k) => k.next(), } } + + fn size_hint(&self) -> (usize, Option<usize>) { + match *self { + Choice3::First(ref i) => i.size_hint(), + Choice3::Second(ref j) => j.size_hint(), + Choice3::Third(ref k) => k.size_hint(), + } + } } diff --git a/components/script/dom/htmlsourceelement.rs b/components/script/dom/htmlsourceelement.rs index 2b15fabd789..5b581e466b5 100644 --- a/components/script/dom/htmlsourceelement.rs +++ b/components/script/dom/htmlsourceelement.rs @@ -1,37 +1,132 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HTMLSourceElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLSourceElementBinding::HTMLSourceElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::root::{Dom, Root}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::AttributeMutation; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlimageelement::HTMLImageElement; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::node::{BindContext, Node, UnbindContext}; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLSourceElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLSourceElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLSourceElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLSourceElement { HTMLSourceElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLSourceElement> { - Node::reflect_node(box HTMLSourceElement::new_inherited(local_name, prefix, document), - document, - HTMLSourceElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLSourceElement> { + Node::reflect_node( + Box::new(HTMLSourceElement::new_inherited( + local_name, prefix, document, + )), + document, + ) + } + + fn iterate_next_html_image_element_siblings( + next_siblings_iterator: impl Iterator<Item = Root<Dom<Node>>>, + ) { + for next_sibling in next_siblings_iterator { + if let Some(html_image_element_sibling) = next_sibling.downcast::<HTMLImageElement>() { + html_image_element_sibling.update_the_image_data(); + } + } + } +} + +impl VirtualMethods for HTMLSourceElement { + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } + + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { + self.super_type().unwrap().attribute_mutated(attr, mutation); + match attr.local_name() { + &local_name!("srcset") | + &local_name!("sizes") | + &local_name!("media") | + &local_name!("type") => { + let next_sibling_iterator = self.upcast::<Node>().following_siblings(); + HTMLSourceElement::iterate_next_html_image_element_siblings(next_sibling_iterator); + }, + _ => {}, + } + } + + /// <https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted> + fn bind_to_tree(&self, context: &BindContext) { + self.super_type().unwrap().bind_to_tree(context); + let parent = self.upcast::<Node>().GetParentNode().unwrap(); + if let Some(media) = parent.downcast::<HTMLMediaElement>() { + media.handle_source_child_insertion(); + } + let next_sibling_iterator = self.upcast::<Node>().following_siblings(); + HTMLSourceElement::iterate_next_html_image_element_siblings(next_sibling_iterator); + } + + fn unbind_from_tree(&self, context: &UnbindContext) { + self.super_type().unwrap().unbind_from_tree(context); + if let Some(next_sibling) = context.next_sibling { + let next_sibling_iterator = next_sibling.inclusively_following_siblings(); + HTMLSourceElement::iterate_next_html_image_element_siblings(next_sibling_iterator); + } + } +} + +impl HTMLSourceElementMethods for HTMLSourceElement { + // https://html.spec.whatwg.org/multipage/#dom-source-src + make_getter!(Src, "src"); + + // https://html.spec.whatwg.org/multipage/#dom-source-src + make_setter!(SetSrc, "src"); + + // https://html.spec.whatwg.org/multipage/#dom-source-type + make_getter!(Type, "type"); + + // https://html.spec.whatwg.org/multipage/#dom-source-type + make_setter!(SetType, "type"); + + // https://html.spec.whatwg.org/multipage/#dom-source-srcset + make_getter!(Srcset, "srcset"); + + // https://html.spec.whatwg.org/multipage/#dom-source-srcset + make_setter!(SetSrcset, "srcset"); + + // https://html.spec.whatwg.org/multipage/#dom-source-sizes + make_getter!(Sizes, "sizes"); + + // https://html.spec.whatwg.org/multipage/#dom-source-sizes + make_setter!(SetSizes, "sizes"); + + // https://html.spec.whatwg.org/multipage/#dom-source-media + make_getter!(Media, "media"); + + // https://html.spec.whatwg.org/multipage/#dom-source-media + make_setter!(SetMedia, "media"); } diff --git a/components/script/dom/htmlspanelement.rs b/components/script/dom/htmlspanelement.rs index 1437d1dbb08..e8a374f5f9b 100644 --- a/components/script/dom/htmlspanelement.rs +++ b/components/script/dom/htmlspanelement.rs @@ -1,34 +1,39 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLSpanElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLSpanElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLSpanElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLSpanElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLSpanElement { HTMLSpanElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLSpanElement> { - Node::reflect_node(box HTMLSpanElement::new_inherited(local_name, prefix, document), - document, - HTMLSpanElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLSpanElement> { + Node::reflect_node( + Box::new(HTMLSpanElement::new_inherited(local_name, prefix, document)), + document, + ) } } diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index 0c026fcfe9a..83d35fc78c8 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -1,41 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use cssparser::Parser as CssParser; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::HTMLStyleElementBinding; -use dom::bindings::codegen::Bindings::HTMLStyleElementBinding::HTMLStyleElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::cssstylesheet::CSSStyleSheet; -use dom::document::Document; -use dom::element::{Element, ElementCreator}; -use dom::eventtarget::EventTarget; -use dom::htmlelement::HTMLElement; -use dom::node::{ChildrenMutation, Node, UnbindContext, document_from_node, window_from_node}; -use dom::stylesheet::StyleSheet as DOMStyleSheet; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLStyleElementBinding::HTMLStyleElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::document::Document; +use crate::dom::element::{Element, ElementCreator}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{ + document_from_node, stylesheets_owner_from_node, window_from_node, BindContext, + ChildrenMutation, Node, UnbindContext, +}; +use crate::dom::stylesheet::StyleSheet as DOMStyleSheet; +use crate::dom::virtualmethods::VirtualMethods; +use crate::stylesheet_loader::{StylesheetLoader, StylesheetOwner}; +use cssparser::{Parser as CssParser, ParserInput}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use net_traits::ReferrerPolicy; -use script_layout_interface::message::Msg; +use servo_arc::Arc; use std::cell::Cell; -use std::sync::Arc; -use style::media_queries::parse_media_query_list; -use style::parser::{LengthParsingMode, ParserContext as CssParserContext}; -use style::stylesheets::{CssRuleType, Stylesheet, Origin}; -use stylesheet_loader::{StylesheetLoader, StylesheetOwner}; +use style::media_queries::MediaList; +use style::parser::ParserContext as CssParserContext; +use style::stylesheets::{AllowImportRules, CssRuleType, Origin, Stylesheet}; +use style_traits::ParsingMode; #[dom_struct] pub struct HTMLStyleElement { htmlelement: HTMLElement, - #[ignore_heap_size_of = "Arc"] - stylesheet: DOMRefCell<Option<Arc<Stylesheet>>>, - cssom_stylesheet: MutNullableJS<CSSStyleSheet>, - /// https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts + #[ignore_malloc_size_of = "Arc"] + stylesheet: DomRefCell<Option<Arc<Stylesheet>>>, + cssom_stylesheet: MutNullableDom<CSSStyleSheet>, + /// <https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts> parser_inserted: Cell<bool>, in_stack_of_open_elements: Cell<bool>, pending_loads: Cell<u32>, @@ -44,14 +44,16 @@ pub struct HTMLStyleElement { } impl HTMLStyleElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document, - creator: ElementCreator) -> HTMLStyleElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + creator: ElementCreator, + ) -> HTMLStyleElement { HTMLStyleElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), - stylesheet: DOMRefCell::new(None), - cssom_stylesheet: MutNullableJS::new(None), + stylesheet: DomRefCell::new(None), + cssom_stylesheet: MutNullableDom::new(None), parser_inserted: Cell::new(creator.is_parser_created()), in_stack_of_open_elements: Cell::new(creator.is_parser_created()), pending_loads: Cell::new(0), @@ -61,21 +63,27 @@ impl HTMLStyleElement { } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document, - creator: ElementCreator) -> Root<HTMLStyleElement> { - Node::reflect_node(box HTMLStyleElement::new_inherited(local_name, prefix, document, creator), - document, - HTMLStyleElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + creator: ElementCreator, + ) -> DomRoot<HTMLStyleElement> { + Node::reflect_node( + Box::new(HTMLStyleElement::new_inherited( + local_name, prefix, document, creator, + )), + document, + ) } pub fn parse_own_css(&self) { let node = self.upcast::<Node>(); let element = self.upcast::<Element>(); - assert!(node.is_in_doc()); + assert!(node.is_connected()); - let win = window_from_node(node); + let window = window_from_node(node); + let doc = document_from_node(self); let mq_attribute = element.get_attribute(&ns!(), &local_name!("media")); let mq_str = match mq_attribute { @@ -83,55 +91,94 @@ impl HTMLStyleElement { None => String::new(), }; - let data = node.GetTextContent().expect("Element.textContent must be a string"); - let url = win.get_url(); - let context = CssParserContext::new_for_cssom(&url, - win.css_error_reporter(), - Some(CssRuleType::Media), - LengthParsingMode::Default); + let data = node + .GetTextContent() + .expect("Element.textContent must be a string"); + let url = window.get_url(); + let css_error_reporter = window.css_error_reporter(); + let context = CssParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Media), + ParsingMode::DEFAULT, + doc.quirks_mode(), + css_error_reporter, + None, + ); let shared_lock = node.owner_doc().style_shared_lock().clone(); - let mq = Arc::new(shared_lock.wrap( - parse_media_query_list(&context, &mut CssParser::new(&mq_str)))); + let mut input = ParserInput::new(&mq_str); + let mq = + Arc::new(shared_lock.wrap(MediaList::parse(&context, &mut CssParser::new(&mut input)))); let loader = StylesheetLoader::for_element(self.upcast()); - let sheet = Stylesheet::from_str(&data, win.get_url(), Origin::Author, mq, - shared_lock, Some(&loader), - win.css_error_reporter(), - self.line_number); + let sheet = Stylesheet::from_str( + &data, + window.get_url(), + Origin::Author, + mq, + shared_lock, + Some(&loader), + css_error_reporter, + doc.quirks_mode(), + self.line_number as u32, + AllowImportRules::Yes, + ); let sheet = Arc::new(sheet); - // No subresource loads were triggered, just fire the load event now. + // No subresource loads were triggered, queue load event if self.pending_loads.get() == 0 { - self.upcast::<EventTarget>().fire_event(atom!("load")); + let window = window_from_node(self); + window + .task_manager() + .dom_manipulation_task_source() + .queue_simple_event(self.upcast(), atom!("load"), &window); } - win.layout_chan().send(Msg::AddStylesheet(sheet.clone())).unwrap(); - *self.stylesheet.borrow_mut() = Some(sheet); - let doc = document_from_node(self); - doc.invalidate_stylesheets(); + self.set_stylesheet(sheet); + } + + // FIXME(emilio): This is duplicated with HTMLLinkElement::set_stylesheet. + #[allow(unrooted_must_root)] + pub fn set_stylesheet(&self, s: Arc<Stylesheet>) { + let stylesheets_owner = stylesheets_owner_from_node(self); + if let Some(ref s) = *self.stylesheet.borrow() { + stylesheets_owner.remove_stylesheet(self.upcast(), s) + } + *self.stylesheet.borrow_mut() = Some(s.clone()); + self.clean_stylesheet_ownership(); + stylesheets_owner.add_stylesheet(self.upcast(), s); } pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> { self.stylesheet.borrow().clone() } - pub fn get_cssom_stylesheet(&self) -> Option<Root<CSSStyleSheet>> { + pub fn get_cssom_stylesheet(&self) -> Option<DomRoot<CSSStyleSheet>> { self.get_stylesheet().map(|sheet| { self.cssom_stylesheet.or_init(|| { - CSSStyleSheet::new(&window_from_node(self), - self.upcast::<Element>(), - "text/css".into(), - None, // todo handle location - None, // todo handle title - sheet) + CSSStyleSheet::new( + &window_from_node(self), + self.upcast::<Element>(), + "text/css".into(), + None, // todo handle location + None, // todo handle title + sheet, + ) }) }) } + + fn clean_stylesheet_ownership(&self) { + if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() { + cssom_stylesheet.set_owner(None); + } + self.cssom_stylesheet.set(None); + } } impl VirtualMethods for HTMLStyleElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn children_changed(&self, mutation: &ChildrenMutation) { @@ -147,14 +194,14 @@ impl VirtualMethods for HTMLStyleElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { - self.super_type().unwrap().bind_to_tree(tree_in_doc); + fn bind_to_tree(&self, context: &BindContext) { + self.super_type().unwrap().bind_to_tree(context); // https://html.spec.whatwg.org/multipage/#update-a-style-block // Handles the case when: // "The element is not on the stack of open elements of an HTML parser or XML parser, // and it becomes connected or disconnected." - if tree_in_doc && !self.in_stack_of_open_elements.get() { + if context.tree_connected && !self.in_stack_of_open_elements.get() { self.parse_own_css(); } } @@ -176,8 +223,12 @@ impl VirtualMethods for HTMLStyleElement { s.unbind_from_tree(context); } - let doc = document_from_node(self); - doc.invalidate_stylesheets(); + if context.tree_connected { + if let Some(s) = self.stylesheet.borrow_mut().take() { + self.clean_stylesheet_ownership(); + stylesheets_owner_from_node(self).remove_stylesheet(self.upcast(), &s) + } + } } } @@ -217,10 +268,9 @@ impl StylesheetOwner for HTMLStyleElement { } } - impl HTMLStyleElementMethods for HTMLStyleElement { // https://drafts.csswg.org/cssom/#dom-linkstyle-sheet - fn GetSheet(&self) -> Option<Root<DOMStyleSheet>> { - self.get_cssom_stylesheet().map(Root::upcast) + fn GetSheet(&self) -> Option<DomRoot<DOMStyleSheet>> { + self.get_cssom_stylesheet().map(DomRoot::upcast) } } diff --git a/components/script/dom/htmltablecaptionelement.rs b/components/script/dom/htmltablecaptionelement.rs index fd6c06643dd..b89b647691e 100644 --- a/components/script/dom/htmltablecaptionelement.rs +++ b/components/script/dom/htmltablecaptionelement.rs @@ -1,37 +1,45 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLTableCaptionElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLTableCaptionElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLTableCaptionElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLTableCaptionElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTableCaptionElement { HTMLTableCaptionElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLTableCaptionElement> { - Node::reflect_node(box HTMLTableCaptionElement::new_inherited(local_name, prefix, document), - document, - HTMLTableCaptionElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTableCaptionElement> { + let n = Node::reflect_node( + Box::new(HTMLTableCaptionElement::new_inherited( + local_name, prefix, document, + )), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } } diff --git a/components/script/dom/htmltablecellelement.rs b/components/script/dom/htmltablecellelement.rs index ef4bffba5d7..75a2140a090 100644 --- a/components/script/dom/htmltablecellelement.rs +++ b/components/script/dom/htmltablecellelement.rs @@ -1,22 +1,24 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::root::LayoutDom; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{Element, LayoutElementHelpers}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmltablerowelement::HTMLTableRowElement; +use crate::dom::node::Node; +use crate::dom::virtualmethods::VirtualMethods; use cssparser::RGBA; -use dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::LayoutJS; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{Element, RawLayoutElementHelpers}; -use dom::htmlelement::HTMLElement; -use dom::htmltablerowelement::HTMLTableRowElement; -use dom::node::Node; -use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; +use style::context::QuirksMode; const DEFAULT_COLSPAN: u32 = 1; const DEFAULT_ROWSPAN: u32 = 1; @@ -27,14 +29,32 @@ pub struct HTMLTableCellElement { } impl HTMLTableCellElement { - pub fn new_inherited(tag_name: LocalName, - prefix: Option<DOMString>, - document: &Document) - -> HTMLTableCellElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTableCellElement { HTMLTableCellElement { - htmlelement: HTMLElement::new_inherited(tag_name, prefix, document), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } + + #[allow(unrooted_must_root)] + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTableCellElement> { + let n = Node::reflect_node( + Box::new(HTMLTableCellElement::new_inherited( + local_name, prefix, document, + )), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n + } } impl HTMLTableCellElementMethods for HTMLTableCellElement { @@ -73,70 +93,87 @@ impl HTMLTableCellElementMethods for HTMLTableCellElement { _ => return -1, }; - parent_children.filter(|c| c.is::<HTMLTableCellElement>()) - .position(|c| &*c == self_node) - .map_or(-1, |p| p as i32) + parent_children + .filter(|c| c.is::<HTMLTableCellElement>()) + .position(|c| &*c == self_node) + .map_or(-1, |p| p as i32) } } - pub trait HTMLTableCellElementLayoutHelpers { - fn get_background_color(&self) -> Option<RGBA>; - fn get_colspan(&self) -> Option<u32>; - fn get_rowspan(&self) -> Option<u32>; - fn get_width(&self) -> LengthOrPercentageOrAuto; + fn get_background_color(self) -> Option<RGBA>; + fn get_colspan(self) -> Option<u32>; + fn get_rowspan(self) -> Option<u32>; + fn get_width(self) -> LengthOrPercentageOrAuto; } -#[allow(unsafe_code)] -impl HTMLTableCellElementLayoutHelpers for LayoutJS<HTMLTableCellElement> { - fn get_background_color(&self) -> Option<RGBA> { - unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) - .and_then(AttrValue::as_color) - .cloned() - } +impl HTMLTableCellElementLayoutHelpers for LayoutDom<'_, HTMLTableCellElement> { + fn get_background_color(self) -> Option<RGBA> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) + .and_then(AttrValue::as_color) + .cloned() } - fn get_colspan(&self) -> Option<u32> { - unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("colspan")) - .map(AttrValue::as_uint) - } + fn get_colspan(self) -> Option<u32> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("colspan")) + .map(AttrValue::as_uint) } - fn get_rowspan(&self) -> Option<u32> { - unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("rowspan")) - .map(AttrValue::as_uint) - } + fn get_rowspan(self) -> Option<u32> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("rowspan")) + .map(AttrValue::as_uint) } - fn get_width(&self) -> LengthOrPercentageOrAuto { - unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("width")) - .map(AttrValue::as_dimension) - .cloned() - .unwrap_or(LengthOrPercentageOrAuto::Auto) - } + fn get_width(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("width")) + .map(AttrValue::as_dimension) + .cloned() + .unwrap_or(LengthOrPercentageOrAuto::Auto) } } impl VirtualMethods for HTMLTableCellElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { - local_name!("colspan") => AttrValue::from_u32(value.into(), DEFAULT_COLSPAN), - local_name!("rowspan") => AttrValue::from_u32(value.into(), DEFAULT_ROWSPAN), + local_name!("colspan") => { + let mut attr = AttrValue::from_u32(value.into(), DEFAULT_COLSPAN); + if let AttrValue::UInt(ref mut s, ref mut val) = attr { + if *val == 0 { + *val = 1; + *s = "1".into(); + } + } + attr + }, + local_name!("rowspan") => { + let mut attr = AttrValue::from_u32(value.into(), DEFAULT_ROWSPAN); + if let AttrValue::UInt(ref mut s, ref mut val) = attr { + if *val == 0 { + let node = self.upcast::<Node>(); + let doc = node.owner_doc(); + // rowspan = 0 is not supported in quirks mode + if doc.quirks_mode() != QuirksMode::NoQuirks { + *val = 1; + *s = "1".into(); + } + } + } + attr + }, local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()), local_name!("width") => AttrValue::from_nonzero_dimension(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(local_name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(local_name, value), } } } diff --git a/components/script/dom/htmltablecolelement.rs b/components/script/dom/htmltablecolelement.rs index 00f618ed65f..86382e1c2cb 100644 --- a/components/script/dom/htmltablecolelement.rs +++ b/components/script/dom/htmltablecolelement.rs @@ -1,15 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLTableColElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLTableColElement { @@ -17,21 +16,30 @@ pub struct HTMLTableColElement { } impl HTMLTableColElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLTableColElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTableColElement { HTMLTableColElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLTableColElement> { - Node::reflect_node(box HTMLTableColElement::new_inherited(local_name, prefix, document), - document, - HTMLTableColElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTableColElement> { + let n = Node::reflect_node( + Box::new(HTMLTableColElement::new_inherited( + local_name, prefix, document, + )), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } } diff --git a/components/script/dom/htmltabledatacellelement.rs b/components/script/dom/htmltabledatacellelement.rs deleted file mode 100644 index 77808c77d79..00000000000 --- a/components/script/dom/htmltabledatacellelement.rs +++ /dev/null @@ -1,38 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HTMLTableDataCellElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmltablecellelement::HTMLTableCellElement; -use dom::node::Node; -use dom_struct::dom_struct; -use html5ever_atoms::LocalName; - -#[dom_struct] -pub struct HTMLTableDataCellElement { - htmltablecellelement: HTMLTableCellElement, -} - -impl HTMLTableDataCellElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLTableDataCellElement { - HTMLTableDataCellElement { - htmltablecellelement: - HTMLTableCellElement::new_inherited(local_name, prefix, document) - } - } - - #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, prefix: Option<DOMString>, document: &Document) - -> Root<HTMLTableDataCellElement> { - Node::reflect_node(box HTMLTableDataCellElement::new_inherited(local_name, - prefix, - document), - document, - HTMLTableDataCellElementBinding::Wrap) - } -} diff --git a/components/script/dom/htmltableelement.rs b/components/script/dom/htmltableelement.rs index 56e9876b753..2fa44f3ad37 100644 --- a/components/script/dom/htmltableelement.rs +++ b/components/script/dom/htmltableelement.rs @@ -1,57 +1,61 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; +use crate::dom::bindings::codegen::Bindings::HTMLTableElementBinding::HTMLTableElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers}; +use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmltablecaptionelement::HTMLTableCaptionElement; +use crate::dom::htmltablecolelement::HTMLTableColElement; +use crate::dom::htmltablerowelement::HTMLTableRowElement; +use crate::dom::htmltablesectionelement::HTMLTableSectionElement; +use crate::dom::node::{document_from_node, window_from_node, Node}; +use crate::dom::virtualmethods::VirtualMethods; use cssparser::RGBA; -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; -use dom::bindings::codegen::Bindings::HTMLTableElementBinding; -use dom::bindings::codegen::Bindings::HTMLTableElementBinding::HTMLTableElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, LayoutJS, MutNullableJS, Root, RootedReference}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; -use dom::htmlcollection::{CollectionFilter, HTMLCollection}; -use dom::htmlelement::HTMLElement; -use dom::htmltablecaptionelement::HTMLTableCaptionElement; -use dom::htmltablecolelement::HTMLTableColElement; -use dom::htmltablerowelement::HTMLTableRowElement; -use dom::htmltablesectionelement::HTMLTableSectionElement; -use dom::node::{Node, document_from_node, window_from_node}; -use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use std::cell::Cell; -use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_unsigned_integer}; +use style::attr::{parse_unsigned_integer, AttrValue, LengthOrPercentageOrAuto}; #[dom_struct] pub struct HTMLTableElement { htmlelement: HTMLElement, border: Cell<Option<u32>>, cellspacing: Cell<Option<u32>>, - tbodies: MutNullableJS<HTMLCollection>, + tbodies: MutNullableDom<HTMLCollection>, } #[allow(unrooted_must_root)] -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] struct TableRowFilter { - sections: Vec<JS<Node>>, + sections: Vec<Dom<Node>>, } impl CollectionFilter for TableRowFilter { fn filter(&self, elem: &Element, root: &Node) -> bool { elem.is::<HTMLTableRowElement>() && - (root.is_parent_of(elem.upcast()) - || self.sections.iter().any(|ref section| section.is_parent_of(elem.upcast()))) + (root.is_parent_of(elem.upcast()) || + self.sections + .iter() + .any(|ref section| section.is_parent_of(elem.upcast()))) } } impl HTMLTableElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) - -> HTMLTableElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTableElement { HTMLTableElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), border: Cell::new(None), @@ -61,11 +65,20 @@ impl HTMLTableElement { } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, prefix: Option<DOMString>, document: &Document) - -> Root<HTMLTableElement> { - Node::reflect_node(box HTMLTableElement::new_inherited(local_name, prefix, document), - document, - HTMLTableElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTableElement> { + let n = Node::reflect_node( + Box::new(HTMLTableElement::new_inherited( + local_name, prefix, document, + )), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } pub fn get_border(&self) -> Option<u32> { @@ -74,24 +87,30 @@ impl HTMLTableElement { // https://html.spec.whatwg.org/multipage/#dom-table-thead // https://html.spec.whatwg.org/multipage/#dom-table-tfoot - fn get_first_section_of_type(&self, atom: &LocalName) -> Option<Root<HTMLTableSectionElement>> { + fn get_first_section_of_type( + &self, + atom: &LocalName, + ) -> Option<DomRoot<HTMLTableSectionElement>> { self.upcast::<Node>() .child_elements() .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == atom) - .and_then(|n| n.downcast().map(Root::from_ref)) + .and_then(|n| n.downcast().map(DomRoot::from_ref)) } // https://html.spec.whatwg.org/multipage/#dom-table-thead // https://html.spec.whatwg.org/multipage/#dom-table-tfoot - fn set_first_section_of_type<P>(&self, - atom: &LocalName, - section: Option<&HTMLTableSectionElement>, - reference_predicate: P) - -> ErrorResult - where P: FnMut(&Root<Element>) -> bool { + fn set_first_section_of_type<P>( + &self, + atom: &LocalName, + section: Option<&HTMLTableSectionElement>, + reference_predicate: P, + ) -> ErrorResult + where + P: FnMut(&DomRoot<Element>) -> bool, + { if let Some(e) = section { if e.upcast::<Element>().local_name() != atom { - return Err(Error::HierarchyRequest) + return Err(Error::HierarchyRequest); } } @@ -101,9 +120,9 @@ impl HTMLTableElement { if let Some(section) = section { let reference_element = node.child_elements().find(reference_predicate); - let reference_node = reference_element.r().map(|e| e.upcast()); + let reference_node = reference_element.as_ref().map(|e| e.upcast()); - try!(node.InsertBefore(section.upcast(), reference_node)); + node.InsertBefore(section.upcast(), reference_node)?; } Ok(()) @@ -111,19 +130,18 @@ impl HTMLTableElement { // https://html.spec.whatwg.org/multipage/#dom-table-createthead // https://html.spec.whatwg.org/multipage/#dom-table-createtfoot - fn create_section_of_type(&self, atom: &LocalName) -> Root<HTMLTableSectionElement> { + fn create_section_of_type(&self, atom: &LocalName) -> DomRoot<HTMLTableSectionElement> { if let Some(section) = self.get_first_section_of_type(atom) { - return section + return section; } - let section = HTMLTableSectionElement::new(atom.clone(), - None, - &document_from_node(self)); + let section = HTMLTableSectionElement::new(atom.clone(), None, &document_from_node(self)); match atom { &local_name!("thead") => self.SetTHead(Some(§ion)), &local_name!("tfoot") => self.SetTFoot(Some(§ion)), - _ => unreachable!("unexpected section type") - }.expect("unexpected section type"); + _ => unreachable!("unexpected section type"), + } + .expect("unexpected section type"); section } @@ -138,25 +156,31 @@ impl HTMLTableElement { fn get_rows(&self) -> TableRowFilter { TableRowFilter { - sections: self.upcast::<Node>() - .children() - .filter_map(|ref node| - node.downcast::<HTMLTableSectionElement>().map(|_| JS::from_ref(&**node))) - .collect() + sections: self + .upcast::<Node>() + .children() + .filter_map(|ref node| { + node.downcast::<HTMLTableSectionElement>() + .map(|_| Dom::from_ref(&**node)) + }) + .collect(), } } } impl HTMLTableElementMethods for HTMLTableElement { // https://html.spec.whatwg.org/multipage/#dom-table-rows - fn Rows(&self) -> Root<HTMLCollection> { + fn Rows(&self) -> DomRoot<HTMLCollection> { let filter = self.get_rows(); - HTMLCollection::new(&window_from_node(self), self.upcast(), box filter) + HTMLCollection::new(&window_from_node(self), self.upcast(), Box::new(filter)) } // https://html.spec.whatwg.org/multipage/#dom-table-caption - fn GetCaption(&self) -> Option<Root<HTMLTableCaptionElement>> { - self.upcast::<Node>().children().filter_map(Root::downcast).next() + fn GetCaption(&self) -> Option<DomRoot<HTMLTableCaptionElement>> { + self.upcast::<Node>() + .children() + .filter_map(DomRoot::downcast) + .next() } // https://html.spec.whatwg.org/multipage/#dom-table-caption @@ -167,22 +191,24 @@ impl HTMLTableElementMethods for HTMLTableElement { if let Some(caption) = new_caption { let node = self.upcast::<Node>(); - node.InsertBefore(caption.upcast(), node.GetFirstChild().r()) + node.InsertBefore(caption.upcast(), node.GetFirstChild().as_deref()) .expect("Insertion failed"); } } // https://html.spec.whatwg.org/multipage/#dom-table-createcaption - fn CreateCaption(&self) -> Root<HTMLTableCaptionElement> { + fn CreateCaption(&self) -> DomRoot<HTMLTableCaptionElement> { match self.GetCaption() { Some(caption) => caption, None => { - let caption = HTMLTableCaptionElement::new(local_name!("caption"), - None, - &document_from_node(self)); + let caption = HTMLTableCaptionElement::new( + local_name!("caption"), + None, + &document_from_node(self), + ); self.SetCaption(Some(&caption)); caption - } + }, } } @@ -193,9 +219,8 @@ impl HTMLTableElementMethods for HTMLTableElement { } } - // https://html.spec.whatwg.org/multipage/#dom-table-thead - fn GetTHead(&self) -> Option<Root<HTMLTableSectionElement>> { + fn GetTHead(&self) -> Option<DomRoot<HTMLTableSectionElement>> { self.get_first_section_of_type(&local_name!("thead")) } @@ -207,7 +232,7 @@ impl HTMLTableElementMethods for HTMLTableElement { } // https://html.spec.whatwg.org/multipage/#dom-table-createthead - fn CreateTHead(&self) -> Root<HTMLTableSectionElement> { + fn CreateTHead(&self) -> DomRoot<HTMLTableSectionElement> { self.create_section_of_type(&local_name!("thead")) } @@ -217,7 +242,7 @@ impl HTMLTableElementMethods for HTMLTableElement { } // https://html.spec.whatwg.org/multipage/#dom-table-tfoot - fn GetTFoot(&self) -> Option<Root<HTMLTableSectionElement>> { + fn GetTFoot(&self) -> Option<DomRoot<HTMLTableSectionElement>> { self.get_first_section_of_type(&local_name!("tfoot")) } @@ -233,7 +258,6 @@ impl HTMLTableElementMethods for HTMLTableElement { if name == &local_name!("thead") || name == &local_name!("tbody") { return false; } - } true @@ -241,7 +265,7 @@ impl HTMLTableElementMethods for HTMLTableElement { } // https://html.spec.whatwg.org/multipage/#dom-table-createtfoot - fn CreateTFoot(&self) -> Root<HTMLTableSectionElement> { + fn CreateTFoot(&self) -> DomRoot<HTMLTableSectionElement> { self.create_section_of_type(&local_name!("tfoot")) } @@ -251,45 +275,42 @@ impl HTMLTableElementMethods for HTMLTableElement { } // https://html.spec.whatwg.org/multipage/#dom-table-tbodies - fn TBodies(&self) -> Root<HTMLCollection> { + fn TBodies(&self) -> DomRoot<HTMLCollection> { #[derive(JSTraceable)] struct TBodiesFilter; impl CollectionFilter for TBodiesFilter { fn filter(&self, elem: &Element, root: &Node) -> bool { elem.is::<HTMLTableSectionElement>() && elem.local_name() == &local_name!("tbody") && - elem.upcast::<Node>().GetParentNode().r() == Some(root) + elem.upcast::<Node>().GetParentNode().as_deref() == Some(root) } } self.tbodies.or_init(|| { let window = window_from_node(self); - let filter = box TBodiesFilter; + let filter = Box::new(TBodiesFilter); HTMLCollection::create(&window, self.upcast(), filter) }) } - // https://html.spec.whatwg.org/multipage/#dom-table-createtbody - fn CreateTBody(&self) -> Root<HTMLTableSectionElement> { - let tbody = HTMLTableSectionElement::new(local_name!("tbody"), - None, - &document_from_node(self)); + fn CreateTBody(&self) -> DomRoot<HTMLTableSectionElement> { + let tbody = + HTMLTableSectionElement::new(local_name!("tbody"), None, &document_from_node(self)); let node = self.upcast::<Node>(); - let last_tbody = - node.rev_children() - .filter_map(Root::downcast::<Element>) - .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")); - let reference_element = - last_tbody.and_then(|t| t.upcast::<Node>().GetNextSibling()); - - node.InsertBefore(tbody.upcast(), reference_element.r()) + let last_tbody = node + .rev_children() + .filter_map(DomRoot::downcast::<Element>) + .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")); + let reference_element = last_tbody.and_then(|t| t.upcast::<Node>().GetNextSibling()); + + node.InsertBefore(tbody.upcast(), reference_element.as_deref()) .expect("Insertion failed"); tbody } // https://html.spec.whatwg.org/multipage/#dom-table-insertrow - fn InsertRow(&self, index: i32) -> Fallible<Root<HTMLTableRowElement>> { + fn InsertRow(&self, index: i32) -> Fallible<DomRoot<HTMLTableRowElement>> { let rows = self.Rows(); let number_of_row_elements = rows.Length(); @@ -297,47 +318,62 @@ impl HTMLTableElementMethods for HTMLTableElement { return Err(Error::IndexSize); } - let new_row = HTMLTableRowElement::new(local_name!("tr"), - None, - &document_from_node(self)); + let new_row = HTMLTableRowElement::new(local_name!("tr"), None, &document_from_node(self)); let node = self.upcast::<Node>(); if number_of_row_elements == 0 { // append new row to last or new tbody in table - if let Some(last_tbody) = node.rev_children() - .filter_map(Root::downcast::<Element>) - .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")) { - last_tbody.upcast::<Node>().AppendChild(new_row.upcast::<Node>()) - .expect("InsertRow failed to append first row."); - } else { - let tbody = self.CreateTBody(); - node.AppendChild(tbody.upcast()) - .expect("InsertRow failed to append new tbody."); - - tbody.upcast::<Node>().AppendChild(new_row.upcast::<Node>()) - .expect("InsertRow failed to append first row."); - } + if let Some(last_tbody) = node + .rev_children() + .filter_map(DomRoot::downcast::<Element>) + .find(|n| { + n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody") + }) + { + last_tbody + .upcast::<Node>() + .AppendChild(new_row.upcast::<Node>()) + .expect("InsertRow failed to append first row."); + } else { + let tbody = self.CreateTBody(); + node.AppendChild(tbody.upcast()) + .expect("InsertRow failed to append new tbody."); + + tbody + .upcast::<Node>() + .AppendChild(new_row.upcast::<Node>()) + .expect("InsertRow failed to append first row."); + } } else if index == number_of_row_elements as i32 || index == -1 { // append new row to parent of last row in table - let last_row = rows.Item(number_of_row_elements - 1) - .expect("InsertRow failed to find last row in table."); - - let last_row_parent = - last_row.upcast::<Node>().GetParentNode() - .expect("InsertRow failed to find parent of last row in table."); - - last_row_parent.upcast::<Node>().AppendChild(new_row.upcast::<Node>()) - .expect("InsertRow failed to append last row."); + let last_row = rows + .Item(number_of_row_elements - 1) + .expect("InsertRow failed to find last row in table."); + + let last_row_parent = last_row + .upcast::<Node>() + .GetParentNode() + .expect("InsertRow failed to find parent of last row in table."); + + last_row_parent + .upcast::<Node>() + .AppendChild(new_row.upcast::<Node>()) + .expect("InsertRow failed to append last row."); } else { // insert new row before the index-th row in rows using the same parent - let ith_row = rows.Item(index as u32) - .expect("InsertRow failed to find a row in table."); - - let ith_row_parent = ith_row.upcast::<Node>().GetParentNode() - .expect("InsertRow failed to find parent of a row in table."); - - ith_row_parent.upcast::<Node>().InsertBefore(new_row.upcast::<Node>(), Some(ith_row.upcast::<Node>())) - .expect("InsertRow failed to append row"); + let ith_row = rows + .Item(index as u32) + .expect("InsertRow failed to find a row in table."); + + let ith_row_parent = ith_row + .upcast::<Node>() + .GetParentNode() + .expect("InsertRow failed to find parent of a row in table."); + + ith_row_parent + .upcast::<Node>() + .InsertBefore(new_row.upcast::<Node>(), Some(ith_row.upcast::<Node>())) + .expect("InsertRow failed to append row"); } Ok(new_row) @@ -355,7 +391,7 @@ impl HTMLTableElementMethods for HTMLTableElement { return Err(Error::IndexSize); } // Step 3. - Root::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(); + DomRoot::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(); Ok(()) } @@ -373,52 +409,42 @@ impl HTMLTableElementMethods for HTMLTableElement { } pub trait HTMLTableElementLayoutHelpers { - fn get_background_color(&self) -> Option<RGBA>; - fn get_border(&self) -> Option<u32>; - fn get_cellspacing(&self) -> Option<u32>; - fn get_width(&self) -> LengthOrPercentageOrAuto; + fn get_background_color(self) -> Option<RGBA>; + fn get_border(self) -> Option<u32>; + fn get_cellspacing(self) -> Option<u32>; + fn get_width(self) -> LengthOrPercentageOrAuto; } -impl HTMLTableElementLayoutHelpers for LayoutJS<HTMLTableElement> { - #[allow(unsafe_code)] - fn get_background_color(&self) -> Option<RGBA> { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) - .and_then(AttrValue::as_color) - .cloned() - } +impl HTMLTableElementLayoutHelpers for LayoutDom<'_, HTMLTableElement> { + fn get_background_color(self) -> Option<RGBA> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) + .and_then(AttrValue::as_color) + .cloned() } #[allow(unsafe_code)] - fn get_border(&self) -> Option<u32> { - unsafe { - (*self.unsafe_get()).border.get() - } + fn get_border(self) -> Option<u32> { + unsafe { (*self.unsafe_get()).border.get() } } #[allow(unsafe_code)] - fn get_cellspacing(&self) -> Option<u32> { - unsafe { - (*self.unsafe_get()).cellspacing.get() - } + fn get_cellspacing(self) -> Option<u32> { + unsafe { (*self.unsafe_get()).cellspacing.get() } } - #[allow(unsafe_code)] - fn get_width(&self) -> LengthOrPercentageOrAuto { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("width")) - .map(AttrValue::as_dimension) - .cloned() - .unwrap_or(LengthOrPercentageOrAuto::Auto) - } + fn get_width(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("width")) + .map(AttrValue::as_dimension) + .cloned() + .unwrap_or(LengthOrPercentageOrAuto::Auto) } } impl VirtualMethods for HTMLTableElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -426,14 +452,18 @@ impl VirtualMethods for HTMLTableElement { match *attr.local_name() { local_name!("border") => { // According to HTML5 § 14.3.9, invalid values map to 1px. - self.border.set(mutation.new_value(attr).map(|value| { - parse_unsigned_integer(value.chars()).unwrap_or(1) - })); - } + self.border.set( + mutation + .new_value(attr) + .map(|value| parse_unsigned_integer(value.chars()).unwrap_or(1)), + ); + }, local_name!("cellspacing") => { - self.cellspacing.set(mutation.new_value(attr).and_then(|value| { - parse_unsigned_integer(value.chars()).ok() - })); + self.cellspacing.set( + mutation + .new_value(attr) + .and_then(|value| parse_unsigned_integer(value.chars()).ok()), + ); }, _ => {}, } @@ -444,7 +474,10 @@ impl VirtualMethods for HTMLTableElement { local_name!("border") => AttrValue::from_u32(value.into(), 1), local_name!("width") => AttrValue::from_nonzero_dimension(value.into()), local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(local_name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(local_name, value), } } } diff --git a/components/script/dom/htmltableheadercellelement.rs b/components/script/dom/htmltableheadercellelement.rs deleted file mode 100644 index a471faee00f..00000000000 --- a/components/script/dom/htmltableheadercellelement.rs +++ /dev/null @@ -1,37 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HTMLTableHeaderCellElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmltablecellelement::HTMLTableCellElement; -use dom::node::Node; -use dom_struct::dom_struct; -use html5ever_atoms::LocalName; - -#[dom_struct] -pub struct HTMLTableHeaderCellElement { - htmltablecellelement: HTMLTableCellElement, -} - -impl HTMLTableHeaderCellElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLTableHeaderCellElement { - HTMLTableHeaderCellElement { - htmltablecellelement: - HTMLTableCellElement::new_inherited(local_name, prefix, document) - } - } - - #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLTableHeaderCellElement> { - Node::reflect_node(box HTMLTableHeaderCellElement::new_inherited(local_name, prefix, document), - document, - HTMLTableHeaderCellElementBinding::Wrap) - } -} diff --git a/components/script/dom/htmltablerowelement.rs b/components/script/dom/htmltablerowelement.rs index be1b636beb4..5da05be4019 100644 --- a/components/script/dom/htmltablerowelement.rs +++ b/components/script/dom/htmltablerowelement.rs @@ -1,48 +1,50 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::HTMLTableElementBinding::HTMLTableElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLTableRowElementBinding::HTMLTableRowElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding::HTMLTableSectionElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::error::{ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{Element, LayoutElementHelpers}; +use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmltablecellelement::HTMLTableCellElement; +use crate::dom::htmltableelement::HTMLTableElement; +use crate::dom::htmltablesectionelement::HTMLTableSectionElement; +use crate::dom::node::{window_from_node, Node}; +use crate::dom::virtualmethods::VirtualMethods; use cssparser::RGBA; -use dom::bindings::codegen::Bindings::HTMLTableElementBinding::HTMLTableElementMethods; -use dom::bindings::codegen::Bindings::HTMLTableRowElementBinding::{self, HTMLTableRowElementMethods}; -use dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding::HTMLTableSectionElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::error::{ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, MutNullableJS, Root, RootedReference}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{Element, RawLayoutElementHelpers}; -use dom::htmlcollection::{CollectionFilter, HTMLCollection}; -use dom::htmlelement::HTMLElement; -use dom::htmltabledatacellelement::HTMLTableDataCellElement; -use dom::htmltableelement::HTMLTableElement; -use dom::htmltableheadercellelement::HTMLTableHeaderCellElement; -use dom::htmltablesectionelement::HTMLTableSectionElement; -use dom::node::{Node, window_from_node}; -use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use style::attr::AttrValue; #[derive(JSTraceable)] struct CellsFilter; impl CollectionFilter for CellsFilter { fn filter(&self, elem: &Element, root: &Node) -> bool { - (elem.is::<HTMLTableHeaderCellElement>() || elem.is::<HTMLTableDataCellElement>()) && - elem.upcast::<Node>().GetParentNode().r() == Some(root) + (elem.is::<HTMLTableCellElement>()) && + elem.upcast::<Node>().GetParentNode().as_deref() == Some(root) } } #[dom_struct] pub struct HTMLTableRowElement { htmlelement: HTMLElement, - cells: MutNullableJS<HTMLCollection>, + cells: MutNullableDom<HTMLCollection>, } impl HTMLTableRowElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) - -> HTMLTableRowElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTableRowElement { HTMLTableRowElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), cells: Default::default(), @@ -50,19 +52,29 @@ impl HTMLTableRowElement { } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, prefix: Option<DOMString>, document: &Document) - -> Root<HTMLTableRowElement> { - Node::reflect_node(box HTMLTableRowElement::new_inherited(local_name, prefix, document), - document, - HTMLTableRowElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTableRowElement> { + let n = Node::reflect_node( + Box::new(HTMLTableRowElement::new_inherited( + local_name, prefix, document, + )), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } /// Determine the index for this `HTMLTableRowElement` within the given /// `HTMLCollection`. Returns `-1` if not found within collection. - fn row_index(&self, collection: Root<HTMLCollection>) -> i32 { - collection.elements_iter() - .position(|elem| (&elem as &Element) == self.upcast()) - .map_or(-1, |i| i as i32) + fn row_index(&self, collection: DomRoot<HTMLCollection>) -> i32 { + collection + .elements_iter() + .position(|elem| (&elem as &Element) == self.upcast()) + .map_or(-1, |i| i as i32) } } @@ -74,30 +86,28 @@ impl HTMLTableRowElementMethods for HTMLTableRowElement { make_legacy_color_setter!(SetBgColor, "bgcolor"); // https://html.spec.whatwg.org/multipage/#dom-tr-cells - fn Cells(&self) -> Root<HTMLCollection> { + fn Cells(&self) -> DomRoot<HTMLCollection> { self.cells.or_init(|| { let window = window_from_node(self); - let filter = box CellsFilter; + let filter = Box::new(CellsFilter); HTMLCollection::create(&window, self.upcast(), filter) }) } // https://html.spec.whatwg.org/multipage/#dom-tr-insertcell - fn InsertCell(&self, index: i32) -> Fallible<Root<HTMLElement>> { + fn InsertCell(&self, index: i32) -> Fallible<DomRoot<HTMLElement>> { let node = self.upcast::<Node>(); node.insert_cell_or_row( index, || self.Cells(), - || HTMLTableDataCellElement::new(local_name!("td"), None, &node.owner_doc())) + || HTMLTableCellElement::new(local_name!("td"), None, &node.owner_doc()), + ) } // https://html.spec.whatwg.org/multipage/#dom-tr-deletecell fn DeleteCell(&self, index: i32) -> ErrorResult { let node = self.upcast::<Node>(); - node.delete_cell_or_row( - index, - || self.Cells(), - |n| n.is::<HTMLTableDataCellElement>()) + node.delete_cell_or_row(index, || self.Cells(), |n| n.is::<HTMLTableCellElement>()) } // https://html.spec.whatwg.org/multipage/#dom-tr-rowindex @@ -116,8 +126,9 @@ impl HTMLTableRowElementMethods for HTMLTableRowElement { Some(parent) => parent, None => return -1, }; - grandparent.downcast::<HTMLTableElement>() - .map_or(-1, |table| self.row_index(table.Rows())) + grandparent + .downcast::<HTMLTableElement>() + .map_or(-1, |table| self.row_index(table.Rows())) } // https://html.spec.whatwg.org/multipage/#dom-tr-sectionrowindex @@ -138,30 +149,30 @@ impl HTMLTableRowElementMethods for HTMLTableRowElement { } pub trait HTMLTableRowElementLayoutHelpers { - fn get_background_color(&self) -> Option<RGBA>; + fn get_background_color(self) -> Option<RGBA>; } -#[allow(unsafe_code)] -impl HTMLTableRowElementLayoutHelpers for LayoutJS<HTMLTableRowElement> { - fn get_background_color(&self) -> Option<RGBA> { - unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) - .and_then(AttrValue::as_color) - .cloned() - } +impl HTMLTableRowElementLayoutHelpers for LayoutDom<'_, HTMLTableRowElement> { + fn get_background_color(self) -> Option<RGBA> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) + .and_then(AttrValue::as_color) + .cloned() } } impl VirtualMethods for HTMLTableRowElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(local_name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(local_name, value), } } } diff --git a/components/script/dom/htmltablesectionelement.rs b/components/script/dom/htmltablesectionelement.rs index cf9be4b81b4..5787546f2be 100644 --- a/components/script/dom/htmltablesectionelement.rs +++ b/components/script/dom/htmltablesectionelement.rs @@ -1,23 +1,23 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding::HTMLTableSectionElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::error::{ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{Element, LayoutElementHelpers}; +use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmltablerowelement::HTMLTableRowElement; +use crate::dom::node::{window_from_node, Node}; +use crate::dom::virtualmethods::VirtualMethods; use cssparser::RGBA; -use dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding::{self, HTMLTableSectionElementMethods}; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::error::{ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, Root, RootedReference}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{Element, RawLayoutElementHelpers}; -use dom::htmlcollection::{CollectionFilter, HTMLCollection}; -use dom::htmlelement::HTMLElement; -use dom::htmltablerowelement::HTMLTableRowElement; -use dom::node::{Node, window_from_node}; -use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use style::attr::AttrValue; #[dom_struct] @@ -26,19 +26,31 @@ pub struct HTMLTableSectionElement { } impl HTMLTableSectionElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) - -> HTMLTableSectionElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTableSectionElement { HTMLTableSectionElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, prefix: Option<DOMString>, document: &Document) - -> Root<HTMLTableSectionElement> { - Node::reflect_node(box HTMLTableSectionElement::new_inherited(local_name, prefix, document), - document, - HTMLTableSectionElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTableSectionElement> { + let n = Node::reflect_node( + Box::new(HTMLTableSectionElement::new_inherited( + local_name, prefix, document, + )), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } } @@ -47,60 +59,58 @@ struct RowsFilter; impl CollectionFilter for RowsFilter { fn filter(&self, elem: &Element, root: &Node) -> bool { elem.is::<HTMLTableRowElement>() && - elem.upcast::<Node>().GetParentNode().r() == Some(root) + elem.upcast::<Node>().GetParentNode().as_deref() == Some(root) } } impl HTMLTableSectionElementMethods for HTMLTableSectionElement { // https://html.spec.whatwg.org/multipage/#dom-tbody-rows - fn Rows(&self) -> Root<HTMLCollection> { - HTMLCollection::create(&window_from_node(self), self.upcast(), box RowsFilter) + fn Rows(&self) -> DomRoot<HTMLCollection> { + HTMLCollection::create(&window_from_node(self), self.upcast(), Box::new(RowsFilter)) } // https://html.spec.whatwg.org/multipage/#dom-tbody-insertrow - fn InsertRow(&self, index: i32) -> Fallible<Root<HTMLElement>> { + fn InsertRow(&self, index: i32) -> Fallible<DomRoot<HTMLElement>> { let node = self.upcast::<Node>(); node.insert_cell_or_row( index, || self.Rows(), - || HTMLTableRowElement::new(local_name!("tr"), None, &node.owner_doc())) + || HTMLTableRowElement::new(local_name!("tr"), None, &node.owner_doc()), + ) } // https://html.spec.whatwg.org/multipage/#dom-tbody-deleterow fn DeleteRow(&self, index: i32) -> ErrorResult { let node = self.upcast::<Node>(); - node.delete_cell_or_row( - index, - || self.Rows(), - |n| n.is::<HTMLTableRowElement>()) + node.delete_cell_or_row(index, || self.Rows(), |n| n.is::<HTMLTableRowElement>()) } } pub trait HTMLTableSectionElementLayoutHelpers { - fn get_background_color(&self) -> Option<RGBA>; + fn get_background_color(self) -> Option<RGBA>; } -#[allow(unsafe_code)] -impl HTMLTableSectionElementLayoutHelpers for LayoutJS<HTMLTableSectionElement> { - fn get_background_color(&self) -> Option<RGBA> { - unsafe { - (&*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) - .and_then(AttrValue::as_color) - .cloned() - } +impl HTMLTableSectionElementLayoutHelpers for LayoutDom<'_, HTMLTableSectionElement> { + fn get_background_color(self) -> Option<RGBA> { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("bgcolor")) + .and_then(AttrValue::as_color) + .cloned() } } impl VirtualMethods for HTMLTableSectionElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(local_name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(local_name, value), } } } diff --git a/components/script/dom/htmltemplateelement.rs b/components/script/dom/htmltemplateelement.rs index 8e54ae63523..673f6e0c98a 100644 --- a/components/script/dom/htmltemplateelement.rs +++ b/components/script/dom/htmltemplateelement.rs @@ -1,67 +1,75 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::HTMLTemplateElementBinding; -use dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::documentfragment::DocumentFragment; -use dom::htmlelement::HTMLElement; -use dom::node::{CloneChildrenFlag, Node, document_from_node}; -use dom::virtualmethods::VirtualMethods; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; +use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::document::Document; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{document_from_node, CloneChildrenFlag, Node}; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLTemplateElement { htmlelement: HTMLElement, - /// https://html.spec.whatwg.org/multipage/#template-contents - contents: MutNullableJS<DocumentFragment>, + /// <https://html.spec.whatwg.org/multipage/#template-contents> + contents: MutNullableDom<DocumentFragment>, } impl HTMLTemplateElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLTemplateElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTemplateElement { HTMLTemplateElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), - contents: MutNullableJS::new(None), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + contents: MutNullableDom::new(None), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLTemplateElement> { - Node::reflect_node(box HTMLTemplateElement::new_inherited(local_name, prefix, document), - document, - HTMLTemplateElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTemplateElement> { + let n = Node::reflect_node( + Box::new(HTMLTemplateElement::new_inherited( + local_name, prefix, document, + )), + document, + ); + + n.upcast::<Node>().set_weird_parser_insertion_mode(); + n } } impl HTMLTemplateElementMethods for HTMLTemplateElement { - /// https://html.spec.whatwg.org/multipage/#dom-template-content - fn Content(&self) -> Root<DocumentFragment> { + /// <https://html.spec.whatwg.org/multipage/#dom-template-content> + fn Content(&self) -> DomRoot<DocumentFragment> { self.contents.or_init(|| { let doc = document_from_node(self); - doc.appropriate_template_contents_owner_document().CreateDocumentFragment() + doc.appropriate_template_contents_owner_document() + .CreateDocumentFragment() }) } } impl VirtualMethods for HTMLTemplateElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } - /// https://html.spec.whatwg.org/multipage/#template-adopting-steps + /// <https://html.spec.whatwg.org/multipage/#template-adopting-steps> fn adopting_steps(&self, old_doc: &Document) { self.super_type().unwrap().adopting_steps(old_doc); // Step 1. @@ -70,21 +78,30 @@ impl VirtualMethods for HTMLTemplateElement { Node::adopt(self.Content().upcast(), &doc); } - /// https://html.spec.whatwg.org/multipage/#the-template-element:concept-node-clone-ext - fn cloning_steps(&self, copy: &Node, maybe_doc: Option<&Document>, - clone_children: CloneChildrenFlag) { - self.super_type().unwrap().cloning_steps(copy, maybe_doc, clone_children); + /// <https://html.spec.whatwg.org/multipage/#the-template-element:concept-node-clone-ext> + fn cloning_steps( + &self, + copy: &Node, + maybe_doc: Option<&Document>, + clone_children: CloneChildrenFlag, + ) { + self.super_type() + .unwrap() + .cloning_steps(copy, maybe_doc, clone_children); if clone_children == CloneChildrenFlag::DoNotCloneChildren { // Step 1. return; } let copy = copy.downcast::<HTMLTemplateElement>().unwrap(); // Steps 2-3. - let copy_contents = Root::upcast::<Node>(copy.Content()); + let copy_contents = DomRoot::upcast::<Node>(copy.Content()); let copy_contents_doc = copy_contents.owner_doc(); for child in self.Content().upcast::<Node>().children() { let copy_child = Node::clone( - &child, Some(©_contents_doc), CloneChildrenFlag::CloneChildren); + &child, + Some(©_contents_doc), + CloneChildrenFlag::CloneChildren, + ); copy_contents.AppendChild(©_child).unwrap(); } } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 8a4e9f41cd3..c6d9331532f 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -1,134 +1,191 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding; -use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::element::RawLayoutElementHelpers; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlformelement::{FormControl, HTMLFormElement}; -use dom::keyboardevent::KeyboardEvent; -use dom::node::{ChildrenMutation, Node, NodeDamage, UnbindContext}; -use dom::node::{document_from_node, window_from_node}; -use dom::nodelist::NodeList; -use dom::validation::Validatable; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode; +use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::compositionevent::CompositionEvent; +use crate::dom::document::Document; +use crate::dom::element::LayoutElementHelpers; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; +use crate::dom::htmlinputelement::HTMLInputElement; +use crate::dom::keyboardevent::KeyboardEvent; +use crate::dom::node::window_from_node; +use crate::dom::node::{ + BindContext, ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, UnbindContext, +}; +use crate::dom::nodelist::NodeList; +use crate::dom::textcontrol::{TextControlElement, TextControlSelection}; +use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable}; +use crate::dom::validitystate::{ValidationFlags, ValidityState}; +use crate::dom::virtualmethods::VirtualMethods; +use crate::textinput::{ + Direction, KeyReaction, Lines, SelectionDirection, TextInput, UTF16CodeUnits, UTF8Bytes, +}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use ipc_channel::ipc::IpcSender; -use script_traits::ScriptMsg as ConstellationMsg; +use html5ever::{LocalName, Prefix}; +use script_traits::ScriptToConstellationChan; use std::cell::Cell; use std::default::Default; use std::ops::Range; use style::attr::AttrValue; -use style::element_state::*; -use textinput::{KeyReaction, Lines, SelectionDirection, TextInput}; +use style::element_state::ElementState; #[dom_struct] pub struct HTMLTextAreaElement { htmlelement: HTMLElement, - #[ignore_heap_size_of = "#7193"] - textinput: DOMRefCell<TextInput<IpcSender<ConstellationMsg>>>, - placeholder: DOMRefCell<DOMString>, + #[ignore_malloc_size_of = "#7193"] + textinput: DomRefCell<TextInput<ScriptToConstellationChan>>, + placeholder: DomRefCell<DOMString>, // https://html.spec.whatwg.org/multipage/#concept-textarea-dirty - value_changed: Cell<bool>, - form_owner: MutNullableJS<HTMLFormElement>, + value_dirty: Cell<bool>, + form_owner: MutNullableDom<HTMLFormElement>, + labels_node_list: MutNullableDom<NodeList>, + validity_state: MutNullableDom<ValidityState>, } pub trait LayoutHTMLTextAreaElementHelpers { - #[allow(unsafe_code)] - unsafe fn get_value_for_layout(self) -> String; - #[allow(unsafe_code)] - unsafe fn selection_for_layout(self) -> Option<Range<usize>>; - #[allow(unsafe_code)] + fn value_for_layout(self) -> String; + fn selection_for_layout(self) -> Option<Range<usize>>; fn get_cols(self) -> u32; - #[allow(unsafe_code)] fn get_rows(self) -> u32; } -impl LayoutHTMLTextAreaElementHelpers for LayoutJS<HTMLTextAreaElement> { - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] - unsafe fn get_value_for_layout(self) -> String { - let text = (*self.unsafe_get()).textinput.borrow_for_layout().get_content(); - String::from(if text.is_empty() { - (*self.unsafe_get()).placeholder.borrow_for_layout().clone() +#[allow(unsafe_code)] +impl<'dom> LayoutDom<'dom, HTMLTextAreaElement> { + fn textinput_content(self) -> DOMString { + unsafe { + self.unsafe_get() + .textinput + .borrow_for_layout() + .get_content() + } + } + + fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> { + unsafe { + self.unsafe_get() + .textinput + .borrow_for_layout() + .sorted_selection_offsets_range() + } + } + + fn placeholder(self) -> &'dom str { + unsafe { self.unsafe_get().placeholder.borrow_for_layout() } + } +} + +impl LayoutHTMLTextAreaElementHelpers for LayoutDom<'_, HTMLTextAreaElement> { + fn value_for_layout(self) -> String { + let text = self.textinput_content(); + if text.is_empty() { + // FIXME(nox): Would be cool to not allocate a new string if the + // placeholder is single line, but that's an unimportant detail. + self.placeholder() + .replace("\r\n", "\n") + .replace("\r", "\n") + .into() } else { - text - }) + text.into() + } } - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] - unsafe fn selection_for_layout(self) -> Option<Range<usize>> { - if !(*self.unsafe_get()).upcast::<Element>().focus_state() { + fn selection_for_layout(self) -> Option<Range<usize>> { + if !self.upcast::<Element>().focus_state() { return None; } - let textinput = (*self.unsafe_get()).textinput.borrow_for_layout(); - Some(textinput.get_absolute_selection_range()) + Some(UTF8Bytes::unwrap_range( + self.textinput_sorted_selection_offsets_range(), + )) } - #[allow(unsafe_code)] fn get_cols(self) -> u32 { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("cols")) - .map_or(DEFAULT_COLS, AttrValue::as_uint) - } + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("cols")) + .map_or(DEFAULT_COLS, AttrValue::as_uint) } - #[allow(unsafe_code)] fn get_rows(self) -> u32 { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("rows")) - .map_or(DEFAULT_ROWS, AttrValue::as_uint) - } + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("rows")) + .map_or(DEFAULT_ROWS, AttrValue::as_uint) } } // https://html.spec.whatwg.org/multipage/#attr-textarea-cols-value -static DEFAULT_COLS: u32 = 20; +const DEFAULT_COLS: u32 = 20; // https://html.spec.whatwg.org/multipage/#attr-textarea-rows-value -static DEFAULT_ROWS: u32 = 2; +const DEFAULT_ROWS: u32 = 2; + +const DEFAULT_MAX_LENGTH: i32 = -1; +const DEFAULT_MIN_LENGTH: i32 = -1; impl HTMLTextAreaElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLTextAreaElement { - let chan = document.window().upcast::<GlobalScope>().constellation_chan().clone(); + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTextAreaElement { + let chan = document + .window() + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .clone(); HTMLTextAreaElement { - htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE | IN_READ_WRITE_STATE, - local_name, prefix, document), - placeholder: DOMRefCell::new(DOMString::new()), - textinput: DOMRefCell::new(TextInput::new( - Lines::Multiple, DOMString::new(), chan, None, None, SelectionDirection::None)), - value_changed: Cell::new(false), + htmlelement: HTMLElement::new_inherited_with_state( + ElementState::IN_ENABLED_STATE | ElementState::IN_READ_WRITE_STATE, + local_name, + prefix, + document, + ), + placeholder: DomRefCell::new(DOMString::new()), + textinput: DomRefCell::new(TextInput::new( + Lines::Multiple, + DOMString::new(), + chan, + None, + None, + SelectionDirection::None, + )), + value_dirty: Cell::new(false), form_owner: Default::default(), + labels_node_list: Default::default(), + validity_state: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLTextAreaElement> { - Node::reflect_node(box HTMLTextAreaElement::new_inherited(local_name, prefix, document), - document, - HTMLTextAreaElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTextAreaElement> { + Node::reflect_node( + Box::new(HTMLTextAreaElement::new_inherited( + local_name, prefix, document, + )), + document, + ) + } + + pub fn auto_directionality(&self) -> String { + let value: String = self.Value().to_string(); + return HTMLInputElement::directionality_from_value(&value); } fn update_placeholder_shown_state(&self) { @@ -136,7 +193,27 @@ impl HTMLTextAreaElement { let has_value = !self.textinput.borrow().is_empty(); let el = self.upcast::<Element>(); el.set_placeholder_shown_state(has_placeholder && !has_value); - el.set_placeholder_shown_state(has_placeholder); + } + + // https://html.spec.whatwg.org/multipage/#concept-fe-mutable + fn is_mutable(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Aconcept-fe-mutable + // https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable + !(self.upcast::<Element>().disabled_state() || self.ReadOnly()) + } +} + +impl TextControlElement for HTMLTextAreaElement { + fn selection_api_applies(&self) -> bool { + true + } + + fn has_selectable_text(&self) -> bool { + true + } + + fn set_dirty_value_flag(&self, value: bool) { + self.value_dirty.set(value) } } @@ -150,6 +227,12 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { // https://html.spec.whatwg.org/multipage/#dom-textarea-cols make_limited_uint_setter!(SetCols, "cols", DEFAULT_COLS); + // https://html.spec.whatwg.org/multipage/#dom-input-dirName + make_getter!(DirName, "dirname"); + + // https://html.spec.whatwg.org/multipage/#dom-input-dirName + make_setter!(SetDirName, "dirname"); + // https://html.spec.whatwg.org/multipage/#dom-fe-disabled make_bool_getter!(Disabled, "disabled"); @@ -157,7 +240,7 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { make_bool_setter!(SetDisabled, "disabled"); // https://html.spec.whatwg.org/multipage/#dom-fae-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner() } @@ -165,7 +248,7 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { make_getter!(Name, "name"); // https://html.spec.whatwg.org/multipage/#attr-fe-name - make_setter!(SetName, "name"); + make_atomic_setter!(SetName, "name"); // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder make_getter!(Placeholder, "placeholder"); @@ -173,6 +256,18 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder make_setter!(SetPlaceholder, "placeholder"); + // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength + make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH); + + // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength + make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH); + + // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength + make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH); + + // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength + make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH); + // https://html.spec.whatwg.org/multipage/#attr-textarea-readonly make_bool_getter!(ReadOnly, "readonly"); @@ -213,7 +308,7 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { // if the element's dirty value flag is false, then the element's // raw value must be set to the value of the element's textContent IDL attribute - if !self.value_changed.get() { + if !self.value_dirty.get() { self.reset(); } } @@ -225,81 +320,140 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { // https://html.spec.whatwg.org/multipage/#dom-textarea-value fn SetValue(&self, value: DOMString) { - // TODO move the cursor to the end of the field - self.textinput.borrow_mut().set_content(value); - self.value_changed.set(true); + let mut textinput = self.textinput.borrow_mut(); + + // Step 1 + let old_value = textinput.get_content(); + + // Step 2 + textinput.set_content(value); + + // Step 3 + self.value_dirty.set(true); + + if old_value != textinput.get_content() { + // Step 4 + textinput.clear_selection_to_limit(Direction::Forward); + } self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } + // https://html.spec.whatwg.org/multipage/#dom-textarea-textlength + fn TextLength(&self) -> u32 { + let UTF16CodeUnits(num_units) = self.textinput.borrow().utf16_len(); + num_units as u32 + } + // https://html.spec.whatwg.org/multipage/#dom-lfe-labels - fn Labels(&self) -> Root<NodeList> { - self.upcast::<HTMLElement>().labels() + make_labels_getter!(Labels, labels_node_list); + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-select + fn Select(&self) { + self.selection().dom_select(); } - // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection - fn SetSelectionDirection(&self, direction: DOMString) { - self.textinput.borrow_mut().selection_direction = SelectionDirection::from(direction); + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart + fn GetSelectionStart(&self) -> Option<u32> { + self.selection().dom_start() } - // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection - fn SelectionDirection(&self) -> DOMString { - DOMString::from(self.textinput.borrow().selection_direction) + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart + fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult { + self.selection().set_dom_start(start) } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend - fn SetSelectionEnd(&self, end: u32) { - let selection_start = self.SelectionStart(); - self.textinput.borrow_mut().set_selection_range(selection_start, end); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + fn GetSelectionEnd(&self) -> Option<u32> { + self.selection().dom_end() } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend - fn SelectionEnd(&self) -> u32 { - self.textinput.borrow().get_absolute_insertion_point() as u32 + fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult { + self.selection().set_dom_end(end) } - // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart - fn SetSelectionStart(&self, start: u32) { - let selection_end = self.SelectionEnd(); - self.textinput.borrow_mut().set_selection_range(start, selection_end); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection + fn GetSelectionDirection(&self) -> Option<DOMString> { + self.selection().dom_direction() } - // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart - fn SelectionStart(&self) -> u32 { - self.textinput.borrow().get_selection_start() + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection + fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult { + self.selection().set_dom_direction(direction) } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange - fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) { - let direction = direction.map_or(SelectionDirection::None, |d| SelectionDirection::from(d)); - self.textinput.borrow_mut().selection_direction = direction; - self.textinput.borrow_mut().set_selection_range(start, end); - let window = window_from_node(self); - let _ = window.user_interaction_task_source().queue_event( - &self.upcast(), - atom!("select"), - EventBubbles::Bubbles, - EventCancelable::NotCancelable, - &window); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult { + self.selection().set_dom_range(start, end, direction) } -} + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext + fn SetRangeText(&self, replacement: DOMString) -> ErrorResult { + self.selection() + .set_dom_range_text(replacement, None, None, Default::default()) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext + fn SetRangeText_( + &self, + replacement: DOMString, + start: u32, + end: u32, + selection_mode: SelectionMode, + ) -> ErrorResult { + self.selection() + .set_dom_range_text(replacement, Some(start), Some(end), selection_mode) + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate + fn WillValidate(&self) -> bool { + self.is_instance_validatable() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validity + fn Validity(&self) -> DomRoot<ValidityState> { + self.validity_state() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity + fn CheckValidity(&self) -> bool { + self.check_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity + fn ReportValidity(&self) -> bool { + self.report_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage + fn ValidationMessage(&self) -> DOMString { + self.validation_message() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity + fn SetCustomValidity(&self, error: DOMString) { + self.validity_state().set_custom_error_message(error); + } +} impl HTMLTextAreaElement { pub fn reset(&self) { // https://html.spec.whatwg.org/multipage/#the-textarea-element:concept-form-reset-control - self.SetValue(self.DefaultValue()); - self.value_changed.set(false); + let mut textinput = self.textinput.borrow_mut(); + textinput.set_content(self.DefaultValue()); + self.value_dirty.set(false); } -} + #[allow(unrooted_must_root)] + fn selection(&self) -> TextControlSelection<Self> { + TextControlSelection::new(&self, &self.textinput) + } +} impl VirtualMethods for HTMLTextAreaElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -322,8 +476,33 @@ impl VirtualMethods for HTMLTextAreaElement { if !el.disabled_state() && !el.read_write_state() { el.set_read_write_state(true); } - } + }, } + el.update_sequentially_focusable_status(); + }, + local_name!("maxlength") => match *attr.value() { + AttrValue::Int(_, value) => { + let mut textinput = self.textinput.borrow_mut(); + + if value < 0 { + textinput.set_max_length(None); + } else { + textinput.set_max_length(Some(UTF16CodeUnits(value as usize))) + } + }, + _ => panic!("Expected an AttrValue::Int"), + }, + local_name!("minlength") => match *attr.value() { + AttrValue::Int(_, value) => { + let mut textinput = self.textinput.borrow_mut(); + + if value < 0 { + textinput.set_min_length(None); + } else { + textinput.set_min_length(Some(UTF16CodeUnits(value as usize))) + } + }, + _ => panic!("Expected an AttrValue::Int"), }, local_name!("placeholder") => { { @@ -343,7 +522,7 @@ impl VirtualMethods for HTMLTextAreaElement { }, AttributeMutation::Removed => { el.set_read_write_state(!el.disabled_state()); - } + }, } }, local_name!("form") => { @@ -353,19 +532,29 @@ impl VirtualMethods for HTMLTextAreaElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - self.upcast::<Element>().check_ancestors_disabled_state_for_form_control(); + self.upcast::<Element>() + .check_ancestors_disabled_state_for_form_control(); } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match *name { local_name!("cols") => AttrValue::from_limited_u32(value.into(), DEFAULT_COLS), local_name!("rows") => AttrValue::from_limited_u32(value.into(), DEFAULT_ROWS), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + local_name!("maxlength") => { + AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH) + }, + local_name!("minlength") => { + AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH) + }, + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } @@ -374,18 +563,38 @@ impl VirtualMethods for HTMLTextAreaElement { let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); - if node.ancestors().any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) { + if node + .ancestors() + .any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) + { el.check_ancestors_disabled_state_for_form_control(); } else { el.check_disabled_attribute(); } } + // The cloning steps for textarea elements must propagate the raw value + // and dirty value flag from the node being cloned to the copy. + fn cloning_steps( + &self, + copy: &Node, + maybe_doc: Option<&Document>, + clone_children: CloneChildrenFlag, + ) { + if let Some(ref s) = self.super_type() { + s.cloning_steps(copy, maybe_doc, clone_children); + } + let el = copy.downcast::<HTMLTextAreaElement>().unwrap(); + el.value_dirty.set(self.value_dirty.get()); + let mut textinput = el.textinput.borrow_mut(); + textinput.set_content(self.textinput.borrow().get_content()); + } + fn children_changed(&self, mutation: &ChildrenMutation) { if let Some(ref s) = self.super_type() { s.children_changed(mutation); } - if !self.value_changed.get() { + if !self.value_dirty.get() { self.reset(); } } @@ -398,8 +607,6 @@ impl VirtualMethods for HTMLTextAreaElement { if event.type_() == atom!("click") && !event.DefaultPrevented() { //TODO: set the editing position for text inputs - - document_from_node(self).request_focus(self.upcast()); } else if event.type_() == atom!("keydown") && !event.DefaultPrevented() { if let Some(kevent) = event.downcast::<KeyboardEvent>() { // This can't be inlined, as holding on to textinput.borrow_mut() @@ -408,27 +615,47 @@ impl VirtualMethods for HTMLTextAreaElement { match action { KeyReaction::TriggerDefaultAction => (), KeyReaction::DispatchInput => { - self.value_changed.set(true); + self.value_dirty.set(true); self.update_placeholder_shown_state(); self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); event.mark_as_handled(); - } + }, KeyReaction::RedrawSelection => { self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); event.mark_as_handled(); - } + }, KeyReaction::Nothing => (), } } } else if event.type_() == atom!("keypress") && !event.DefaultPrevented() { if event.IsTrusted() { let window = window_from_node(self); - let _ = window.user_interaction_task_source() - .queue_event(&self.upcast(), - atom!("input"), - EventBubbles::Bubbles, - EventCancelable::NotCancelable, - &window); + let _ = window + .task_manager() + .user_interaction_task_source() + .queue_event( + &self.upcast(), + atom!("input"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + &window, + ); + } + } else if event.type_() == atom!("compositionstart") || + event.type_() == atom!("compositionupdate") || + event.type_() == atom!("compositionend") + { + // TODO: Update DOM on start and continue + // and generally do proper CompositionEvent handling. + if let Some(compositionevent) = event.downcast::<CompositionEvent>() { + if event.type_() == atom!("compositionend") { + let _ = self + .textinput + .borrow_mut() + .handle_compositionend(compositionevent); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + event.mark_as_handled(); } } } @@ -442,7 +669,7 @@ impl VirtualMethods for HTMLTextAreaElement { } impl FormControl for HTMLTextAreaElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } @@ -455,5 +682,61 @@ impl FormControl for HTMLTextAreaElement { } } +impl Validatable for HTMLTextAreaElement { + fn as_element(&self) -> &Element { + self.upcast() + } + + fn validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), self.upcast())) + } + + fn is_instance_validatable(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation + !self.upcast::<Element>().disabled_state() && + !self.ReadOnly() && + !is_barred_by_datalist_ancestor(self.upcast()) + } -impl Validatable for HTMLTextAreaElement {} + fn perform_validation(&self, validate_flags: ValidationFlags) -> ValidationFlags { + let mut failed_flags = ValidationFlags::empty(); + + let textinput = self.textinput.borrow(); + let UTF16CodeUnits(value_len) = textinput.utf16_len(); + let last_edit_by_user = !textinput.was_last_change_by_set_content(); + let value_dirty = self.value_dirty.get(); + + // https://html.spec.whatwg.org/multipage/#suffering-from-being-missing + // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Asuffering-from-being-missing + if validate_flags.contains(ValidationFlags::VALUE_MISSING) { + if self.Required() && self.is_mutable() && value_len == 0 { + failed_flags.insert(ValidationFlags::VALUE_MISSING); + } + } + + if value_dirty && last_edit_by_user && value_len > 0 { + // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long + // https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long + if validate_flags.contains(ValidationFlags::TOO_LONG) { + let max_length = self.MaxLength(); + if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) { + failed_flags.insert(ValidationFlags::TOO_LONG); + } + } + + // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short + // https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short + if validate_flags.contains(ValidationFlags::TOO_SHORT) { + let min_length = self.MinLength(); + if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) { + failed_flags.insert(ValidationFlags::TOO_SHORT); + } + } + } + + failed_flags + } +} diff --git a/components/script/dom/htmltimeelement.rs b/components/script/dom/htmltimeelement.rs index 2c559a30cc5..2b0974dd76c 100644 --- a/components/script/dom/htmltimeelement.rs +++ b/components/script/dom/htmltimeelement.rs @@ -1,16 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLTimeElementBinding; -use dom::bindings::codegen::Bindings::HTMLTimeElementBinding::HTMLTimeElementMethods; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::codegen::Bindings::HTMLTimeElementBinding::HTMLTimeElementMethods; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLTimeElement { @@ -18,19 +17,26 @@ pub struct HTMLTimeElement { } impl HTMLTimeElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLTimeElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTimeElement { HTMLTimeElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLTimeElement> { - Node::reflect_node(box HTMLTimeElement::new_inherited(local_name, prefix, document), - document, - HTMLTimeElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTimeElement> { + Node::reflect_node( + Box::new(HTMLTimeElement::new_inherited(local_name, prefix, document)), + document, + ) } } diff --git a/components/script/dom/htmltitleelement.rs b/components/script/dom/htmltitleelement.rs index 43208399fb6..7fce947e626 100644 --- a/components/script/dom/htmltitleelement.rs +++ b/components/script/dom/htmltitleelement.rs @@ -1,19 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLTitleElementBinding; -use dom::bindings::codegen::Bindings::HTMLTitleElementBinding::HTMLTitleElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::{ChildrenMutation, Node}; -use dom::virtualmethods::VirtualMethods; +use crate::dom::bindings::codegen::Bindings::HTMLTitleElementBinding::HTMLTitleElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{BindContext, ChildrenMutation, Node}; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLTitleElement { @@ -21,19 +20,28 @@ pub struct HTMLTitleElement { } impl HTMLTitleElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLTitleElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTitleElement { HTMLTitleElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLTitleElement> { - Node::reflect_node(box HTMLTitleElement::new_inherited(local_name, prefix, document), - document, - HTMLTitleElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTitleElement> { + Node::reflect_node( + Box::new(HTMLTitleElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } @@ -50,8 +58,8 @@ impl HTMLTitleElementMethods for HTMLTitleElement { } impl VirtualMethods for HTMLTitleElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn children_changed(&self, mutation: &ChildrenMutation) { @@ -64,12 +72,12 @@ impl VirtualMethods for HTMLTitleElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } let node = self.upcast::<Node>(); - if tree_in_doc { + if context.tree_in_doc { node.owner_doc().title_changed(); } } diff --git a/components/script/dom/htmltrackelement.rs b/components/script/dom/htmltrackelement.rs index 97811ee5a10..c068bd88295 100644 --- a/components/script/dom/htmltrackelement.rs +++ b/components/script/dom/htmltrackelement.rs @@ -1,34 +1,135 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HTMLTrackElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::HTMLTrackElementBinding::{ + HTMLTrackElementConstants, HTMLTrackElementMethods, +}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use crate::dom::texttrack::TextTrack; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; + +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +#[repr(u16)] +#[allow(unused)] +pub enum ReadyState { + None = HTMLTrackElementConstants::NONE, + Loading = HTMLTrackElementConstants::LOADING, + Loaded = HTMLTrackElementConstants::LOADED, + Error = HTMLTrackElementConstants::ERROR, +} #[dom_struct] pub struct HTMLTrackElement { htmlelement: HTMLElement, + ready_state: ReadyState, + track: Dom<TextTrack>, } impl HTMLTrackElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLTrackElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + track: &TextTrack, + ) -> HTMLTrackElement { HTMLTrackElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), + ready_state: ReadyState::None, + track: Dom::from_ref(&track), + } + } + + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTrackElement> { + let track = TextTrack::new( + &document.window(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + None, + ); + Node::reflect_node( + Box::new(HTMLTrackElement::new_inherited( + local_name, prefix, document, &track, + )), + document, + ) + } +} + +impl HTMLTrackElementMethods for HTMLTrackElement { + // https://html.spec.whatwg.org/multipage/#dom-track-kind + fn Kind(&self) -> DOMString { + let element = self.upcast::<Element>(); + // Get the value of "kind" and transform all uppercase + // chars into lowercase. + let kind = element + .get_string_attribute(&local_name!("kind")) + .to_lowercase(); + match &*kind { + "subtitles" | "captions" | "descriptions" | "chapters" | "metadata" => { + // The value of "kind" is valid. Return the lowercase version + // of it. + DOMString::from(kind) + }, + _ if kind.is_empty() => { + // The default value should be "subtitles". If "kind" has not + // been set, the real value for "kind" is "subtitles" + DOMString::from("subtitles") + }, + _ => { + // If "kind" has been set but it is not one of the valid + // values, return the default invalid value of "metadata" + DOMString::from("metadata") + }, } } - #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLTrackElement> { - Node::reflect_node(box HTMLTrackElement::new_inherited(local_name, prefix, document), - document, - HTMLTrackElementBinding::Wrap) + // https://html.spec.whatwg.org/multipage/#dom-track-kind + // Do no transformations on the value of "kind" when setting it. + // All transformations should be done in the get method. + make_setter!(SetKind, "kind"); + + // https://html.spec.whatwg.org/multipage/#dom-track-src + make_url_getter!(Src, "src"); + // https://html.spec.whatwg.org/multipage/#dom-track-src + make_url_setter!(SetSrc, "src"); + + // https://html.spec.whatwg.org/multipage/#dom-track-srclang + make_getter!(Srclang, "srclang"); + // https://html.spec.whatwg.org/multipage/#dom-track-srclang + make_setter!(SetSrclang, "srclang"); + + // https://html.spec.whatwg.org/multipage/#dom-track-label + make_getter!(Label, "label"); + // https://html.spec.whatwg.org/multipage/#dom-track-label + make_setter!(SetLabel, "label"); + + // https://html.spec.whatwg.org/multipage/#dom-track-default + make_bool_getter!(Default, "default"); + // https://html.spec.whatwg.org/multipage/#dom-track-default + make_bool_setter!(SetDefault, "default"); + + // https://html.spec.whatwg.org/multipage/#dom-track-readystate + fn ReadyState(&self) -> u16 { + self.ready_state as u16 + } + + // https://html.spec.whatwg.org/multipage/#dom-track-track + fn Track(&self) -> DomRoot<TextTrack> { + DomRoot::from_ref(&*self.track) } } diff --git a/components/script/dom/htmlulistelement.rs b/components/script/dom/htmlulistelement.rs index 4153709e108..aab11124365 100644 --- a/components/script/dom/htmlulistelement.rs +++ b/components/script/dom/htmlulistelement.rs @@ -1,34 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLUListElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLUListElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLUListElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLUListElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLUListElement { HTMLUListElement { - htmlelement: HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLUListElement> { - Node::reflect_node(box HTMLUListElement::new_inherited(local_name, prefix, document), - document, - HTMLUListElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLUListElement> { + Node::reflect_node( + Box::new(HTMLUListElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmlunknownelement.rs b/components/script/dom/htmlunknownelement.rs index d54025de852..27fa932e936 100644 --- a/components/script/dom/htmlunknownelement.rs +++ b/components/script/dom/htmlunknownelement.rs @@ -1,37 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLUnknownElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlelement::HTMLElement; -use dom::node::Node; +use crate::dom::bindings::root::DomRoot; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; #[dom_struct] pub struct HTMLUnknownElement { - htmlelement: HTMLElement + htmlelement: HTMLElement, } impl HTMLUnknownElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLUnknownElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLUnknownElement { HTMLUnknownElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document) + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLUnknownElement> { - Node::reflect_node(box HTMLUnknownElement::new_inherited(local_name, prefix, document), - document, - HTMLUnknownElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLUnknownElement> { + Node::reflect_node( + Box::new(HTMLUnknownElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } } diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs index 1a0d93dacf8..2100b70330d 100644 --- a/components/script/dom/htmlvideoelement.rs +++ b/components/script/dom/htmlvideoelement.rs @@ -1,35 +1,407 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::HTMLVideoElementBinding; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::htmlmediaelement::HTMLMediaElement; -use dom::node::Node; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::document_loader::{LoadBlocker, LoadType}; +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLVideoElementBinding::HTMLVideoElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlmediaelement::{HTMLMediaElement, ReadyState}; +use crate::dom::node::{document_from_node, window_from_node, Node}; +use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::virtualmethods::VirtualMethods; +use crate::fetch::FetchCanceller; +use crate::image_listener::{generate_cache_listener_for_element, ImageCacheListener}; +use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use euclid::default::Size2D; +use html5ever::{LocalName, Prefix}; +use ipc_channel::ipc; +use ipc_channel::router::ROUTER; +use net_traits::image_cache::{ + ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, PendingImageId, + UsePlaceholder, +}; +use net_traits::request::{CredentialsMode, Destination, RequestBuilder}; +use net_traits::{ + CoreResourceMsg, FetchChannels, FetchMetadata, FetchResponseListener, FetchResponseMsg, +}; +use net_traits::{NetworkError, ResourceFetchTiming, ResourceTimingType}; +use servo_media::player::video::VideoFrame; +use servo_url::ServoUrl; +use std::cell::Cell; +use std::sync::{Arc, Mutex}; + +const DEFAULT_WIDTH: u32 = 300; +const DEFAULT_HEIGHT: u32 = 150; #[dom_struct] pub struct HTMLVideoElement { - htmlmediaelement: HTMLMediaElement + htmlmediaelement: HTMLMediaElement, + /// https://html.spec.whatwg.org/multipage/#dom-video-videowidth + video_width: Cell<u32>, + /// https://html.spec.whatwg.org/multipage/#dom-video-videoheight + video_height: Cell<u32>, + /// Incremented whenever tasks associated with this element are cancelled. + generation_id: Cell<u32>, + /// Poster frame fetch request canceller. + poster_frame_canceller: DomRefCell<FetchCanceller>, + /// Load event blocker. Will block the load event while the poster frame + /// is being fetched. + load_blocker: DomRefCell<Option<LoadBlocker>>, + /// A copy of the last frame + #[ignore_malloc_size_of = "VideoFrame"] + last_frame: DomRefCell<Option<VideoFrame>>, } impl HTMLVideoElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document) -> HTMLVideoElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLVideoElement { HTMLVideoElement { - htmlmediaelement: - HTMLMediaElement::new_inherited(local_name, prefix, document) + htmlmediaelement: HTMLMediaElement::new_inherited(local_name, prefix, document), + video_width: Cell::new(DEFAULT_WIDTH), + video_height: Cell::new(DEFAULT_HEIGHT), + generation_id: Cell::new(0), + poster_frame_canceller: DomRefCell::new(Default::default()), + load_blocker: Default::default(), + last_frame: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLVideoElement> { - Node::reflect_node(box HTMLVideoElement::new_inherited(local_name, prefix, document), - document, - HTMLVideoElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLVideoElement> { + Node::reflect_node( + Box::new(HTMLVideoElement::new_inherited( + local_name, prefix, document, + )), + document, + ) + } + + pub fn get_video_width(&self) -> u32 { + self.video_width.get() + } + + pub fn set_video_width(&self, width: u32) { + self.video_width.set(width); + } + + pub fn get_video_height(&self) -> u32 { + self.video_height.get() + } + + pub fn set_video_height(&self, height: u32) { + self.video_height.set(height); + } + + pub fn allow_load_event(&self) { + LoadBlocker::terminate(&mut *self.load_blocker.borrow_mut()); + } + + pub fn get_current_frame_data(&self) -> Option<(Option<ipc::IpcSharedMemory>, Size2D<u32>)> { + let frame = self.htmlmediaelement.get_current_frame(); + if frame.is_some() { + *self.last_frame.borrow_mut() = frame; + } + + match self.last_frame.borrow().as_ref() { + 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)) + } else { + // XXX(victor): here we only have the GL texture ID. + Some((None, size)) + } + }, + None => None, + } + } + + /// https://html.spec.whatwg.org/multipage/#poster-frame + fn fetch_poster_frame(&self, poster_url: &str) { + // Step 1. + let cancel_receiver = self.poster_frame_canceller.borrow_mut().initialize(); + self.generation_id.set(self.generation_id.get() + 1); + + // Step 2. + if poster_url.is_empty() { + return; + } + + // Step 3. + let poster_url = match document_from_node(self).url().join(&poster_url) { + Ok(url) => url, + Err(_) => return, + }; + + // Step 4. + // We use the image cache for poster frames so we save as much + // network activity as possible. + let window = window_from_node(self); + let image_cache = window.image_cache(); + let sender = generate_cache_listener_for_element(self); + let cache_result = image_cache.track_image( + poster_url.clone(), + window.origin().immutable().clone(), + None, + sender, + UsePlaceholder::No, + ); + + match cache_result { + ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { + image, + url, + .. + }) => { + self.process_image_response(ImageResponse::Loaded(image, url)); + }, + ImageCacheResult::ReadyForRequest(id) => { + self.do_fetch_poster_frame(poster_url, id, cancel_receiver) + }, + _ => (), + } + } + + /// https://html.spec.whatwg.org/multipage/#poster-frame + fn do_fetch_poster_frame( + &self, + poster_url: ServoUrl, + id: PendingImageId, + cancel_receiver: ipc::IpcReceiver<()>, + ) { + // Continuation of step 4. + let document = document_from_node(self); + let request = RequestBuilder::new(poster_url.clone(), document.global().get_referrer()) + .destination(Destination::Image) + .credentials_mode(CredentialsMode::Include) + .use_url_credentials(true) + .origin(document.origin().immutable().clone()) + .pipeline_id(Some(document.global().pipeline_id())); + + // Step 5. + // This delay must be independent from the ones created by HTMLMediaElement during + // its media load algorithm, otherwise a code like + // <video poster="poster.png"></video> + // (which triggers no media load algorithm unless a explicit call to .load() is done) + // will block the document's load event forever. + let mut blocker = self.load_blocker.borrow_mut(); + LoadBlocker::terminate(&mut *blocker); + *blocker = Some(LoadBlocker::new( + &document_from_node(self), + LoadType::Image(poster_url.clone()), + )); + + let window = window_from_node(self); + let context = Arc::new(Mutex::new(PosterFrameFetchContext::new( + self, poster_url, id, + ))); + + let (action_sender, action_receiver) = ipc::channel().unwrap(); + let (task_source, canceller) = window + .task_manager() + .networking_task_source_with_canceller(); + let listener = NetworkListener { + context, + task_source, + canceller: Some(canceller), + }; + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + listener.notify_fetch(message.to().unwrap()); + }), + ); + let global = self.global(); + global + .core_resource_thread() + .send(CoreResourceMsg::Fetch( + request, + FetchChannels::ResponseMsg(action_sender, Some(cancel_receiver)), + )) + .unwrap(); + } +} + +impl HTMLVideoElementMethods for HTMLVideoElement { + // https://html.spec.whatwg.org/multipage/#dom-video-videowidth + fn VideoWidth(&self) -> u32 { + if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing { + return 0; + } + self.video_width.get() + } + + // https://html.spec.whatwg.org/multipage/#dom-video-videoheight + fn VideoHeight(&self) -> u32 { + if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing { + return 0; + } + self.video_height.get() + } + + // https://html.spec.whatwg.org/multipage/#dom-video-poster + make_getter!(Poster, "poster"); + + // https://html.spec.whatwg.org/multipage/#dom-video-poster + make_setter!(SetPoster, "poster"); + + // For testing purposes only. This is not an event from + // https://html.spec.whatwg.org/multipage/#dom-video-poster + event_handler!(postershown, GetOnpostershown, SetOnpostershown); +} + +impl VirtualMethods for HTMLVideoElement { + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLMediaElement>() as &dyn VirtualMethods) + } + + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { + self.super_type().unwrap().attribute_mutated(attr, mutation); + + if let Some(new_value) = mutation.new_value(attr) { + match attr.local_name() { + &local_name!("poster") => { + self.fetch_poster_frame(&new_value); + }, + _ => (), + }; + } + } +} + +impl ImageCacheListener for HTMLVideoElement { + fn generation_id(&self) -> u32 { + self.generation_id.get() + } + + fn process_image_response(&self, response: ImageResponse) { + self.htmlmediaelement.process_poster_response(response); + } +} + +struct PosterFrameFetchContext { + /// Reference to the script thread image cache. + image_cache: Arc<dyn ImageCache>, + /// The element that initiated the request. + elem: Trusted<HTMLVideoElement>, + /// The cache ID for this request. + id: PendingImageId, + /// True if this response is invalid and should be ignored. + cancelled: bool, + /// Timing data for this resource + resource_timing: ResourceFetchTiming, + /// Url for the resource + url: ServoUrl, +} + +impl FetchResponseListener for PosterFrameFetchContext { + fn process_request_body(&mut self) {} + fn process_request_eof(&mut self) {} + + fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { + self.image_cache + .notify_pending_response(self.id, FetchResponseMsg::ProcessResponse(metadata.clone())); + + let metadata = metadata.ok().map(|meta| match meta { + FetchMetadata::Unfiltered(m) => m, + FetchMetadata::Filtered { unsafe_, .. } => unsafe_, + }); + + let status_is_ok = metadata + .as_ref() + .and_then(|m| m.status.as_ref()) + .map_or(true, |s| s.0 >= 200 && s.0 < 300); + + if !status_is_ok { + self.cancelled = true; + self.elem + .root() + .poster_frame_canceller + .borrow_mut() + .cancel(); + } + } + + fn process_response_chunk(&mut self, payload: Vec<u8>) { + if self.cancelled { + // An error was received previously, skip processing the payload. + return; + } + + self.image_cache + .notify_pending_response(self.id, FetchResponseMsg::ProcessResponseChunk(payload)); + } + + fn process_response_eof(&mut self, response: Result<ResourceFetchTiming, NetworkError>) { + self.elem.root().allow_load_event(); + self.image_cache + .notify_pending_response(self.id, FetchResponseMsg::ProcessResponseEOF(response)); + } + + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } + + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } + + fn submit_resource_timing(&mut self) { + network_listener::submit_timing(self) + } +} + +impl ResourceTimingListener for PosterFrameFetchContext { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + let initiator_type = InitiatorType::LocalName( + self.elem + .root() + .upcast::<Element>() + .local_name() + .to_string(), + ); + (initiator_type, self.url.clone()) + } + + fn resource_timing_global(&self) -> DomRoot<GlobalScope> { + document_from_node(&*self.elem.root()).global() + } +} + +impl PreInvoke for PosterFrameFetchContext { + fn should_invoke(&self) -> bool { + true + } +} + +impl PosterFrameFetchContext { + fn new(elem: &HTMLVideoElement, url: ServoUrl, id: PendingImageId) -> PosterFrameFetchContext { + let window = window_from_node(elem); + PosterFrameFetchContext { + image_cache: window.image_cache(), + elem: Trusted::new(elem), + id, + cancelled: false, + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), + url, + } } } diff --git a/components/script/dom/identityhub.rs b/components/script/dom/identityhub.rs new file mode 100644 index 00000000000..515e9f7e1b1 --- /dev/null +++ b/components/script/dom/identityhub.rs @@ -0,0 +1,228 @@ +/* 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 smallvec::SmallVec; +use webgpu::wgpu::{ + hub::IdentityManager, + id::{ + AdapterId, BindGroupId, BindGroupLayoutId, BufferId, CommandEncoderId, ComputePipelineId, + DeviceId, PipelineLayoutId, RenderBundleId, RenderPipelineId, SamplerId, ShaderModuleId, + TextureId, TextureViewId, + }, +}; +use webgpu::wgt::Backend; + +#[derive(Debug)] +pub struct IdentityHub { + adapters: IdentityManager, + devices: IdentityManager, + buffers: IdentityManager, + bind_groups: IdentityManager, + bind_group_layouts: IdentityManager, + compute_pipelines: IdentityManager, + pipeline_layouts: IdentityManager, + shader_modules: IdentityManager, + command_encoders: IdentityManager, + textures: IdentityManager, + texture_views: IdentityManager, + samplers: IdentityManager, + render_pipelines: IdentityManager, + render_bundles: IdentityManager, +} + +impl IdentityHub { + fn new() -> Self { + IdentityHub { + adapters: IdentityManager::default(), + devices: IdentityManager::default(), + buffers: IdentityManager::default(), + bind_groups: IdentityManager::default(), + bind_group_layouts: IdentityManager::default(), + compute_pipelines: IdentityManager::default(), + pipeline_layouts: IdentityManager::default(), + shader_modules: IdentityManager::default(), + command_encoders: IdentityManager::default(), + textures: IdentityManager::default(), + texture_views: IdentityManager::default(), + samplers: IdentityManager::default(), + render_pipelines: IdentityManager::default(), + render_bundles: IdentityManager::default(), + } + } +} + +#[derive(Debug)] +pub struct Identities { + surface: IdentityManager, + #[cfg(any(target_os = "linux", target_os = "windows"))] + vk_hub: IdentityHub, + #[cfg(target_os = "windows")] + dx12_hub: IdentityHub, + #[cfg(target_os = "windows")] + dx11_hub: IdentityHub, + #[cfg(any(target_os = "ios", target_os = "macos"))] + metal_hub: IdentityHub, + dummy_hub: IdentityHub, +} + +impl Identities { + pub fn new() -> Self { + Identities { + surface: IdentityManager::default(), + #[cfg(any(target_os = "linux", target_os = "windows"))] + vk_hub: IdentityHub::new(), + #[cfg(target_os = "windows")] + dx12_hub: IdentityHub::new(), + #[cfg(target_os = "windows")] + dx11_hub: IdentityHub::new(), + #[cfg(any(target_os = "ios", target_os = "macos"))] + metal_hub: IdentityHub::new(), + dummy_hub: IdentityHub::new(), + } + } + + fn select(&mut self, backend: Backend) -> &mut IdentityHub { + match backend { + #[cfg(any(target_os = "linux", target_os = "windows"))] + Backend::Vulkan => &mut self.vk_hub, + #[cfg(target_os = "windows")] + Backend::Dx12 => &mut self.dx12_hub, + #[cfg(target_os = "windows")] + Backend::Dx11 => &mut self.dx11_hub, + #[cfg(any(target_os = "ios", target_os = "macos"))] + Backend::Metal => &mut self.metal_hub, + _ => &mut self.dummy_hub, + } + } + + fn hubs(&mut self) -> Vec<(&mut IdentityHub, Backend)> { + vec![ + #[cfg(any(target_os = "linux", target_os = "windows"))] + (&mut self.vk_hub, Backend::Vulkan), + #[cfg(target_os = "windows")] + (&mut self.dx12_hub, Backend::Dx12), + #[cfg(target_os = "windows")] + (&mut self.dx11_hub, Backend::Dx11), + #[cfg(any(target_os = "ios", target_os = "macos"))] + (&mut self.metal_hub, Backend::Metal), + (&mut self.dummy_hub, Backend::Empty), + ] + } + + pub fn create_device_id(&mut self, backend: Backend) -> DeviceId { + self.select(backend).devices.alloc(backend) + } + + pub fn kill_device_id(&mut self, id: DeviceId) { + self.select(id.backend()).devices.free(id); + } + + pub fn create_adapter_ids(&mut self) -> SmallVec<[AdapterId; 4]> { + let mut ids = SmallVec::new(); + for hubs in self.hubs() { + ids.push(hubs.0.adapters.alloc(hubs.1)); + } + ids + } + + pub fn kill_adapter_id(&mut self, id: AdapterId) { + self.select(id.backend()).adapters.free(id); + } + + pub fn create_buffer_id(&mut self, backend: Backend) -> BufferId { + self.select(backend).buffers.alloc(backend) + } + + pub fn kill_buffer_id(&mut self, id: BufferId) { + self.select(id.backend()).buffers.free(id); + } + + pub fn create_bind_group_id(&mut self, backend: Backend) -> BindGroupId { + self.select(backend).bind_groups.alloc(backend) + } + + pub fn kill_bind_group_id(&mut self, id: BindGroupId) { + self.select(id.backend()).bind_groups.free(id); + } + + pub fn create_bind_group_layout_id(&mut self, backend: Backend) -> BindGroupLayoutId { + self.select(backend).bind_group_layouts.alloc(backend) + } + + pub fn kill_bind_group_layout_id(&mut self, id: BindGroupLayoutId) { + self.select(id.backend()).bind_group_layouts.free(id); + } + + pub fn create_compute_pipeline_id(&mut self, backend: Backend) -> ComputePipelineId { + self.select(backend).compute_pipelines.alloc(backend) + } + + pub fn kill_compute_pipeline_id(&mut self, id: ComputePipelineId) { + self.select(id.backend()).compute_pipelines.free(id); + } + + pub fn create_pipeline_layout_id(&mut self, backend: Backend) -> PipelineLayoutId { + self.select(backend).pipeline_layouts.alloc(backend) + } + + pub fn kill_pipeline_layout_id(&mut self, id: PipelineLayoutId) { + self.select(id.backend()).pipeline_layouts.free(id); + } + + pub fn create_shader_module_id(&mut self, backend: Backend) -> ShaderModuleId { + self.select(backend).shader_modules.alloc(backend) + } + + pub fn kill_shader_module_id(&mut self, id: ShaderModuleId) { + self.select(id.backend()).shader_modules.free(id); + } + + pub fn create_command_encoder_id(&mut self, backend: Backend) -> CommandEncoderId { + self.select(backend).command_encoders.alloc(backend) + } + + pub fn kill_command_buffer_id(&mut self, id: CommandEncoderId) { + self.select(id.backend()).command_encoders.free(id); + } + + pub fn create_sampler_id(&mut self, backend: Backend) -> SamplerId { + self.select(backend).samplers.alloc(backend) + } + + pub fn kill_sampler_id(&mut self, id: SamplerId) { + self.select(id.backend()).samplers.free(id); + } + + pub fn create_render_pipeline_id(&mut self, backend: Backend) -> RenderPipelineId { + self.select(backend).render_pipelines.alloc(backend) + } + + pub fn kill_render_pipeline_id(&mut self, id: RenderPipelineId) { + self.select(id.backend()).render_pipelines.free(id); + } + + pub fn create_texture_id(&mut self, backend: Backend) -> TextureId { + self.select(backend).textures.alloc(backend) + } + + pub fn kill_texture_id(&mut self, id: TextureId) { + self.select(id.backend()).textures.free(id); + } + + pub fn create_texture_view_id(&mut self, backend: Backend) -> TextureViewId { + self.select(backend).texture_views.alloc(backend) + } + + pub fn kill_texture_view_id(&mut self, id: TextureViewId) { + self.select(id.backend()).texture_views.free(id); + } + + pub fn create_render_bundle_id(&mut self, backend: Backend) -> RenderBundleId { + self.select(backend).render_bundles.alloc(backend) + } + + pub fn kill_render_bundle_id(&mut self, id: RenderBundleId) { + self.select(id.backend()).render_bundles.free(id); + } +} diff --git a/components/script/dom/imagebitmap.rs b/components/script/dom/imagebitmap.rs new file mode 100644 index 00000000000..72b4871490f --- /dev/null +++ b/components/script/dom/imagebitmap.rs @@ -0,0 +1,69 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; + +use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::ImageBitmapMethods; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; + +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use dom_struct::dom_struct; + +use std::cell::Cell; +use std::vec::Vec; + +#[dom_struct] +pub struct ImageBitmap { + reflector_: Reflector, + width: u32, + height: u32, + bitmap_data: DomRefCell<Vec<u8>>, + origin_clean: Cell<bool>, +} + +impl ImageBitmap { + fn new_inherited(width_arg: u32, height_arg: u32) -> ImageBitmap { + ImageBitmap { + reflector_: Reflector::new(), + width: width_arg, + height: height_arg, + bitmap_data: DomRefCell::new(vec![]), + origin_clean: Cell::new(true), + } + } + + #[allow(dead_code)] + pub fn new(global: &GlobalScope, width: u32, height: u32) -> 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)) + } + + pub fn set_bitmap_data(&self, data: Vec<u8>) { + *self.bitmap_data.borrow_mut() = data; + } + + pub fn set_origin_clean(&self, origin_is_clean: bool) { + self.origin_clean.set(origin_is_clean); + } +} + +impl ImageBitmapMethods for ImageBitmap { + // https://html.spec.whatwg.org/multipage/#dom-imagebitmap-height + fn Height(&self) -> u32 { + //to do: add a condition for checking detached internal slot + //and return 0 if set to true + self.height + } + + // https://html.spec.whatwg.org/multipage/#dom-imagebitmap-width + fn Width(&self) -> u32 { + //to do: add a condition to check detached internal slot + ////and return 0 if set to true + self.width + } +} diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index 220ead847e8..d8574a9b666 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -1,21 +1,23 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use core::nonzero::NonZero; -use dom::bindings::codegen::Bindings::ImageDataBinding; -use dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods; -use dom::bindings::error::{Fallible, Error}; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use euclid::size::Size2D; -use js::jsapi::{Heap, JSContext, JSObject}; +use euclid::default::{Rect, Size2D}; +use ipc_channel::ipc::IpcSharedMemory; +use js::jsapi::{Heap, JSObject}; use js::rust::Runtime; -use js::typedarray::{Uint8ClampedArray, CreateWith}; +use js::typedarray::{CreateWith, Uint8ClampedArray}; +use std::borrow::Cow; use std::default::Default; use std::ptr; +use std::ptr::NonNull; use std::vec::Vec; #[dom_struct] @@ -23,126 +25,147 @@ pub struct ImageData { reflector_: Reflector, width: u32, height: u32, + #[ignore_malloc_size_of = "mozjs"] data: Heap<*mut JSObject>, } impl ImageData { #[allow(unsafe_code)] - pub fn new(global: &GlobalScope, - width: u32, - height: u32, - mut data: Option<Vec<u8>>) - -> Fallible<Root<ImageData>> { + pub fn new( + global: &GlobalScope, + width: u32, + height: u32, + mut data: Option<Vec<u8>>, + ) -> Fallible<DomRoot<ImageData>> { let len = width * height * 4; unsafe { let cx = global.get_cx(); - rooted!(in (cx) let mut js_object = ptr::null_mut()); - let data = match data { - Some(ref mut d) => { - d.resize(len as usize, 0); - CreateWith::Slice(&d[..]) - }, - None => CreateWith::Length(len), - }; - Uint8ClampedArray::create(cx, data, js_object.handle_mut()).unwrap(); - Self::new_with_jsobject(global, width, Some(height), Some(js_object.get())) + rooted!(in (*cx) let mut js_object = ptr::null_mut::<JSObject>()); + if let Some(ref mut d) = data { + d.resize(len as usize, 0); + let data = CreateWith::Slice(&d[..]); + Uint8ClampedArray::create(*cx, data, js_object.handle_mut()).unwrap(); + Self::new_with_jsobject(global, width, Some(height), js_object.get()) + } else { + Self::new_without_jsobject(global, width, height) + } } } #[allow(unsafe_code)] - unsafe fn new_with_jsobject(global: &GlobalScope, - width: u32, - mut opt_height: Option<u32>, - opt_jsobject: Option<*mut JSObject>) - -> Fallible<Root<ImageData>> { - assert!(opt_jsobject.is_some() || opt_height.is_some()); - - if width == 0 { + unsafe fn new_with_jsobject( + global: &GlobalScope, + width: u32, + opt_height: Option<u32>, + jsobject: *mut JSObject, + ) -> Fallible<DomRoot<ImageData>> { + // checking jsobject type + let cx = global.get_cx(); + typedarray!(in(*cx) let array_res: Uint8ClampedArray = jsobject); + let array = array_res.map_err(|_| { + Error::Type("Argument to Image data is not an Uint8ClampedArray".to_owned()) + })?; + + let byte_len = array.as_slice().len() as u32; + if byte_len == 0 || byte_len % 4 != 0 { + return Err(Error::InvalidState); + } + + let len = byte_len / 4; + if width == 0 || len % width != 0 { return Err(Error::IndexSize); } - // checking jsobject type and verifying (height * width * 4 == jsobject.byte_len()) - if let Some(jsobject) = opt_jsobject { - let cx = global.get_cx(); - typedarray!(in(cx) let array_res: Uint8ClampedArray = jsobject); - let mut array = try!(array_res - .map_err(|_| Error::Type("Argument to Image data is not an Uint8ClampedArray".to_owned()))); + let height = len / width; + if opt_height.map_or(false, |x| height != x) { + return Err(Error::IndexSize); + } - let byte_len = array.as_slice().len() as u32; - if byte_len % 4 != 0 { - return Err(Error::InvalidState); - } + let imagedata = Box::new(ImageData { + reflector_: Reflector::new(), + width: width, + height: height, + data: Heap::default(), + }); - let len = byte_len / 4; - if width == 0 || len % width != 0 { - return Err(Error::IndexSize); - } + (*imagedata).data.set(jsobject); - let height = len / width; - if opt_height.map_or(false, |x| height != x) { - return Err(Error::IndexSize); - } else { - opt_height = Some(height); - } - } + Ok(reflect_dom_object(imagedata, global)) + } - let height = opt_height.unwrap(); - if height == 0 { + #[allow(unsafe_code)] + unsafe fn new_without_jsobject( + global: &GlobalScope, + width: u32, + height: u32, + ) -> Fallible<DomRoot<ImageData>> { + if width == 0 || height == 0 { return Err(Error::IndexSize); } - let imagedata = box ImageData { + let imagedata = Box::new(ImageData { reflector_: Reflector::new(), width: width, height: height, data: Heap::default(), - }; + }); - if let Some(jsobject) = opt_jsobject { - (*imagedata).data.set(jsobject); - } else { - let len = width * height * 4; - let cx = global.get_cx(); - rooted!(in (cx) let mut array = ptr::null_mut()); - Uint8ClampedArray::create(cx, CreateWith::Length(len), array.handle_mut()).unwrap(); - (*imagedata).data.set(array.get()); - } + let len = width * height * 4; + let cx = global.get_cx(); + rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); + Uint8ClampedArray::create(*cx, CreateWith::Length(len as usize), array.handle_mut()) + .unwrap(); + (*imagedata).data.set(array.get()); - Ok(reflect_dom_object(imagedata, global, ImageDataBinding::Wrap)) + Ok(reflect_dom_object(imagedata, global)) } - // https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-3 - #[allow(unsafe_code)] - pub fn Constructor(global: &GlobalScope, width: u32, height: u32) -> Fallible<Root<Self>> { - unsafe { Self::new_with_jsobject(global, width, Some(height), None) } + #[allow(unsafe_code, non_snake_case)] + pub fn Constructor(global: &GlobalScope, width: u32, height: u32) -> Fallible<DomRoot<Self>> { + unsafe { Self::new_without_jsobject(global, width, height) } } // https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-4 + #[allow(unsafe_code, unused_variables, non_snake_case)] + pub unsafe fn Constructor_( + cx: JSContext, + global: &GlobalScope, + jsobject: *mut JSObject, + width: u32, + opt_height: Option<u32>, + ) -> Fallible<DomRoot<Self>> { + Self::new_with_jsobject(global, width, opt_height, jsobject) + } + + /// Nothing must change the array on the JS side while the slice is live. #[allow(unsafe_code)] - #[allow(unused_variables)] - pub unsafe fn Constructor_(cx: *mut JSContext, - global: &GlobalScope, - jsobject: *mut JSObject, - width: u32, - opt_height: Option<u32>) - -> Fallible<Root<Self>> { - Self::new_with_jsobject(global, width, opt_height, Some(jsobject)) + pub unsafe fn as_slice(&self) -> &[u8] { + assert!(!self.data.get().is_null()); + let cx = Runtime::get(); + assert!(!cx.is_null()); + typedarray!(in(cx) let array: Uint8ClampedArray = self.data.get()); + let array = array.as_ref().unwrap(); + // NOTE(nox): This is just as unsafe as `as_slice` itself even though we + // are extending the lifetime of the slice, because the data in + // this ImageData instance will never change. The method is thus unsafe + // because the array may be manipulated from JS while the reference + // is live. + let ptr = array.as_slice() as *const _; + &*ptr } #[allow(unsafe_code)] - pub fn get_data_array(&self) -> Vec<u8> { - unsafe { - assert!(!self.data.get().is_null()); - let cx = Runtime::get(); - assert!(!cx.is_null()); - typedarray!(in(cx) let array: Uint8ClampedArray = self.data.get()); - let vec = array.unwrap().as_slice().to_vec(); - vec - } + pub fn to_shared_memory(&self) -> IpcSharedMemory { + IpcSharedMemory::from_bytes(unsafe { self.as_slice() }) + } + + #[allow(unsafe_code)] + pub unsafe fn get_rect(&self, rect: Rect<u64>) -> Cow<[u8]> { + pixels::rgba8_get_rect(self.as_slice(), self.get_size().to_u64(), rect) } - pub fn get_size(&self) -> Size2D<i32> { - Size2D::new(self.Width() as i32, self.Height() as i32) + pub fn get_size(&self) -> Size2D<u32> { + Size2D::new(self.Width(), self.Height()) } } @@ -157,10 +180,8 @@ impl ImageDataMethods for ImageData { self.height } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-imagedata-data - unsafe fn Data(&self, _: *mut JSContext) -> NonZero<*mut JSObject> { - assert!(!self.data.get().is_null()); - NonZero::new(self.data.get()) + fn Data(&self, _: JSContext) -> NonNull<JSObject> { + NonNull::new(self.data.get()).expect("got a null pointer") } } diff --git a/components/script/dom/inputevent.rs b/components/script/dom/inputevent.rs new file mode 100644 index 00000000000..33a4ed3bbdb --- /dev/null +++ b/components/script/dom/inputevent.rs @@ -0,0 +1,81 @@ +/* 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 crate::dom::bindings::codegen::Bindings::InputEventBinding::{self, InputEventMethods}; +use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventBinding::UIEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::uievent::UIEvent; +use crate::dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct InputEvent { + uievent: UIEvent, + data: Option<DOMString>, + is_composing: bool, +} + +impl InputEvent { + pub fn new( + window: &Window, + type_: DOMString, + can_bubble: bool, + cancelable: bool, + view: Option<&Window>, + detail: i32, + data: Option<DOMString>, + is_composing: bool, + ) -> DomRoot<InputEvent> { + let ev = reflect_dom_object( + Box::new(InputEvent { + uievent: UIEvent::new_inherited(), + data: data, + is_composing: is_composing, + }), + window, + ); + ev.uievent + .InitUIEvent(type_, can_bubble, cancelable, view, detail); + ev + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &InputEventBinding::InputEventInit, + ) -> Fallible<DomRoot<InputEvent>> { + let event = InputEvent::new( + window, + type_, + init.parent.parent.bubbles, + init.parent.parent.cancelable, + init.parent.view.as_deref(), + init.parent.detail, + init.data.clone(), + init.isComposing, + ); + Ok(event) + } +} + +impl InputEventMethods for InputEvent { + // https://w3c.github.io/uievents/#dom-inputevent-data + fn GetData(&self) -> Option<DOMString> { + self.data.clone() + } + + // https://w3c.github.io/uievents/#dom-inputevent-iscomposing + fn IsComposing(&self) -> bool { + self.is_composing + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.uievent.IsTrusted() + } +} diff --git a/components/script/dom/keyboardevent.rs b/components/script/dom/keyboardevent.rs index 33b044085a2..12668a6c214 100644 --- a/components/script/dom/keyboardevent.rs +++ b/components/script/dom/keyboardevent.rs @@ -1,788 +1,167 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::KeyboardEventBinding; -use dom::bindings::codegen::Bindings::KeyboardEventBinding::{KeyboardEventConstants, KeyboardEventMethods}; -use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{Root, RootedReference}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::uievent::UIEvent; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::KeyboardEventBinding; +use crate::dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods; +use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::uievent::UIEvent; +use crate::dom::window::Window; use dom_struct::dom_struct; -use msg::constellation_msg; -use msg::constellation_msg::{Key, KeyModifiers}; -use std::borrow::Cow; +use keyboard_types::{Key, Modifiers}; use std::cell::Cell; unsafe_no_jsmanaged_fields!(Key); +unsafe_no_jsmanaged_fields!(Modifiers); #[dom_struct] pub struct KeyboardEvent { uievent: UIEvent, - key: Cell<Option<Key>>, - key_string: DOMRefCell<DOMString>, - code: DOMRefCell<DOMString>, + key: DomRefCell<DOMString>, + typed_key: DomRefCell<Key>, + code: DomRefCell<DOMString>, location: Cell<u32>, - ctrl: Cell<bool>, - alt: Cell<bool>, - shift: Cell<bool>, - meta: Cell<bool>, + modifiers: Cell<Modifiers>, repeat: Cell<bool>, is_composing: Cell<bool>, - char_code: Cell<Option<u32>>, + char_code: Cell<u32>, key_code: Cell<u32>, - printable: Cell<Option<char>>, } impl KeyboardEvent { fn new_inherited() -> KeyboardEvent { KeyboardEvent { uievent: UIEvent::new_inherited(), - key: Cell::new(None), - key_string: DOMRefCell::new(DOMString::new()), - code: DOMRefCell::new(DOMString::new()), + key: DomRefCell::new(DOMString::new()), + typed_key: DomRefCell::new(Key::Unidentified), + code: DomRefCell::new(DOMString::new()), location: Cell::new(0), - ctrl: Cell::new(false), - alt: Cell::new(false), - shift: Cell::new(false), - meta: Cell::new(false), + modifiers: Cell::new(Modifiers::empty()), repeat: Cell::new(false), is_composing: Cell::new(false), - char_code: Cell::new(None), + char_code: Cell::new(0), key_code: Cell::new(0), - printable: Cell::new(None), } } - pub fn new_uninitialized(window: &Window) -> Root<KeyboardEvent> { - reflect_dom_object(box KeyboardEvent::new_inherited(), - window, - KeyboardEventBinding::Wrap) - } - - pub fn new(window: &Window, - type_: DOMString, - can_bubble: bool, - cancelable: bool, - view: Option<&Window>, - _detail: i32, - ch: Option<char>, - key: Option<Key>, - key_string: DOMString, - code: DOMString, - location: u32, - repeat: bool, - is_composing: bool, - ctrl_key: bool, - alt_key: bool, - shift_key: bool, - meta_key: bool, - char_code: Option<u32>, - key_code: u32) -> Root<KeyboardEvent> { + pub fn new_uninitialized(window: &Window) -> DomRoot<KeyboardEvent> { + reflect_dom_object(Box::new(KeyboardEvent::new_inherited()), window) + } + + pub fn new( + window: &Window, + type_: DOMString, + can_bubble: bool, + cancelable: bool, + view: Option<&Window>, + _detail: i32, + key: Key, + code: DOMString, + location: u32, + repeat: bool, + is_composing: bool, + modifiers: Modifiers, + char_code: u32, + key_code: u32, + ) -> DomRoot<KeyboardEvent> { let ev = KeyboardEvent::new_uninitialized(window); - ev.InitKeyboardEvent(type_, can_bubble, cancelable, view, key_string, location, - DOMString::new(), repeat, DOMString::new()); - ev.key.set(key); + ev.InitKeyboardEvent( + type_, + can_bubble, + cancelable, + view, + DOMString::from(key.to_string()), + location, + DOMString::new(), + repeat, + DOMString::new(), + ); + *ev.typed_key.borrow_mut() = key; *ev.code.borrow_mut() = code; - ev.ctrl.set(ctrl_key); - ev.alt.set(alt_key); - ev.shift.set(shift_key); - ev.meta.set(meta_key); + ev.modifiers.set(modifiers); + ev.is_composing.set(is_composing); ev.char_code.set(char_code); - ev.printable.set(ch); ev.key_code.set(key_code); - ev.is_composing.set(is_composing); ev } - pub fn Constructor(window: &Window, - type_: DOMString, - init: &KeyboardEventBinding::KeyboardEventInit) -> Fallible<Root<KeyboardEvent>> { - let event = KeyboardEvent::new(window, - type_, - init.parent.parent.parent.bubbles, - init.parent.parent.parent.cancelable, - init.parent.parent.view.r(), - init.parent.parent.detail, - None, - key_from_string(&init.key, init.location), - init.key.clone(), init.code.clone(), init.location, - init.repeat, init.isComposing, init.parent.ctrlKey, - init.parent.altKey, init.parent.shiftKey, init.parent.metaKey, - None, 0); + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &KeyboardEventBinding::KeyboardEventInit, + ) -> Fallible<DomRoot<KeyboardEvent>> { + let mut modifiers = Modifiers::empty(); + modifiers.set(Modifiers::CONTROL, init.parent.ctrlKey); + modifiers.set(Modifiers::ALT, init.parent.altKey); + modifiers.set(Modifiers::SHIFT, init.parent.shiftKey); + modifiers.set(Modifiers::META, init.parent.metaKey); + let event = KeyboardEvent::new( + window, + type_, + init.parent.parent.parent.bubbles, + init.parent.parent.parent.cancelable, + init.parent.parent.view.as_deref(), + init.parent.parent.detail, + Key::Unidentified, + init.code.clone(), + init.location, + init.repeat, + init.isComposing, + modifiers, + 0, + 0, + ); + *event.key.borrow_mut() = init.key.clone(); Ok(event) } - - pub fn key_properties(ch: Option<char>, key: Key, mods: KeyModifiers) - -> KeyEventProperties { - KeyEventProperties { - key_string: key_value(ch, key, mods), - code: code_value(key), - location: key_location(key), - char_code: ch.map(|ch| ch as u32), - key_code: key_keycode(key), - } - } } - impl KeyboardEvent { - pub fn printable(&self) -> Option<char> { - self.printable.get() - } - - pub fn get_key(&self) -> Option<Key> { - self.key.get().clone() - } - - pub fn get_key_modifiers(&self) -> KeyModifiers { - let mut result = KeyModifiers::empty(); - if self.shift.get() { - result = result | constellation_msg::SHIFT; - } - if self.ctrl.get() { - result = result | constellation_msg::CONTROL; - } - if self.alt.get() { - result = result | constellation_msg::ALT; - } - if self.meta.get() { - result = result | constellation_msg::SUPER; - } - result - } -} - -// https://w3c.github.io/uievents-key/#key-value-tables -pub fn key_value(ch: Option<char>, key: Key, mods: KeyModifiers) -> Cow<'static, str> { - if let Some(ch) = ch { - return Cow::from(format!("{}", ch)); + pub fn key(&self) -> Key { + self.typed_key.borrow().clone() } - let shift = mods.contains(constellation_msg::SHIFT); - Cow::from(match key { - Key::Space => " ", - Key::Apostrophe if shift => "\"", - Key::Apostrophe => "'", - Key::Comma if shift => "<", - Key::Comma => ",", - Key::Minus if shift => "_", - Key::Minus => "-", - Key::Period if shift => ">", - Key::Period => ".", - Key::Slash if shift => "?", - Key::Slash => "/", - Key::GraveAccent if shift => "~", - Key::GraveAccent => "`", - Key::Num0 if shift => ")", - Key::Num0 => "0", - Key::Num1 if shift => "!", - Key::Num1 => "1", - Key::Num2 if shift => "@", - Key::Num2 => "2", - Key::Num3 if shift => "#", - Key::Num3 => "3", - Key::Num4 if shift => "$", - Key::Num4 => "4", - Key::Num5 if shift => "%", - Key::Num5 => "5", - Key::Num6 if shift => "^", - Key::Num6 => "6", - Key::Num7 if shift => "&", - Key::Num7 => "7", - Key::Num8 if shift => "*", - Key::Num8 => "8", - Key::Num9 if shift => "(", - Key::Num9 => "9", - Key::Semicolon if shift => ":", - Key::Semicolon => ";", - Key::Equal if shift => "+", - Key::Equal => "=", - Key::A if shift => "A", - Key::A => "a", - Key::B if shift => "B", - Key::B => "b", - Key::C if shift => "C", - Key::C => "c", - Key::D if shift => "D", - Key::D => "d", - Key::E if shift => "E", - Key::E => "e", - Key::F if shift => "F", - Key::F => "f", - Key::G if shift => "G", - Key::G => "g", - Key::H if shift => "H", - Key::H => "h", - Key::I if shift => "I", - Key::I => "i", - Key::J if shift => "J", - Key::J => "j", - Key::K if shift => "K", - Key::K => "k", - Key::L if shift => "L", - Key::L => "l", - Key::M if shift => "M", - Key::M => "m", - Key::N if shift => "N", - Key::N => "n", - Key::O if shift => "O", - Key::O => "o", - Key::P if shift => "P", - Key::P => "p", - Key::Q if shift => "Q", - Key::Q => "q", - Key::R if shift => "R", - Key::R => "r", - Key::S if shift => "S", - Key::S => "s", - Key::T if shift => "T", - Key::T => "t", - Key::U if shift => "U", - Key::U => "u", - Key::V if shift => "V", - Key::V => "v", - Key::W if shift => "W", - Key::W => "w", - Key::X if shift => "X", - Key::X => "x", - Key::Y if shift => "Y", - Key::Y => "y", - Key::Z if shift => "Z", - Key::Z => "z", - Key::LeftBracket if shift => "{", - Key::LeftBracket => "[", - Key::Backslash if shift => "|", - Key::Backslash => "\\", - Key::RightBracket if shift => "}", - Key::RightBracket => "]", - Key::World1 => "Unidentified", - Key::World2 => "Unidentified", - Key::Escape => "Escape", - Key::Enter => "Enter", - Key::Tab => "Tab", - Key::Backspace => "Backspace", - Key::Insert => "Insert", - Key::Delete => "Delete", - Key::Right => "ArrowRight", - Key::Left => "ArrowLeft", - Key::Down => "ArrowDown", - Key::Up => "ArrowUp", - Key::PageUp => "PageUp", - Key::PageDown => "PageDown", - Key::Home => "Home", - Key::End => "End", - Key::CapsLock => "CapsLock", - Key::ScrollLock => "ScrollLock", - Key::NumLock => "NumLock", - Key::PrintScreen => "PrintScreen", - Key::Pause => "Pause", - Key::F1 => "F1", - Key::F2 => "F2", - Key::F3 => "F3", - Key::F4 => "F4", - Key::F5 => "F5", - Key::F6 => "F6", - Key::F7 => "F7", - Key::F8 => "F8", - Key::F9 => "F9", - Key::F10 => "F10", - Key::F11 => "F11", - Key::F12 => "F12", - Key::F13 => "F13", - Key::F14 => "F14", - Key::F15 => "F15", - Key::F16 => "F16", - Key::F17 => "F17", - Key::F18 => "F18", - Key::F19 => "F19", - Key::F20 => "F20", - Key::F21 => "F21", - Key::F22 => "F22", - Key::F23 => "F23", - Key::F24 => "F24", - Key::F25 => "F25", - Key::Kp0 => "0", - Key::Kp1 => "1", - Key::Kp2 => "2", - Key::Kp3 => "3", - Key::Kp4 => "4", - Key::Kp5 => "5", - Key::Kp6 => "6", - Key::Kp7 => "7", - Key::Kp8 => "8", - Key::Kp9 => "9", - Key::KpDecimal => ".", - Key::KpDivide => "/", - Key::KpMultiply => "*", - Key::KpSubtract => "-", - Key::KpAdd => "+", - Key::KpEnter => "Enter", - Key::KpEqual => "=", - Key::LeftShift => "Shift", - Key::LeftControl => "Control", - Key::LeftAlt => "Alt", - Key::LeftSuper => "Super", - Key::RightShift => "Shift", - Key::RightControl => "Control", - Key::RightAlt => "Alt", - Key::RightSuper => "Super", - Key::Menu => "ContextMenu", - Key::NavigateForward => "BrowserForward", - Key::NavigateBackward => "BrowserBack", - }) -} - -fn key_from_string(key_string: &str, location: u32) -> Option<Key> { - match key_string { - " " => Some(Key::Space), - "\"" => Some(Key::Apostrophe), - "'" => Some(Key::Apostrophe), - "<" => Some(Key::Comma), - "," => Some(Key::Comma), - "_" => Some(Key::Minus), - "-" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Minus), - ">" => Some(Key::Period), - "." if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Period), - "?" => Some(Key::Slash), - "/" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Slash), - "~" => Some(Key::GraveAccent), - "`" => Some(Key::GraveAccent), - ")" => Some(Key::Num0), - "0" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num0), - "!" => Some(Key::Num1), - "1" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num1), - "@" => Some(Key::Num2), - "2" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num2), - "#" => Some(Key::Num3), - "3" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num3), - "$" => Some(Key::Num4), - "4" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num4), - "%" => Some(Key::Num5), - "5" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num5), - "^" => Some(Key::Num6), - "6" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num6), - "&" => Some(Key::Num7), - "7" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num7), - "*" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num8), - "8" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num8), - "(" => Some(Key::Num9), - "9" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Num9), - ":" => Some(Key::Semicolon), - ";" => Some(Key::Semicolon), - "+" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Equal), - "=" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Equal), - "A" => Some(Key::A), - "a" => Some(Key::A), - "B" => Some(Key::B), - "b" => Some(Key::B), - "C" => Some(Key::C), - "c" => Some(Key::C), - "D" => Some(Key::D), - "d" => Some(Key::D), - "E" => Some(Key::E), - "e" => Some(Key::E), - "F" => Some(Key::F), - "f" => Some(Key::F), - "G" => Some(Key::G), - "g" => Some(Key::G), - "H" => Some(Key::H), - "h" => Some(Key::H), - "I" => Some(Key::I), - "i" => Some(Key::I), - "J" => Some(Key::J), - "j" => Some(Key::J), - "K" => Some(Key::K), - "k" => Some(Key::K), - "L" => Some(Key::L), - "l" => Some(Key::L), - "M" => Some(Key::M), - "m" => Some(Key::M), - "N" => Some(Key::N), - "n" => Some(Key::N), - "O" => Some(Key::O), - "o" => Some(Key::O), - "P" => Some(Key::P), - "p" => Some(Key::P), - "Q" => Some(Key::Q), - "q" => Some(Key::Q), - "R" => Some(Key::R), - "r" => Some(Key::R), - "S" => Some(Key::S), - "s" => Some(Key::S), - "T" => Some(Key::T), - "t" => Some(Key::T), - "U" => Some(Key::U), - "u" => Some(Key::U), - "V" => Some(Key::V), - "v" => Some(Key::V), - "W" => Some(Key::W), - "w" => Some(Key::W), - "X" => Some(Key::X), - "x" => Some(Key::X), - "Y" => Some(Key::Y), - "y" => Some(Key::Y), - "Z" => Some(Key::Z), - "z" => Some(Key::Z), - "{" => Some(Key::LeftBracket), - "[" => Some(Key::LeftBracket), - "|" => Some(Key::Backslash), - "\\" => Some(Key::Backslash), - "}" => Some(Key::RightBracket), - "]" => Some(Key::RightBracket), - "Escape" => Some(Key::Escape), - "Enter" if location == KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD => Some(Key::Enter), - "Tab" => Some(Key::Tab), - "Backspace" => Some(Key::Backspace), - "Insert" => Some(Key::Insert), - "Delete" => Some(Key::Delete), - "ArrowRight" => Some(Key::Right), - "ArrowLeft" => Some(Key::Left), - "ArrowDown" => Some(Key::Down), - "ArrowUp" => Some(Key::Up), - "PageUp" => Some(Key::PageUp), - "PageDown" => Some(Key::PageDown), - "Home" => Some(Key::Home), - "End" => Some(Key::End), - "CapsLock" => Some(Key::CapsLock), - "ScrollLock" => Some(Key::ScrollLock), - "NumLock" => Some(Key::NumLock), - "PrintScreen" => Some(Key::PrintScreen), - "Pause" => Some(Key::Pause), - "F1" => Some(Key::F1), - "F2" => Some(Key::F2), - "F3" => Some(Key::F3), - "F4" => Some(Key::F4), - "F5" => Some(Key::F5), - "F6" => Some(Key::F6), - "F7" => Some(Key::F7), - "F8" => Some(Key::F8), - "F9" => Some(Key::F9), - "F10" => Some(Key::F10), - "F11" => Some(Key::F11), - "F12" => Some(Key::F12), - "F13" => Some(Key::F13), - "F14" => Some(Key::F14), - "F15" => Some(Key::F15), - "F16" => Some(Key::F16), - "F17" => Some(Key::F17), - "F18" => Some(Key::F18), - "F19" => Some(Key::F19), - "F20" => Some(Key::F20), - "F21" => Some(Key::F21), - "F22" => Some(Key::F22), - "F23" => Some(Key::F23), - "F24" => Some(Key::F24), - "F25" => Some(Key::F25), - "0" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp0), - "1" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp1), - "2" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp2), - "3" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp3), - "4" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp4), - "5" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp5), - "6" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp6), - "7" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp7), - "8" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp8), - "9" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::Kp9), - "." if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::KpDecimal), - "/" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::KpDivide), - "*" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::KpMultiply), - "-" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::KpSubtract), - "+" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::KpAdd), - "Enter" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::KpEnter), - "=" if location == KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD => Some(Key::KpEqual), - "Shift" if location == KeyboardEventConstants::DOM_KEY_LOCATION_LEFT => Some(Key::LeftShift), - "Control" if location == KeyboardEventConstants::DOM_KEY_LOCATION_LEFT => Some(Key::LeftControl), - "Alt" if location == KeyboardEventConstants::DOM_KEY_LOCATION_LEFT => Some(Key::LeftAlt), - "Super" if location == KeyboardEventConstants::DOM_KEY_LOCATION_LEFT => Some(Key::LeftSuper), - "Shift" if location == KeyboardEventConstants::DOM_KEY_LOCATION_RIGHT => Some(Key::RightShift), - "Control" if location == KeyboardEventConstants::DOM_KEY_LOCATION_RIGHT => Some(Key::RightControl), - "Alt" if location == KeyboardEventConstants::DOM_KEY_LOCATION_RIGHT => Some(Key::RightAlt), - "Super" if location == KeyboardEventConstants::DOM_KEY_LOCATION_RIGHT => Some(Key::RightSuper), - "ContextMenu" => Some(Key::Menu), - "BrowserForward" => Some(Key::NavigateForward), - "BrowserBack" => Some(Key::NavigateBackward), - _ => None - } -} - -// https://w3c.github.io/uievents-code/#code-value-tables -fn code_value(key: Key) -> &'static str { - match key { - Key::Space => "Space", - Key::Apostrophe => "Quote", - Key::Comma => "Comma", - Key::Minus => "Minus", - Key::Period => "Period", - Key::Slash => "Slash", - Key::GraveAccent => "Backquote", - Key::Num0 => "Digit0", - Key::Num1 => "Digit1", - Key::Num2 => "Digit2", - Key::Num3 => "Digit3", - Key::Num4 => "Digit4", - Key::Num5 => "Digit5", - Key::Num6 => "Digit6", - Key::Num7 => "Digit7", - Key::Num8 => "Digit8", - Key::Num9 => "Digit9", - Key::Semicolon => "Semicolon", - Key::Equal => "Equal", - Key::A => "KeyA", - Key::B => "KeyB", - Key::C => "KeyC", - Key::D => "KeyD", - Key::E => "KeyE", - Key::F => "KeyF", - Key::G => "KeyG", - Key::H => "KeyH", - Key::I => "KeyI", - Key::J => "KeyJ", - Key::K => "KeyK", - Key::L => "KeyL", - Key::M => "KeyM", - Key::N => "KeyN", - Key::O => "KeyO", - Key::P => "KeyP", - Key::Q => "KeyQ", - Key::R => "KeyR", - Key::S => "KeyS", - Key::T => "KeyT", - Key::U => "KeyU", - Key::V => "KeyV", - Key::W => "KeyW", - Key::X => "KeyX", - Key::Y => "KeyY", - Key::Z => "KeyZ", - Key::LeftBracket => "BracketLeft", - Key::Backslash => "Backslash", - Key::RightBracket => "BracketRight", - - Key::World1 | - Key::World2 => panic!("unknown char code for {:?}", key), - - Key::Escape => "Escape", - Key::Enter => "Enter", - Key::Tab => "Tab", - Key::Backspace => "Backspace", - Key::Insert => "Insert", - Key::Delete => "Delete", - Key::Right => "ArrowRight", - Key::Left => "ArrowLeft", - Key::Down => "ArrowDown", - Key::Up => "ArrowUp", - Key::PageUp => "PageUp", - Key::PageDown => "PageDown", - Key::Home => "Home", - Key::End => "End", - Key::CapsLock => "CapsLock", - Key::ScrollLock => "ScrollLock", - Key::NumLock => "NumLock", - Key::PrintScreen => "PrintScreen", - Key::Pause => "Pause", - Key::F1 => "F1", - Key::F2 => "F2", - Key::F3 => "F3", - Key::F4 => "F4", - Key::F5 => "F5", - Key::F6 => "F6", - Key::F7 => "F7", - Key::F8 => "F8", - Key::F9 => "F9", - Key::F10 => "F10", - Key::F11 => "F11", - Key::F12 => "F12", - Key::F13 => "F13", - Key::F14 => "F14", - Key::F15 => "F15", - Key::F16 => "F16", - Key::F17 => "F17", - Key::F18 => "F18", - Key::F19 => "F19", - Key::F20 => "F20", - Key::F21 => "F21", - Key::F22 => "F22", - Key::F23 => "F23", - Key::F24 => "F24", - Key::F25 => "F25", - Key::Kp0 => "Numpad0", - Key::Kp1 => "Numpad1", - Key::Kp2 => "Numpad2", - Key::Kp3 => "Numpad3", - Key::Kp4 => "Numpad4", - Key::Kp5 => "Numpad5", - Key::Kp6 => "Numpad6", - Key::Kp7 => "Numpad7", - Key::Kp8 => "Numpad8", - Key::Kp9 => "Numpad9", - Key::KpDecimal => "NumpadDecimal", - Key::KpDivide => "NumpadDivide", - Key::KpMultiply => "NumpadMultiply", - Key::KpSubtract => "NumpadSubtract", - Key::KpAdd => "NumpadAdd", - Key::KpEnter => "NumpadEnter", - Key::KpEqual => "NumpadEqual", - Key::LeftShift | Key::RightShift => "Shift", - Key::LeftControl | Key::RightControl => "Control", - Key::LeftAlt | Key::RightAlt => "Alt", - Key::LeftSuper | Key::RightSuper => "Super", - Key::Menu => "ContextMenu", - - Key::NavigateForward => "BrowserForward", - Key::NavigateBackward => "BrowserBackward", - } -} - -fn key_location(key: Key) -> u32 { - match key { - Key::Kp0 | Key::Kp1 | Key::Kp2 | - Key::Kp3 | Key::Kp4 | Key::Kp5 | - Key::Kp6 | Key::Kp7 | Key::Kp8 | - Key::Kp9 | Key::KpDecimal | - Key::KpDivide | Key::KpMultiply | - Key::KpSubtract | Key::KpAdd | - Key::KpEnter | Key::KpEqual => - KeyboardEventConstants::DOM_KEY_LOCATION_NUMPAD, - - Key::LeftShift | Key::LeftAlt | - Key::LeftControl | Key::LeftSuper => - KeyboardEventConstants::DOM_KEY_LOCATION_LEFT, - - Key::RightShift | Key::RightAlt | - Key::RightControl | Key::RightSuper => - KeyboardEventConstants::DOM_KEY_LOCATION_RIGHT, - - _ => KeyboardEventConstants::DOM_KEY_LOCATION_STANDARD, - } -} - -// https://w3c.github.io/uievents/#legacy-key-models -fn key_keycode(key: Key) -> u32 { - match key { - // https://w3c.github.io/uievents/#legacy-key-models - Key::Backspace => 8, - Key::Tab => 9, - Key::Enter => 13, - Key::LeftShift | Key::RightShift => 16, - Key::LeftControl | Key::RightControl => 17, - Key::LeftAlt | Key::RightAlt => 18, - Key::CapsLock => 20, - Key::Escape => 27, - Key::Space => 32, - Key::PageUp => 33, - Key::PageDown => 34, - Key::End => 35, - Key::Home => 36, - Key::Left => 37, - Key::Up => 38, - Key::Right => 39, - Key::Down => 40, - Key::Delete => 46, - - // https://w3c.github.io/uievents/#optionally-fixed-virtual-key-codes - Key::Semicolon => 186, - Key::Equal => 187, - Key::Comma => 188, - Key::Minus => 189, - Key::Period => 190, - Key::Slash => 191, - Key::LeftBracket => 219, - Key::Backslash => 220, - Key::RightBracket => 221, - Key::Apostrophe => 222, - - //§ B.2.1.3 - Key::Num0 | - Key::Num1 | - Key::Num2 | - Key::Num3 | - Key::Num4 | - Key::Num5 | - Key::Num6 | - Key::Num7 | - Key::Num8 | - Key::Num9 => key as u32 - Key::Num0 as u32 + '0' as u32, - - //§ B.2.1.4 - Key::A | - Key::B | - Key::C | - Key::D | - Key::E | - Key::F | - Key::G | - Key::H | - Key::I | - Key::J | - Key::K | - Key::L | - Key::M | - Key::N | - Key::O | - Key::P | - Key::Q | - Key::R | - Key::S | - Key::T | - Key::U | - Key::V | - Key::W | - Key::X | - Key::Y | - Key::Z => key as u32 - Key::A as u32 + 'A' as u32, - - //§ B.2.1.8 - _ => 0 - } -} - -#[derive(HeapSizeOf)] -pub struct KeyEventProperties { - pub key_string: Cow<'static, str>, - pub code: &'static str, - pub location: u32, - pub char_code: Option<u32>, - pub key_code: u32, -} - -impl KeyEventProperties { - pub fn is_printable(&self) -> bool { - self.char_code.is_some() + pub fn modifiers(&self) -> Modifiers { + self.modifiers.get() } } impl KeyboardEventMethods for KeyboardEvent { // https://w3c.github.io/uievents/#widl-KeyboardEvent-initKeyboardEvent - fn InitKeyboardEvent(&self, - type_arg: DOMString, - can_bubble_arg: bool, - cancelable_arg: bool, - view_arg: Option<&Window>, - key_arg: DOMString, - location_arg: u32, - _modifiers_list_arg: DOMString, - repeat: bool, - _locale: DOMString) { + fn InitKeyboardEvent( + &self, + type_arg: DOMString, + can_bubble_arg: bool, + cancelable_arg: bool, + view_arg: Option<&Window>, + key_arg: DOMString, + location_arg: u32, + _modifiers_list_arg: DOMString, + repeat: bool, + _locale: DOMString, + ) { if self.upcast::<Event>().dispatching() { return; } self.upcast::<UIEvent>() .InitUIEvent(type_arg, can_bubble_arg, cancelable_arg, view_arg, 0); - *self.key_string.borrow_mut() = key_arg; + *self.key.borrow_mut() = key_arg; self.location.set(location_arg); self.repeat.set(repeat); } // https://w3c.github.io/uievents/#widl-KeyboardEvent-key fn Key(&self) -> DOMString { - self.key_string.borrow().clone() + self.key.borrow().clone() } // https://w3c.github.io/uievents/#widl-KeyboardEvent-code @@ -797,22 +176,22 @@ impl KeyboardEventMethods for KeyboardEvent { // https://w3c.github.io/uievents/#widl-KeyboardEvent-ctrlKey fn CtrlKey(&self) -> bool { - self.ctrl.get() + self.modifiers.get().contains(Modifiers::CONTROL) } // https://w3c.github.io/uievents/#widl-KeyboardEvent-shiftKey fn ShiftKey(&self) -> bool { - self.shift.get() + self.modifiers.get().contains(Modifiers::SHIFT) } // https://w3c.github.io/uievents/#widl-KeyboardEvent-altKey fn AltKey(&self) -> bool { - self.alt.get() + self.modifiers.get().contains(Modifiers::ALT) } // https://w3c.github.io/uievents/#widl-KeyboardEvent-metaKey fn MetaKey(&self) -> bool { - self.meta.get() + self.modifiers.get().contains(Modifiers::META) } // https://w3c.github.io/uievents/#widl-KeyboardEvent-repeat @@ -827,20 +206,26 @@ impl KeyboardEventMethods for KeyboardEvent { // https://w3c.github.io/uievents/#dom-keyboardevent-getmodifierstate fn GetModifierState(&self, key_arg: DOMString) -> bool { - match &*key_arg { - "Ctrl" => self.CtrlKey(), - "Alt" => self.AltKey(), - "Shift" => self.ShiftKey(), - "Meta" => self.MetaKey(), - "AltGraph" | "CapsLock" | "NumLock" | "ScrollLock" | "Accel" | - "Fn" | "FnLock" | "Hyper" | "OS" | "Symbol" | "SymbolLock" => false, //FIXME - _ => false, - } + self.modifiers.get().contains(match &*key_arg { + "Alt" => Modifiers::ALT, + "AltGraph" => Modifiers::ALT_GRAPH, + "CapsLock" => Modifiers::CAPS_LOCK, + "Control" => Modifiers::CONTROL, + "Fn" => Modifiers::FN, + "FnLock" => Modifiers::FN_LOCK, + "Meta" => Modifiers::META, + "NumLock" => Modifiers::NUM_LOCK, + "ScrollLock" => Modifiers::SCROLL_LOCK, + "Shift" => Modifiers::SHIFT, + "Symbol" => Modifiers::SYMBOL, + "SymbolLock" => Modifiers::SYMBOL_LOCK, + _ => return false, + }) } // https://w3c.github.io/uievents/#widl-KeyboardEvent-charCode fn CharCode(&self) -> u32 { - self.char_code.get().unwrap_or(0) + self.char_code.get() } // https://w3c.github.io/uievents/#widl-KeyboardEvent-keyCode @@ -850,7 +235,11 @@ impl KeyboardEventMethods for KeyboardEvent { // https://w3c.github.io/uievents/#widl-KeyboardEvent-which fn Which(&self) -> u32 { - self.char_code.get().unwrap_or(self.KeyCode()) + if self.char_code.get() != 0 { + self.char_code.get() + } else { + self.key_code.get() + } } // https://dom.spec.whatwg.org/#dom-event-istrusted diff --git a/components/script/dom/location.rs b/components/script/dom/location.rs index cac2322be82..42e3a5846f3 100644 --- a/components/script/dom/location.rs +++ b/components/script/dom/location.rs @@ -1,64 +1,90 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::LocationBinding; -use dom::bindings::codegen::Bindings::LocationBinding::LocationMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::globalscope::GlobalScope; -use dom::urlhelper::UrlHelper; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::LocationBinding::LocationMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::urlhelper::UrlHelper; +use crate::dom::window::Window; use dom_struct::dom_struct; +use net_traits::request::Referrer; +use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin}; use servo_url::{MutableOrigin, ServoUrl}; #[dom_struct] pub struct Location { reflector_: Reflector, - window: JS<Window>, + window: Dom<Window>, } impl Location { fn new_inherited(window: &Window) -> Location { Location { reflector_: Reflector::new(), - window: JS::from_ref(window) + window: Dom::from_ref(window), } } - pub fn new(window: &Window) -> Root<Location> { - reflect_dom_object(box Location::new_inherited(window), - window, - LocationBinding::Wrap) + pub fn new(window: &Window) -> DomRoot<Location> { + reflect_dom_object(Box::new(Location::new_inherited(window)), window) } - fn get_url(&self) -> ServoUrl { - self.window.get_url() + /// https://html.spec.whatwg.org/multipage/#location-object-navigate + fn navigate( + &self, + url: ServoUrl, + referrer: Referrer, + replacement_flag: HistoryEntryReplacement, + reload_triggered: bool, + ) { + let document = self.window.Document(); + let referrer_policy = document.get_referrer_policy(); + let pipeline_id = self.window.upcast::<GlobalScope>().pipeline_id(); + let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + url, + Some(pipeline_id), + referrer, + referrer_policy, + None, // Top navigation doesn't inherit secure context + ); + // TODO: rethrow exceptions, set exceptions enabled flag. + self.window + .load_url(replacement_flag, reload_triggered, load_data); } - fn set_url_component(&self, value: USVString, - setter: fn(&mut ServoUrl, USVString)) { - let mut url = self.window.get_url(); - setter(&mut url, value); - self.window.load_url(url, false, false, None); + fn get_url(&self) -> ServoUrl { + self.window.get_url() } fn check_same_origin_domain(&self) -> ErrorResult { - let entry_document = GlobalScope::entry().as_window().Document(); let this_document = self.window.Document(); - if entry_document.origin().same_origin_domain(this_document.origin()) { + if self + .entry_settings_object() + .origin() + .same_origin_domain(this_document.origin()) + { Ok(()) } else { Err(Error::Security) } } + fn entry_settings_object(&self) -> DomRoot<GlobalScope> { + GlobalScope::entry() + } + // https://html.spec.whatwg.org/multipage/#dom-location-reload pub fn reload_without_origin_check(&self) { - self.window.load_url(self.get_url(), true, true, None); + let url = self.get_url(); + let referrer = Referrer::ReferrerUrl(url.clone()); + self.navigate(url, referrer, HistoryEntryReplacement::Enabled, true); } #[allow(dead_code)] @@ -70,158 +96,311 @@ impl Location { impl LocationMethods for Location { // https://html.spec.whatwg.org/multipage/#dom-location-assign fn Assign(&self, url: USVString) -> ErrorResult { - try!(self.check_same_origin_domain()); - // TODO: per spec, we should use the _API base URL_ specified by the - // _entry settings object_. - let base_url = self.window.get_url(); - if let Ok(url) = base_url.join(&url.0) { - self.window.load_url(url, false, false, None); - Ok(()) - } else { - Err(Error::Syntax) + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Step 2: If this Location object's relevant Document's origin is not same + // origin-domain with the entry settings object's origin, then throw a + // "SecurityError" DOMException. + self.check_same_origin_domain()?; + // Step 3: Parse url relative to the entry settings object. If that failed, + // throw a "SyntaxError" DOMException. + let base_url = self.entry_settings_object().api_base_url(); + let url = match base_url.join(&url.0) { + Ok(url) => url, + Err(_) => return Err(Error::Syntax), + }; + // Step 4: Location-object navigate to the resulting URL record. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(url, referrer, HistoryEntryReplacement::Disabled, false); } + Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-location-reload fn Reload(&self) -> ErrorResult { - try!(self.check_same_origin_domain()); - self.window.load_url(self.get_url(), true, true, None); + self.check_same_origin_domain()?; + let url = self.get_url(); + let referrer = Referrer::ReferrerUrl(url.clone()); + self.navigate(url, referrer, HistoryEntryReplacement::Enabled, true); Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-location-replace fn Replace(&self, url: USVString) -> ErrorResult { - // Note: no call to self.check_same_origin_domain() - // TODO: per spec, we should use the _API base URL_ specified by the - // _entry settings object_. - let base_url = self.window.get_url(); - if let Ok(url) = base_url.join(&url.0) { - self.window.load_url(url, true, false, None); - Ok(()) - } else { - Err(Error::Syntax) + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Step 2: Parse url relative to the entry settings object. If that failed, + // throw a "SyntaxError" DOMException. + let base_url = self.entry_settings_object().api_base_url(); + let url = match base_url.join(&url.0) { + Ok(url) => url, + Err(_) => return Err(Error::Syntax), + }; + // Step 3: Location-object navigate to the resulting URL record with + // the replacement flag set. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(url, referrer, HistoryEntryReplacement::Enabled, false); } + Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-location-hash fn GetHash(&self) -> Fallible<USVString> { - try!(self.check_same_origin_domain()); + self.check_same_origin_domain()?; Ok(UrlHelper::Hash(&self.get_url())) } // https://html.spec.whatwg.org/multipage/#dom-location-hash - fn SetHash(&self, mut value: USVString) -> ErrorResult { - if value.0.is_empty() { - value = USVString("#".to_owned()); + fn SetHash(&self, value: USVString) -> ErrorResult { + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Step 2: If this Location object's relevant Document's origin is not + // same origin-domain with the entry settings object's origin, then + // throw a "SecurityError" DOMException. + self.check_same_origin_domain()?; + // Step 3: Let copyURL be a copy of this Location object's url. + let mut copy_url = self.get_url(); + // Step 4: Let input be the given value with a single leading "#" removed, if any. + // Step 5: Set copyURL's fragment to the empty string. + // Step 6: Basic URL parse input, with copyURL as url and fragment state as + // state override. + copy_url.as_mut_url().set_fragment(match value.0.as_str() { + "" => Some("#"), + _ if value.0.starts_with('#') => Some(&value.0[1..]), + _ => Some(&value.0), + }); + // Step 7: Location-object-setter navigate to copyURL. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(copy_url, referrer, HistoryEntryReplacement::Disabled, false); } - try!(self.check_same_origin_domain()); - self.set_url_component(value, UrlHelper::SetHash); Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-location-host fn GetHost(&self) -> Fallible<USVString> { - try!(self.check_same_origin_domain()); + self.check_same_origin_domain()?; Ok(UrlHelper::Host(&self.get_url())) } // https://html.spec.whatwg.org/multipage/#dom-location-host fn SetHost(&self, value: USVString) -> ErrorResult { - try!(self.check_same_origin_domain()); - self.set_url_component(value, UrlHelper::SetHost); + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Step 2: If this Location object's relevant Document's origin is not + // same origin-domain with the entry settings object's origin, then + // throw a "SecurityError" DOMException. + self.check_same_origin_domain()?; + // Step 3: Let copyURL be a copy of this Location object's url. + let mut copy_url = self.get_url(); + // Step 4: If copyURL's cannot-be-a-base-URL flag is set, terminate these steps. + if !copy_url.cannot_be_a_base() { + // Step 5: Basic URL parse the given value, with copyURL as url and host state + // as state override. + let _ = copy_url.as_mut_url().set_host(Some(&value.0)); + // Step 6: Location-object-setter navigate to copyURL. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(copy_url, referrer, HistoryEntryReplacement::Disabled, false); + } + } Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-location-origin fn GetOrigin(&self) -> Fallible<USVString> { - try!(self.check_same_origin_domain()); + self.check_same_origin_domain()?; Ok(UrlHelper::Origin(&self.get_url())) } // https://html.spec.whatwg.org/multipage/#dom-location-hostname fn GetHostname(&self) -> Fallible<USVString> { - try!(self.check_same_origin_domain()); + self.check_same_origin_domain()?; Ok(UrlHelper::Hostname(&self.get_url())) } // https://html.spec.whatwg.org/multipage/#dom-location-hostname fn SetHostname(&self, value: USVString) -> ErrorResult { - try!(self.check_same_origin_domain()); - self.set_url_component(value, UrlHelper::SetHostname); + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Step 2: If this Location object's relevant Document's origin is not + // same origin-domain with the entry settings object's origin, then + // throw a "SecurityError" DOMException. + self.check_same_origin_domain()?; + // Step 3: Let copyURL be a copy of this Location object's url. + let mut copy_url = self.get_url(); + // Step 4: If copyURL's cannot-be-a-base-URL flag is set, terminate these steps. + if !copy_url.cannot_be_a_base() { + // Step 5: Basic URL parse the given value, with copyURL as url and hostname + // state as state override. + let _ = copy_url.as_mut_url().set_host(Some(&value.0)); + // Step 6: Location-object-setter navigate to copyURL. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(copy_url, referrer, HistoryEntryReplacement::Disabled, false); + } + } Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-location-href fn GetHref(&self) -> Fallible<USVString> { - try!(self.check_same_origin_domain()); + self.check_same_origin_domain()?; Ok(UrlHelper::Href(&self.get_url())) } // https://html.spec.whatwg.org/multipage/#dom-location-href fn SetHref(&self, value: USVString) -> ErrorResult { - // Note: no call to self.check_same_origin_domain() - let url = match self.window.get_url().join(&value.0) { - Ok(url) => url, - Err(e) => return Err(Error::Type(format!("Couldn't parse URL: {}", e))), - }; - self.window.load_url(url, false, false, None); + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Note: no call to self.check_same_origin_domain() + // Step 2: Parse the given value relative to the entry settings object. + // If that failed, throw a TypeError exception. + let base_url = self.entry_settings_object().api_base_url(); + let url = match base_url.join(&value.0) { + Ok(url) => url, + Err(e) => return Err(Error::Type(format!("Couldn't parse URL: {}", e))), + }; + // Step 3: Location-object-setter navigate to the resulting URL record. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(url, referrer, HistoryEntryReplacement::Disabled, false); + } Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-location-pathname fn GetPathname(&self) -> Fallible<USVString> { - try!(self.check_same_origin_domain()); + self.check_same_origin_domain()?; Ok(UrlHelper::Pathname(&self.get_url())) } // https://html.spec.whatwg.org/multipage/#dom-location-pathname fn SetPathname(&self, value: USVString) -> ErrorResult { - try!(self.check_same_origin_domain()); - self.set_url_component(value, UrlHelper::SetPathname); + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Step 2: If this Location object's relevant Document's origin is not + // same origin-domain with the entry settings object's origin, then + // throw a "SecurityError" DOMException. + self.check_same_origin_domain()?; + // Step 3: Let copyURL be a copy of this Location object's url. + let mut copy_url = self.get_url(); + // Step 4: If copyURL's cannot-be-a-base-URL flag is set, terminate these steps. + if !copy_url.cannot_be_a_base() { + // Step 5: Set copyURL's path to the empty list. + // Step 6: Basic URL parse the given value, with copyURL as url and path + // start state as state override. + copy_url.as_mut_url().set_path(&value.0); + // Step 7: Location-object-setter navigate to copyURL. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(copy_url, referrer, HistoryEntryReplacement::Disabled, false); + } + } Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-location-port fn GetPort(&self) -> Fallible<USVString> { - try!(self.check_same_origin_domain()); + self.check_same_origin_domain()?; Ok(UrlHelper::Port(&self.get_url())) } // https://html.spec.whatwg.org/multipage/#dom-location-port fn SetPort(&self, value: USVString) -> ErrorResult { - try!(self.check_same_origin_domain()); - self.set_url_component(value, UrlHelper::SetPort); + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Step 2: If this Location object's relevant Document's origin is not + // same origin-domain with the entry settings object's origin, then + // throw a "SecurityError" DOMException. + self.check_same_origin_domain()?; + // Step 3: Let copyURL be a copy of this Location object's url. + let mut copy_url = self.get_url(); + // Step 4: If copyURL cannot have a username/password/port, then return. + // https://url.spec.whatwg.org/#cannot-have-a-username-password-port + if copy_url.host().is_some() && + !copy_url.cannot_be_a_base() && + copy_url.scheme() != "file" + { + // Step 5: If the given value is the empty string, then set copyURL's + // port to null. + // Step 6: Otherwise, basic URL parse the given value, with copyURL as url + // and port state as state override. + let _ = url::quirks::set_port(copy_url.as_mut_url(), &value.0); + // Step 7: Location-object-setter navigate to copyURL. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(copy_url, referrer, HistoryEntryReplacement::Disabled, false); + } + } Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-location-protocol fn GetProtocol(&self) -> Fallible<USVString> { - try!(self.check_same_origin_domain()); + self.check_same_origin_domain()?; Ok(UrlHelper::Protocol(&self.get_url())) } // https://html.spec.whatwg.org/multipage/#dom-location-protocol fn SetProtocol(&self, value: USVString) -> ErrorResult { - try!(self.check_same_origin_domain()); - self.set_url_component(value, UrlHelper::SetProtocol); + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Step 2: If this Location object's relevant Document's origin is not + // same origin-domain with the entry settings object's origin, then + // throw a "SecurityError" DOMException. + self.check_same_origin_domain()?; + // Step 3: Let copyURL be a copy of this Location object's url. + let mut copy_url = self.get_url(); + // Step 4: Let possibleFailure be the result of basic URL parsing the given + // value, followed by ":", with copyURL as url and scheme start state as + // state override. + let scheme = match value.0.find(':') { + Some(position) => &value.0[..position], + None => &value.0, + }; + if let Err(_) = copy_url.as_mut_url().set_scheme(scheme) { + // Step 5: If possibleFailure is failure, then throw a "SyntaxError" DOMException. + return Err(Error::Syntax); + } + // Step 6: If copyURL's scheme is not an HTTP(S) scheme, then terminate these steps. + if copy_url.scheme().eq_ignore_ascii_case("http") || + copy_url.scheme().eq_ignore_ascii_case("https") + { + // Step 7: Location-object-setter navigate to copyURL. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(copy_url, referrer, HistoryEntryReplacement::Disabled, false); + } + } Ok(()) } - // https://html.spec.whatwg.org/multipage/#dom-location-href - fn Stringifier(&self) -> Fallible<DOMString> { - Ok(DOMString::from(try!(self.GetHref()).0)) - } - // https://html.spec.whatwg.org/multipage/#dom-location-search fn GetSearch(&self) -> Fallible<USVString> { - try!(self.check_same_origin_domain()); + self.check_same_origin_domain()?; Ok(UrlHelper::Search(&self.get_url())) } // https://html.spec.whatwg.org/multipage/#dom-location-search fn SetSearch(&self, value: USVString) -> ErrorResult { - try!(self.check_same_origin_domain()); - self.set_url_component(value, UrlHelper::SetSearch); + // Step 1: If this Location object's relevant Document is null, then return. + if self.window.has_document() { + // Step 2: If this Location object's relevant Document's origin is not + // same origin-domain with the entry settings object's origin, then + // throw a "SecurityError" DOMException. + self.check_same_origin_domain()?; + // Step 3: Let copyURL be a copy of this Location object's url. + let mut copy_url = self.get_url(); + // Step 4: If the given value is the empty string, set copyURL's query to null. + // Step 5: Otherwise, run these substeps: + // 1. Let input be the given value with a single leading "?" removed, if any. + // 2. Set copyURL's query to the empty string. + // 3. Basic URL parse input, with copyURL as url and query state as state + // override, and the relevant Document's document's character encoding as + // encoding override. + copy_url.as_mut_url().set_query(match value.0.as_str() { + "" => None, + _ if value.0.starts_with('?') => Some(&value.0[1..]), + _ => Some(&value.0), + }); + // Step 6: Location-object-setter navigate to copyURL. + let referrer = Referrer::ReferrerUrl(self.get_url()); + self.navigate(copy_url, referrer, HistoryEntryReplacement::Disabled, false); + } Ok(()) } } diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index 14c05bd3cee..5a32b36996b 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -1,13 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #[macro_export] macro_rules! make_getter( ( $attr:ident, $htmlname:tt ) => ( fn $attr(&self) -> DOMString { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); element.get_string_attribute(&local_name!($htmlname)) } @@ -18,8 +18,8 @@ macro_rules! make_getter( macro_rules! make_bool_getter( ( $attr:ident, $htmlname:tt ) => ( fn $attr(&self) -> bool { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); element.has_attribute(&local_name!($htmlname)) } @@ -30,8 +30,8 @@ macro_rules! make_bool_getter( macro_rules! make_limited_int_setter( ($attr:ident, $htmlname:tt, $default:expr) => ( fn $attr(&self, value: i32) -> $crate::dom::bindings::error::ErrorResult { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let value = if value < 0 { return Err($crate::dom::bindings::error::Error::IndexSize); @@ -50,8 +50,8 @@ macro_rules! make_limited_int_setter( macro_rules! make_int_setter( ($attr:ident, $htmlname:tt, $default:expr) => ( fn $attr(&self, value: i32) { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); element.set_int_attribute(&local_name!($htmlname), value) @@ -66,8 +66,8 @@ macro_rules! make_int_setter( macro_rules! make_int_getter( ($attr:ident, $htmlname:tt, $default:expr) => ( fn $attr(&self) -> i32 { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); element.get_int_attribute(&local_name!($htmlname), $default) } @@ -82,8 +82,8 @@ macro_rules! make_int_getter( macro_rules! make_uint_getter( ($attr:ident, $htmlname:tt, $default:expr) => ( fn $attr(&self) -> u32 { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); element.get_uint_attribute(&local_name!($htmlname), $default) } @@ -96,9 +96,9 @@ macro_rules! make_uint_getter( #[macro_export] macro_rules! make_url_getter( ( $attr:ident, $htmlname:tt ) => ( - fn $attr(&self) -> DOMString { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + fn $attr(&self) -> USVString { + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); element.get_url_attribute(&local_name!($htmlname)) } @@ -106,50 +106,61 @@ macro_rules! make_url_getter( ); #[macro_export] -macro_rules! make_url_or_base_getter( +macro_rules! make_url_setter( ( $attr:ident, $htmlname:tt ) => ( - fn $attr(&self) -> DOMString { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + fn $attr(&self, value: USVString) { + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); - let url = element.get_url_attribute(&local_name!($htmlname)); - if url.is_empty() { - let window = window_from_node(self); - DOMString::from(window.get_url().into_string()) - } else { - url - } + element.set_url_attribute(&local_name!($htmlname), + value); } ); ); #[macro_export] -macro_rules! make_string_or_document_url_getter( +macro_rules! make_form_action_getter( ( $attr:ident, $htmlname:tt ) => ( fn $attr(&self) -> DOMString { - use dom::bindings::inheritance::Castable; - use dom::element::Element; - use dom::node::document_from_node; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); - let val = element.get_string_attribute(&local_name!($htmlname)); - - if val.is_empty() { - let doc = document_from_node(self); - DOMString::from(doc.url().into_string()) - } else { - val + let doc = crate::dom::node::document_from_node(self); + let attr = element.get_attribute(&ns!(), &local_name!($htmlname)); + let value = attr.as_ref().map(|attr| attr.value()); + let value = match value { + Some(ref value) if !value.is_empty() => &***value, + _ => return doc.url().into_string().into(), + }; + match doc.base_url().join(value) { + Ok(parsed) => parsed.into_string().into(), + Err(_) => value.to_owned().into(), } } ); ); #[macro_export] +macro_rules! make_labels_getter( + ( $attr:ident, $memo:ident ) => ( + fn $attr(&self) -> DomRoot<NodeList> { + use crate::dom::htmlelement::HTMLElement; + use crate::dom::nodelist::NodeList; + self.$memo.or_init(|| NodeList::new_labels_list( + self.upcast::<Node>().owner_doc().window(), + self.upcast::<HTMLElement>() + ) + ) + } + ); +); + +#[macro_export] macro_rules! make_enumerated_getter( ( $attr:ident, $htmlname:tt, $default:expr, $($choices: pat)|+) => ( fn $attr(&self) -> DOMString { - use dom::bindings::inheritance::Castable; - use dom::element::Element; - use std::ascii::AsciiExt; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); let mut val = element.get_string_attribute(&local_name!($htmlname)); val.make_ascii_lowercase(); @@ -168,8 +179,8 @@ macro_rules! make_enumerated_getter( macro_rules! make_setter( ( $attr:ident, $htmlname:tt ) => ( fn $attr(&self, value: DOMString) { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); element.set_string_attribute(&local_name!($htmlname), value) } @@ -180,8 +191,8 @@ macro_rules! make_setter( macro_rules! make_bool_setter( ( $attr:ident, $htmlname:tt ) => ( fn $attr(&self, value: bool) { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); element.set_bool_attribute(&local_name!($htmlname), value) } @@ -189,27 +200,12 @@ macro_rules! make_bool_setter( ); #[macro_export] -macro_rules! make_url_setter( - ( $attr:ident, $htmlname:tt ) => ( - fn $attr(&self, value: DOMString) { - use dom::bindings::inheritance::Castable; - use dom::element::Element; - use dom::node::document_from_node; - let value = AttrValue::from_url(document_from_node(self).url(), - value.into()); - let element = self.upcast::<Element>(); - element.set_attribute(&local_name!($htmlname), value); - } - ); -); - -#[macro_export] macro_rules! make_uint_setter( ($attr:ident, $htmlname:tt, $default:expr) => ( fn $attr(&self, value: u32) { - use dom::bindings::inheritance::Castable; - use dom::element::Element; - use dom::values::UNSIGNED_LONG_MAX; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; + use crate::dom::values::UNSIGNED_LONG_MAX; let value = if value > UNSIGNED_LONG_MAX { $default } else { @@ -228,9 +224,9 @@ macro_rules! make_uint_setter( macro_rules! make_limited_uint_setter( ($attr:ident, $htmlname:tt, $default:expr) => ( fn $attr(&self, value: u32) -> $crate::dom::bindings::error::ErrorResult { - use dom::bindings::inheritance::Castable; - use dom::element::Element; - use dom::values::UNSIGNED_LONG_MAX; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; + use crate::dom::values::UNSIGNED_LONG_MAX; let value = if value == 0 { return Err($crate::dom::bindings::error::Error::IndexSize); } else if value > UNSIGNED_LONG_MAX { @@ -252,8 +248,8 @@ macro_rules! make_limited_uint_setter( macro_rules! make_atomic_setter( ( $attr:ident, $htmlname:tt ) => ( fn $attr(&self, value: DOMString) { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); element.set_atomic_attribute(&local_name!($htmlname), value) } @@ -264,8 +260,8 @@ macro_rules! make_atomic_setter( macro_rules! make_legacy_color_setter( ( $attr:ident, $htmlname:tt ) => ( fn $attr(&self, value: DOMString) { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; use style::attr::AttrValue; let element = self.upcast::<Element>(); let value = AttrValue::from_legacy_color(value.into()); @@ -278,8 +274,8 @@ macro_rules! make_legacy_color_setter( macro_rules! make_dimension_setter( ( $attr:ident, $htmlname:tt ) => ( fn $attr(&self, value: DOMString) { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); let value = AttrValue::from_dimension(value.into()); element.set_attribute(&local_name!($htmlname), value) @@ -291,8 +287,8 @@ macro_rules! make_dimension_setter( macro_rules! make_nonzero_dimension_setter( ( $attr:ident, $htmlname:tt ) => ( fn $attr(&self, value: DOMString) { - use dom::bindings::inheritance::Castable; - use dom::element::Element; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::element::Element; let element = self.upcast::<Element>(); let value = AttrValue::from_nonzero_dimension(value.into()); element.set_attribute(&local_name!($htmlname), value) @@ -303,7 +299,7 @@ macro_rules! make_nonzero_dimension_setter( /// For use on non-jsmanaged types /// Use #[derive(JSTraceable)] on JS managed types macro_rules! unsafe_no_jsmanaged_fields( - ($($ty:ident),+) => ( + ($($ty:ty),+) => ( $( #[allow(unsafe_code)] unsafe impl $crate::dom::bindings::trace::JSTraceable for $ty { @@ -334,17 +330,17 @@ macro_rules! jsmanaged_array( /// These are used to generate a event handler which has no special case. macro_rules! define_event_handler( - ($handler: ident, $event_type: ident, $getter: ident, $setter: ident, $setter_fn: ident) => ( + ($handler: ty, $event_type: ident, $getter: ident, $setter: ident, $setter_fn: ident) => ( fn $getter(&self) -> Option<::std::rc::Rc<$handler>> { - use dom::bindings::inheritance::Castable; - use dom::eventtarget::EventTarget; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::eventtarget::EventTarget; let eventtarget = self.upcast::<EventTarget>(); eventtarget.get_event_handler_common(stringify!($event_type)) } fn $setter(&self, listener: Option<::std::rc::Rc<$handler>>) { - use dom::bindings::inheritance::Castable; - use dom::eventtarget::EventTarget; + use crate::dom::bindings::inheritance::Castable; + use crate::dom::eventtarget::EventTarget; let eventtarget = self.upcast::<EventTarget>(); eventtarget.$setter_fn(stringify!($event_type), listener) } @@ -352,7 +348,7 @@ macro_rules! define_event_handler( ); macro_rules! define_window_owned_event_handler( - ($handler: ident, $event_type: ident, $getter: ident, $setter: ident) => ( + ($handler: ty, $event_type: ident, $getter: ident, $setter: ident) => ( fn $getter(&self) -> Option<::std::rc::Rc<$handler>> { let document = document_from_node(self); if document.has_browsing_context() { @@ -373,36 +369,59 @@ macro_rules! define_window_owned_event_handler( macro_rules! event_handler( ($event_type: ident, $getter: ident, $setter: ident) => ( - define_event_handler!(EventHandlerNonNull, $event_type, $getter, $setter, - set_event_handler_common); + define_event_handler!( + crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull, + $event_type, + $getter, + $setter, + set_event_handler_common + ); ) ); macro_rules! error_event_handler( ($event_type: ident, $getter: ident, $setter: ident) => ( - define_event_handler!(OnErrorEventHandlerNonNull, $event_type, $getter, $setter, - set_error_event_handler); + define_event_handler!( + crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull, + $event_type, + $getter, + $setter, + set_error_event_handler + ); ) ); macro_rules! beforeunload_event_handler( ($event_type: ident, $getter: ident, $setter: ident) => ( - define_event_handler!(OnBeforeUnloadEventHandlerNonNull, $event_type, - $getter, $setter, set_beforeunload_event_handler); + define_event_handler!( + crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull, + $event_type, + $getter, + $setter, + set_beforeunload_event_handler + ); ) ); macro_rules! window_owned_event_handler( ($event_type: ident, $getter: ident, $setter: ident) => ( - define_window_owned_event_handler!(EventHandlerNonNull, - $event_type, $getter, $setter); + define_window_owned_event_handler!( + crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull, + $event_type, + $getter, + $setter + ); ) ); macro_rules! window_owned_beforeunload_event_handler( ($event_type: ident, $getter: ident, $setter: ident) => ( - define_window_owned_event_handler!(OnBeforeUnloadEventHandlerNonNull, - $event_type, $getter, $setter); + define_window_owned_event_handler!( + crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull, + $event_type, + $getter, + $setter + ); ) ); @@ -411,7 +430,9 @@ macro_rules! window_owned_beforeunload_event_handler( // As more methods get added, just update them here. macro_rules! global_event_handlers( () => ( + // These are special when on body/frameset elements event_handler!(blur, GetOnblur, SetOnblur); + error_event_handler!(error, GetOnerror, SetOnerror); event_handler!(focus, GetOnfocus, SetOnfocus); event_handler!(load, GetOnload, SetOnload); event_handler!(resize, GetOnresize, SetOnresize); @@ -421,6 +442,8 @@ macro_rules! global_event_handlers( ); (NoOnload) => ( event_handler!(abort, GetOnabort, SetOnabort); + event_handler!(animationend, GetOnanimationend, SetOnanimationend); + event_handler!(animationiteration, GetOnanimationiteration, SetOnanimationiteration); event_handler!(cancel, GetOncancel, SetOncancel); event_handler!(canplay, GetOncanplay, SetOncanplay); event_handler!(canplaythrough, GetOncanplaythrough, SetOncanplaythrough); @@ -441,14 +464,14 @@ macro_rules! global_event_handlers( event_handler!(durationchange, GetOndurationchange, SetOndurationchange); event_handler!(emptied, GetOnemptied, SetOnemptied); event_handler!(ended, GetOnended, SetOnended); - error_event_handler!(error, GetOnerror, SetOnerror); + event_handler!(formdata, GetOnformdata, SetOnformdata); event_handler!(input, GetOninput, SetOninput); event_handler!(invalid, GetOninvalid, SetOninvalid); event_handler!(keydown, GetOnkeydown, SetOnkeydown); event_handler!(keypress, GetOnkeypress, SetOnkeypress); event_handler!(keyup, GetOnkeyup, SetOnkeyup); event_handler!(loadeddata, GetOnloadeddata, SetOnloadeddata); - event_handler!(loadedmetata, GetOnloadedmetadata, SetOnloadedmetadata); + event_handler!(loadedmetadata, GetOnloadedmetadata, SetOnloadedmetadata); event_handler!(loadstart, GetOnloadstart, SetOnloadstart); event_handler!(mousedown, GetOnmousedown, SetOnmousedown); event_handler!(mouseenter, GetOnmouseenter, SetOnmouseenter); @@ -467,13 +490,17 @@ macro_rules! global_event_handlers( event_handler!(seeked, GetOnseeked, SetOnseeked); event_handler!(seeking, GetOnseeking, SetOnseeking); event_handler!(select, GetOnselect, SetOnselect); + event_handler!(selectionchange, GetOnselectionchange, SetOnselectionchange); + event_handler!(selectstart, GetOnselectstart, SetOnselectstart); event_handler!(show, GetOnshow, SetOnshow); event_handler!(stalled, GetOnstalled, SetOnstalled); event_handler!(submit, GetOnsubmit, SetOnsubmit); event_handler!(suspend, GetOnsuspend, SetOnsuspend); event_handler!(timeupdate, GetOntimeupdate, SetOntimeupdate); event_handler!(toggle, GetOntoggle, SetOntoggle); + event_handler!(transitioncancel, GetOntransitioncancel, SetOntransitioncancel); event_handler!(transitionend, GetOntransitionend, SetOntransitionend); + event_handler!(transitionrun, GetOntransitionrun, SetOntransitionrun); event_handler!(volumechange, GetOnvolumechange, SetOnvolumechange); event_handler!(waiting, GetOnwaiting, SetOnwaiting); ) @@ -492,6 +519,7 @@ macro_rules! window_event_handlers( event_handler!(languagechange, GetOnlanguagechange, SetOnlanguagechange); event_handler!(message, GetOnmessage, SetOnmessage); + event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); event_handler!(offline, GetOnoffline, SetOnoffline); event_handler!(online, GetOnonline, SetOnonline); event_handler!(pagehide, GetOnpagehide, SetOnpagehide); @@ -517,6 +545,7 @@ macro_rules! window_event_handlers( window_owned_event_handler!(languagechange, GetOnlanguagechange, SetOnlanguagechange); window_owned_event_handler!(message, GetOnmessage, SetOnmessage); + window_owned_event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); window_owned_event_handler!(offline, GetOnoffline, SetOnoffline); window_owned_event_handler!(online, GetOnonline, SetOnonline); window_owned_event_handler!(pagehide, GetOnpagehide, SetOnpagehide); @@ -555,5 +584,92 @@ macro_rules! rooted_vec { (let mut $name:ident <- $iter:expr) => { let mut root = $crate::dom::bindings::trace::RootableVec::new_unrooted(); let mut $name = $crate::dom::bindings::trace::RootedVec::from_iter(&mut root, $iter); - } + }; +} + +/// DOM struct implementation for simple interfaces inheriting from PerformanceEntry. +macro_rules! impl_performance_entry_struct( + ($binding:ident, $struct:ident, $type:expr) => ( + use crate::dom::bindings::reflector::reflect_dom_object; + use crate::dom::bindings::root::DomRoot; + use crate::dom::bindings::str::DOMString; + use crate::dom::globalscope::GlobalScope; + use crate::dom::performanceentry::PerformanceEntry; + use dom_struct::dom_struct; + + #[dom_struct] + pub struct $struct { + entry: PerformanceEntry, + } + + impl $struct { + fn new_inherited(name: DOMString, start_time: f64, duration: f64) + -> $struct { + $struct { + entry: PerformanceEntry::new_inherited(name, + DOMString::from($type), + start_time, + duration) + } + } + + #[allow(unrooted_must_root)] + pub fn new(global: &GlobalScope, + name: DOMString, + start_time: f64, + duration: f64) -> DomRoot<$struct> { + let entry = $struct::new_inherited(name, start_time, duration); + reflect_dom_object(Box::new(entry), global) + } + } + ); +); + +macro_rules! handle_potential_webgl_error { + ($context:expr, $call:expr, $return_on_error:expr) => { + match $call { + Ok(ret) => ret, + Err(error) => { + $context.webgl_error(error); + $return_on_error + }, + } + }; + ($context:expr, $call:expr) => { + handle_potential_webgl_error!($context, $call, ()); + }; +} + +macro_rules! impl_rare_data ( + ($type:ty) => ( + fn rare_data(&self) -> Ref<Option<Box<$type>>> { + self.rare_data.borrow() + } + + #[allow(dead_code)] + fn rare_data_mut(&self) -> RefMut<Option<Box<$type>>> { + self.rare_data.borrow_mut() + } + + fn ensure_rare_data(&self) -> RefMut<Box<$type>> { + let mut rare_data = self.rare_data.borrow_mut(); + if rare_data.is_none() { + *rare_data = Some(Default::default()); + } + RefMut::map(rare_data, |rare_data| { + rare_data.as_mut().unwrap() + }) + } + ); +); + +#[macro_export] +macro_rules! optional_root_object_to_js_or_null { + ($cx: expr, $binding:expr) => {{ + rooted!(in($cx) let mut rval = NullValue()); + if let Some(object) = $binding { + object.to_jsval($cx, rval.handle_mut()); + } + rval.get() + }}; } diff --git a/components/script/dom/mediadeviceinfo.rs b/components/script/dom/mediadeviceinfo.rs new file mode 100644 index 00000000000..29d63027952 --- /dev/null +++ b/components/script/dom/mediadeviceinfo.rs @@ -0,0 +1,85 @@ +/* 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 crate::dom::bindings::codegen::Bindings::MediaDeviceInfoBinding::MediaDeviceInfoMethods; +use crate::dom::bindings::codegen::Bindings::MediaDeviceInfoBinding::MediaDeviceKind; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use servo_media::streams::device_monitor::MediaDeviceKind as ServoMediaDeviceKind; + +#[dom_struct] +pub struct MediaDeviceInfo { + reflector_: Reflector, + device_id: DOMString, + kind: MediaDeviceKind, + label: DOMString, + group_id: DOMString, +} + +impl MediaDeviceInfo { + fn new_inherited( + device_id: &str, + kind: MediaDeviceKind, + label: &str, + group_id: &str, + ) -> MediaDeviceInfo { + MediaDeviceInfo { + reflector_: Reflector::new(), + device_id: DOMString::from(device_id), + kind, + label: DOMString::from(label), + group_id: DOMString::from(group_id), + } + } + + pub fn new( + global: &GlobalScope, + device_id: &str, + kind: MediaDeviceKind, + label: &str, + group_id: &str, + ) -> DomRoot<MediaDeviceInfo> { + reflect_dom_object( + Box::new(MediaDeviceInfo::new_inherited( + device_id, kind, label, group_id, + )), + global, + ) + } +} + +impl MediaDeviceInfoMethods for MediaDeviceInfo { + /// https://w3c.github.io/mediacapture-main/#dom-mediadeviceinfo-deviceid + fn DeviceId(&self) -> DOMString { + self.device_id.clone() + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediadeviceinfo-kind + fn Kind(&self) -> MediaDeviceKind { + self.kind + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediadeviceinfo-label + fn Label(&self) -> DOMString { + self.label.clone() + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediadeviceinfo-groupid + fn GroupId(&self) -> DOMString { + self.group_id.clone() + } +} + +impl From<ServoMediaDeviceKind> for MediaDeviceKind { + fn from(kind: ServoMediaDeviceKind) -> MediaDeviceKind { + match kind { + ServoMediaDeviceKind::AudioInput => MediaDeviceKind::Audioinput, + ServoMediaDeviceKind::AudioOutput => MediaDeviceKind::Audiooutput, + ServoMediaDeviceKind::VideoInput => MediaDeviceKind::Videoinput, + } + } +} diff --git a/components/script/dom/mediadevices.rs b/components/script/dom/mediadevices.rs new file mode 100644 index 00000000000..b1f00a154be --- /dev/null +++ b/components/script/dom/mediadevices.rs @@ -0,0 +1,159 @@ +/* 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 crate::dom::bindings::codegen::Bindings::MediaDevicesBinding::MediaDevicesMethods; +use crate::dom::bindings::codegen::Bindings::MediaDevicesBinding::MediaStreamConstraints; +use crate::dom::bindings::codegen::UnionTypes::BooleanOrMediaTrackConstraints; +use crate::dom::bindings::codegen::UnionTypes::ClampedUnsignedLongOrConstrainULongRange as ConstrainULong; +use crate::dom::bindings::codegen::UnionTypes::DoubleOrConstrainDoubleRange as ConstrainDouble; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::DomRoot; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::mediadeviceinfo::MediaDeviceInfo; +use crate::dom::mediastream::MediaStream; +use crate::dom::mediastreamtrack::MediaStreamTrack; +use crate::dom::promise::Promise; +use crate::realms::{AlreadyInRealm, InRealm}; +use dom_struct::dom_struct; +use servo_media::streams::capture::{Constrain, ConstrainRange, MediaTrackConstraintSet}; +use servo_media::streams::MediaStreamType; +use servo_media::ServoMedia; +use std::rc::Rc; + +#[dom_struct] +pub struct MediaDevices { + eventtarget: EventTarget, +} + +impl MediaDevices { + pub fn new_inherited() -> MediaDevices { + MediaDevices { + eventtarget: EventTarget::new_inherited(), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<MediaDevices> { + reflect_dom_object(Box::new(MediaDevices::new_inherited()), global) + } +} + +impl MediaDevicesMethods for MediaDevices { + /// https://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia + #[allow(unsafe_code)] + fn GetUserMedia(&self, constraints: &MediaStreamConstraints, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); + let media = ServoMedia::get().unwrap(); + let stream = MediaStream::new(&self.global()); + if let Some(constraints) = convert_constraints(&constraints.audio) { + if let Some(audio) = media.create_audioinput_stream(constraints) { + let track = MediaStreamTrack::new(&self.global(), audio, MediaStreamType::Audio); + stream.add_track(&track); + } + } + if let Some(constraints) = convert_constraints(&constraints.video) { + if let Some(video) = media.create_videoinput_stream(constraints) { + let track = MediaStreamTrack::new(&self.global(), video, MediaStreamType::Video); + stream.add_track(&track); + } + } + + p.resolve_native(&stream); + p + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices + fn EnumerateDevices(&self) -> Rc<Promise> { + // Step 1. + let global = self.global(); + let in_realm_proof = AlreadyInRealm::assert(&global); + let p = Promise::new_in_current_realm(&global, InRealm::Already(&in_realm_proof)); + + // Step 2. + // XXX These steps should be run in parallel. + // XXX Steps 2.1 - 2.4 + + // Step 2.5 + let media = ServoMedia::get().unwrap(); + let device_monitor = media.get_device_monitor(); + let result_list = match device_monitor.enumerate_devices() { + Ok(devices) => devices + .iter() + .map(|device| { + // XXX The media backend has no way to group devices yet. + MediaDeviceInfo::new( + &self.global(), + &device.device_id, + device.kind.into(), + &device.label, + "", + ) + }) + .collect(), + Err(_) => Vec::new(), + }; + + p.resolve_native(&result_list); + + // Step 3. + p + } +} + +fn convert_constraints(js: &BooleanOrMediaTrackConstraints) -> Option<MediaTrackConstraintSet> { + match js { + BooleanOrMediaTrackConstraints::Boolean(false) => None, + BooleanOrMediaTrackConstraints::Boolean(true) => Some(Default::default()), + BooleanOrMediaTrackConstraints::MediaTrackConstraints(ref c) => { + Some(MediaTrackConstraintSet { + height: c.parent.height.as_ref().and_then(convert_culong), + width: c.parent.width.as_ref().and_then(convert_culong), + aspect: c.parent.aspectRatio.as_ref().and_then(convert_cdouble), + frame_rate: c.parent.frameRate.as_ref().and_then(convert_cdouble), + sample_rate: c.parent.sampleRate.as_ref().and_then(convert_culong), + }) + }, + } +} + +fn convert_culong(js: &ConstrainULong) -> Option<Constrain<u32>> { + match js { + ConstrainULong::ClampedUnsignedLong(val) => Some(Constrain::Value(*val)), + ConstrainULong::ConstrainULongRange(ref range) => { + if range.parent.min.is_some() || range.parent.max.is_some() { + Some(Constrain::Range(ConstrainRange { + min: range.parent.min, + max: range.parent.max, + ideal: range.ideal, + })) + } else if let Some(exact) = range.exact { + Some(Constrain::Value(exact)) + } else { + // the unspecified case is treated as all three being none + None + } + }, + } +} + +fn convert_cdouble(js: &ConstrainDouble) -> Option<Constrain<f64>> { + match js { + ConstrainDouble::Double(val) => Some(Constrain::Value(**val)), + ConstrainDouble::ConstrainDoubleRange(ref range) => { + if range.parent.min.is_some() || range.parent.max.is_some() { + Some(Constrain::Range(ConstrainRange { + min: range.parent.min.map(|x| *x), + max: range.parent.max.map(|x| *x), + ideal: range.ideal.map(|x| *x), + })) + } else if let Some(exact) = range.exact { + Some(Constrain::Value(*exact)) + } else { + // the unspecified case is treated as all three being none + None + } + }, + } +} diff --git a/components/script/dom/mediaelementaudiosourcenode.rs b/components/script/dom/mediaelementaudiosourcenode.rs new file mode 100644 index 00000000000..29737fc3bc0 --- /dev/null +++ b/components/script/dom/mediaelementaudiosourcenode.rs @@ -0,0 +1,76 @@ +/* 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 crate::dom::audiocontext::AudioContext; +use crate::dom::audionode::AudioNode; +use crate::dom::bindings::codegen::Bindings::MediaElementAudioSourceNodeBinding::MediaElementAudioSourceNodeMethods; +use crate::dom::bindings::codegen::Bindings::MediaElementAudioSourceNodeBinding::MediaElementAudioSourceOptions; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::media_element_source_node::MediaElementSourceNodeMessage; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage}; +use std::sync::mpsc; + +#[dom_struct] +pub struct MediaElementAudioSourceNode { + node: AudioNode, + media_element: Dom<HTMLMediaElement>, +} + +impl MediaElementAudioSourceNode { + #[allow(unrooted_must_root)] + fn new_inherited( + context: &AudioContext, + media_element: &HTMLMediaElement, + ) -> Fallible<MediaElementAudioSourceNode> { + let node = AudioNode::new_inherited( + AudioNodeInit::MediaElementSourceNode, + &*context.base(), + Default::default(), + 0, + 1, + )?; + let (sender, receiver) = mpsc::channel(); + node.message(AudioNodeMessage::MediaElementSourceNode( + MediaElementSourceNodeMessage::GetAudioRenderer(sender), + )); + let audio_renderer = receiver.recv().unwrap(); + media_element.set_audio_renderer(audio_renderer); + let media_element = Dom::from_ref(media_element); + Ok(MediaElementAudioSourceNode { + node, + media_element, + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &AudioContext, + media_element: &HTMLMediaElement, + ) -> Fallible<DomRoot<MediaElementAudioSourceNode>> { + let node = MediaElementAudioSourceNode::new_inherited(context, media_element)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &AudioContext, + options: &MediaElementAudioSourceOptions, + ) -> Fallible<DomRoot<MediaElementAudioSourceNode>> { + MediaElementAudioSourceNode::new(window, context, &*options.mediaElement) + } +} + +impl MediaElementAudioSourceNodeMethods for MediaElementAudioSourceNode { + /// https://webaudio.github.io/web-audio-api/#dom-mediaelementaudiosourcenode-mediaelement + fn MediaElement(&self) -> DomRoot<HTMLMediaElement> { + DomRoot::from_ref(&*self.media_element) + } +} diff --git a/components/script/dom/mediaerror.rs b/components/script/dom/mediaerror.rs index ff158333473..ace68e43583 100644 --- a/components/script/dom/mediaerror.rs +++ b/components/script/dom/mediaerror.rs @@ -1,11 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::MediaErrorBinding::{self, MediaErrorMethods}; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::window::Window; use dom_struct::dom_struct; #[dom_struct] @@ -22,10 +23,8 @@ impl MediaError { } } - pub fn new(window: &Window, code: u16) -> Root<MediaError> { - reflect_dom_object(box MediaError::new_inherited(code), - window, - MediaErrorBinding::Wrap) + pub fn new(window: &Window, code: u16) -> DomRoot<MediaError> { + reflect_dom_object(Box::new(MediaError::new_inherited(code)), window) } } @@ -34,4 +33,9 @@ impl MediaErrorMethods for MediaError { fn Code(&self) -> u16 { self.code } + + // https://html.spec.whatwg.org/multipage/#dom-mediaerror-message + fn Message(&self) -> DOMString { + DOMString::new() + } } diff --git a/components/script/dom/mediafragmentparser.rs b/components/script/dom/mediafragmentparser.rs new file mode 100644 index 00000000000..2e9de4b0f8c --- /dev/null +++ b/components/script/dom/mediafragmentparser.rs @@ -0,0 +1,340 @@ +/* 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 chrono::NaiveDateTime; +use servo_url::ServoUrl; +use std::borrow::Cow; +use std::collections::VecDeque; +use std::str::FromStr; +use url::{form_urlencoded, Position, Url}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum SpatialRegion { + Pixel, + Percent, +} + +impl FromStr for SpatialRegion { + type Err = (); + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "pixel" => Ok(SpatialRegion::Pixel), + "percent" => Ok(SpatialRegion::Percent), + _ => Err(()), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SpatialClipping { + region: Option<SpatialRegion>, + x: u32, + y: u32, + width: u32, + height: u32, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MediaFragmentParser { + id: Option<String>, + tracks: Vec<String>, + spatial: Option<SpatialClipping>, + start: Option<f64>, + end: Option<f64>, +} + +impl MediaFragmentParser { + pub fn id(&self) -> Option<String> { + self.id.clone() + } + + pub fn tracks(&self) -> &Vec<String> { + self.tracks.as_ref() + } + + pub fn start(&self) -> Option<f64> { + self.start + } + + // Parse an str of key value pairs, a URL, or a fragment. + pub fn parse(input: &str) -> MediaFragmentParser { + let mut parser = MediaFragmentParser::default(); + let (query, fragment) = split_url(input); + let mut octets = decode_octets(query.as_bytes()); + octets.extend(decode_octets(fragment.as_bytes())); + + if !octets.is_empty() { + for (key, value) in octets.iter() { + match key.as_bytes() { + b"t" => { + if let Ok((start, end)) = parser.parse_temporal(value) { + parser.start = start; + parser.end = end; + } + }, + b"xywh" => { + if let Ok(spatial) = parser.parse_spatial(value) { + parser.spatial = Some(spatial); + } + }, + b"id" => parser.id = Some(value.to_string()), + b"track" => parser.tracks.push(value.to_string()), + _ => {}, + } + } + parser + } else { + if let Ok((start, end)) = parser.parse_temporal(input) { + parser.start = start; + parser.end = end; + } else if let Ok(spatial) = parser.parse_spatial(input) { + parser.spatial = Some(spatial); + } + parser + } + } + + // Either NPT or UTC timestamp (real world clock time). + fn parse_temporal(&self, input: &str) -> Result<(Option<f64>, Option<f64>), ()> { + let (_, fragment) = split_prefix(input); + + if fragment.ends_with('Z') || fragment.ends_with("Z-") { + return self.parse_utc_timestamp(fragment); + } + + if fragment.starts_with(',') || !fragment.contains(',') { + let sec = parse_hms(&fragment.replace(',', ""))?; + if fragment.starts_with(',') { + Ok((Some(0.), Some(sec))) + } else { + Ok((Some(sec), None)) + } + } else { + let mut iterator = fragment.split(','); + let start = parse_hms(iterator.next().ok_or_else(|| ())?)?; + let end = parse_hms(iterator.next().ok_or_else(|| ())?)?; + + if iterator.next().is_some() || start >= end { + return Err(()); + } + + Ok((Some(start), Some(end))) + } + } + + fn parse_utc_timestamp(&self, input: &str) -> Result<(Option<f64>, Option<f64>), ()> { + if input.ends_with('-') || input.starts_with(',') || !input.contains('-') { + let sec = parse_hms( + NaiveDateTime::parse_from_str( + &input.replace('-', "").replace(',', ""), + "%Y%m%dT%H%M%S%.fZ", + ) + .map_err(|_| ())? + .time() + .to_string() + .as_ref(), + )?; + if input.starts_with(',') { + Ok((Some(0.), Some(sec))) + } else { + Ok((Some(sec), None)) + } + } else { + let vec: Vec<&str> = input.split('-').collect(); + let mut hms: Vec<f64> = vec + .iter() + .map(|s| NaiveDateTime::parse_from_str(s, "%Y%m%dT%H%M%S%.fZ")) + .flatten() + .map(|s| parse_hms(&s.time().to_string())) + .flatten() + .collect(); + + let end = hms.pop().ok_or_else(|| ())?; + let start = hms.pop().ok_or_else(|| ())?; + + if !hms.is_empty() || start >= end { + return Err(()); + } + + Ok((Some(start), Some(end))) + } + } + + fn parse_spatial(&self, input: &str) -> Result<SpatialClipping, ()> { + let (prefix, s) = split_prefix(input); + let vec: Vec<&str> = s.split(',').collect(); + let mut queue: VecDeque<u32> = vec.iter().map(|s| s.parse::<u32>()).flatten().collect(); + + let mut clipping = SpatialClipping { + region: None, + x: queue.pop_front().ok_or_else(|| ())?, + y: queue.pop_front().ok_or_else(|| ())?, + width: queue.pop_front().ok_or_else(|| ())?, + height: queue.pop_front().ok_or_else(|| ())?, + }; + + if !queue.is_empty() { + return Err(()); + } + + if let Some(s) = prefix { + let region = SpatialRegion::from_str(s)?; + if region.eq(&SpatialRegion::Percent) && + (clipping.x + clipping.width > 100 || clipping.y + clipping.height > 100) + { + return Err(()); + } + clipping.region = Some(region); + } + + Ok(clipping) + } +} + +impl From<&Url> for MediaFragmentParser { + fn from(url: &Url) -> Self { + let input: &str = &url[Position::AfterPath..]; + MediaFragmentParser::parse(input) + } +} + +impl From<&ServoUrl> for MediaFragmentParser { + fn from(servo_url: &ServoUrl) -> Self { + let input: &str = &servo_url[Position::AfterPath..]; + MediaFragmentParser::parse(input) + } +} + +// 5.1.1 Processing name-value components. +fn decode_octets(bytes: &[u8]) -> Vec<(Cow<str>, Cow<str>)> { + form_urlencoded::parse(bytes) + .filter(|(key, _)| match key.as_bytes() { + b"t" | b"track" | b"id" | b"xywh" => true, + _ => false, + }) + .collect() +} + +// Parse a full URL or a relative URL without a base retaining the query and/or fragment. +fn split_url(s: &str) -> (&str, &str) { + if s.contains('?') || s.contains('#') { + for (index, byte) in s.bytes().enumerate() { + if byte == b'?' { + let partial = &s[index + 1..]; + for (i, byte) in partial.bytes().enumerate() { + if byte == b'#' { + return (&partial[..i], &partial[i + 1..]); + } + } + return (partial, ""); + } + + if byte == b'#' { + return ("", &s[index + 1..]); + } + } + } + ("", s) +} + +fn is_byte_number(byte: u8) -> bool { + match byte { + 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 => true, + _ => false, + } +} + +fn split_prefix(s: &str) -> (Option<&str>, &str) { + for (index, byte) in s.bytes().enumerate() { + if index == 0 && is_byte_number(byte) { + break; + } + + if byte == b':' { + return (Some(&s[..index]), &s[index + 1..]); + } + } + (None, s) +} + +fn hms_to_seconds(hour: u32, minutes: u32, seconds: f64) -> f64 { + let mut sec: f64 = f64::from(hour) * 3600.; + sec += f64::from(minutes) * 60.; + sec += seconds; + sec +} + +fn parse_npt_minute(s: &str) -> Result<u32, ()> { + if s.len() > 2 { + return Err(()); + } + + let minute = s.parse().map_err(|_| ())?; + if minute > 59 { + return Err(()); + } + + Ok(minute) +} + +fn parse_npt_seconds(s: &str) -> Result<f64, ()> { + if s.contains('.') { + let mut iterator = s.split('.'); + if let Some(s) = iterator.next() { + if s.len() > 2 { + return Err(()); + } + let sec = s.parse::<u32>().map_err(|_| ())?; + if sec > 59 { + return Err(()); + } + } + + let _ = iterator.next(); + if iterator.next().is_some() { + return Err(()); + } + } + + s.parse().map_err(|_| ()) +} + +fn parse_hms(s: &str) -> Result<f64, ()> { + let mut vec: VecDeque<&str> = s.split(':').collect(); + vec.retain(|x| !x.eq(&"")); + + let result = match vec.len() { + 1 => { + let secs = vec + .pop_front() + .ok_or_else(|| ())? + .parse::<f64>() + .map_err(|_| ())?; + + if secs == 0. { + return Err(()); + } + + hms_to_seconds(0, 0, secs) + }, + 2 => hms_to_seconds( + 0, + parse_npt_minute(vec.pop_front().ok_or_else(|| ())?)?, + parse_npt_seconds(vec.pop_front().ok_or_else(|| ())?)?, + ), + 3 => hms_to_seconds( + vec.pop_front().ok_or_else(|| ())?.parse().map_err(|_| ())?, + parse_npt_minute(vec.pop_front().ok_or_else(|| ())?)?, + parse_npt_seconds(vec.pop_front().ok_or_else(|| ())?)?, + ), + _ => return Err(()), + }; + + if !vec.is_empty() { + return Err(()); + } + + Ok(result) +} diff --git a/components/script/dom/medialist.rs b/components/script/dom/medialist.rs index 881c17966da..772c508fc04 100644 --- a/components/script/dom/medialist.rs +++ b/components/script/dom/medialist.rs @@ -1,50 +1,55 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use cssparser::Parser; -use dom::bindings::codegen::Bindings::MediaListBinding; -use dom::bindings::codegen::Bindings::MediaListBinding::MediaListMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::MediaListBinding::MediaListMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::window::Window; +use cssparser::{Parser, ParserInput}; use dom_struct::dom_struct; -use std::sync::Arc; -use style::media_queries::{MediaQuery, parse_media_query_list}; +use servo_arc::Arc; use style::media_queries::MediaList as StyleMediaList; -use style::parser::{LengthParsingMode, ParserContext}; -use style::shared_lock::{SharedRwLock, Locked}; -use style::stylesheets::CssRuleType; -use style_traits::ToCss; +use style::media_queries::MediaQuery; +use style::parser::ParserContext; +use style::shared_lock::{Locked, SharedRwLock}; +use style::stylesheets::{CssRuleType, Origin}; +use style_traits::{ParsingMode, ToCss}; #[dom_struct] pub struct MediaList { reflector_: Reflector, - parent_stylesheet: JS<CSSStyleSheet>, - #[ignore_heap_size_of = "Arc"] + parent_stylesheet: Dom<CSSStyleSheet>, + #[ignore_malloc_size_of = "Arc"] media_queries: Arc<Locked<StyleMediaList>>, } impl MediaList { #[allow(unrooted_must_root)] - pub fn new_inherited(parent_stylesheet: &CSSStyleSheet, - media_queries: Arc<Locked<StyleMediaList>>) -> MediaList { + pub fn new_inherited( + parent_stylesheet: &CSSStyleSheet, + media_queries: Arc<Locked<StyleMediaList>>, + ) -> MediaList { MediaList { - parent_stylesheet: JS::from_ref(parent_stylesheet), + parent_stylesheet: Dom::from_ref(parent_stylesheet), reflector_: Reflector::new(), media_queries: media_queries, } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, parent_stylesheet: &CSSStyleSheet, - media_queries: Arc<Locked<StyleMediaList>>) - -> Root<MediaList> { - reflect_dom_object(box MediaList::new_inherited(parent_stylesheet, media_queries), - window, - MediaListBinding::Wrap) + pub fn new( + window: &Window, + parent_stylesheet: &CSSStyleSheet, + media_queries: Arc<Locked<StyleMediaList>>, + ) -> DomRoot<MediaList> { + reflect_dom_object( + Box::new(MediaList::new_inherited(parent_stylesheet, media_queries)), + window, + ) } fn shared_lock(&self) -> &SharedRwLock { @@ -62,7 +67,7 @@ impl MediaListMethods for MediaList { // https://drafts.csswg.org/cssom/#dom-medialist-mediatext fn SetMediaText(&self, value: DOMString) { let mut guard = self.shared_lock().write(); - let mut media_queries = self.media_queries.write_with(&mut guard); + let media_queries = self.media_queries.write_with(&mut guard); // Step 2 if value.is_empty() { // Step 1 @@ -70,13 +75,22 @@ impl MediaListMethods for MediaList { return; } // Step 3 - let mut parser = Parser::new(&value); + let mut input = ParserInput::new(&value); + let mut parser = Parser::new(&mut input); let global = self.global(); - let win = global.as_window(); - let url = win.get_url(); - let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Media), - LengthParsingMode::Default); - *media_queries = parse_media_query_list(&context, &mut parser); + let window = global.as_window(); + let url = window.get_url(); + let quirks_mode = window.Document().quirks_mode(); + let context = ParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Media), + ParsingMode::DEFAULT, + quirks_mode, + window.css_error_reporter(), + None, + ); + *media_queries = StyleMediaList::parse(&context, &mut parser); } // https://drafts.csswg.org/cssom/#dom-medialist-length @@ -88,12 +102,11 @@ impl MediaListMethods for MediaList { // https://drafts.csswg.org/cssom/#dom-medialist-item fn Item(&self, index: u32) -> Option<DOMString> { let guard = self.shared_lock().read(); - self.media_queries.read_with(&guard).media_queries - .get(index as usize).and_then(|query| { - let mut s = String::new(); - query.to_css(&mut s).unwrap(); - Some(DOMString::from_string(s)) - }) + self.media_queries + .read_with(&guard) + .media_queries + .get(index as usize) + .map(|query| query.to_css_string().into()) } // https://drafts.csswg.org/cssom/#dom-medialist-item @@ -104,12 +117,21 @@ impl MediaListMethods for MediaList { // https://drafts.csswg.org/cssom/#dom-medialist-appendmedium fn AppendMedium(&self, medium: DOMString) { // Step 1 - let mut parser = Parser::new(&medium); + let mut input = ParserInput::new(&medium); + let mut parser = Parser::new(&mut input); let global = self.global(); let win = global.as_window(); let url = win.get_url(); - let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Media), - LengthParsingMode::Default); + let quirks_mode = win.Document().quirks_mode(); + let context = ParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Media), + ParsingMode::DEFAULT, + quirks_mode, + win.css_error_reporter(), + None, + ); let m = MediaQuery::parse(&context, &mut parser); // Step 2 if let Err(_) = m { @@ -119,7 +141,10 @@ impl MediaListMethods for MediaList { let m_serialized = m.clone().unwrap().to_css_string(); let mut guard = self.shared_lock().write(); let mq = self.media_queries.write_with(&mut guard); - let any = mq.media_queries.iter().any(|q| m_serialized == q.to_css_string()); + let any = mq + .media_queries + .iter() + .any(|q| m_serialized == q.to_css_string()); if any { return; } @@ -130,12 +155,21 @@ impl MediaListMethods for MediaList { // https://drafts.csswg.org/cssom/#dom-medialist-deletemedium fn DeleteMedium(&self, medium: DOMString) { // Step 1 - let mut parser = Parser::new(&medium); + let mut input = ParserInput::new(&medium); + let mut parser = Parser::new(&mut input); let global = self.global(); let win = global.as_window(); let url = win.get_url(); - let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Media), - LengthParsingMode::Default); + let quirks_mode = win.Document().quirks_mode(); + let context = ParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Media), + ParsingMode::DEFAULT, + quirks_mode, + win.css_error_reporter(), + None, + ); let m = MediaQuery::parse(&context, &mut parser); // Step 2 if let Err(_) = m { @@ -144,10 +178,12 @@ impl MediaListMethods for MediaList { // Step 3 let m_serialized = m.unwrap().to_css_string(); let mut guard = self.shared_lock().write(); - let mut media_list = self.media_queries.write_with(&mut guard); - let new_vec = media_list.media_queries.drain(..) - .filter(|q| m_serialized != q.to_css_string()) - .collect(); + let media_list = self.media_queries.write_with(&mut guard); + let new_vec = media_list + .media_queries + .drain(..) + .filter(|q| m_serialized != q.to_css_string()) + .collect(); media_list.media_queries = new_vec; } } diff --git a/components/script/dom/mediametadata.rs b/components/script/dom/mediametadata.rs new file mode 100644 index 00000000000..3abe0a1a20c --- /dev/null +++ b/components/script/dom/mediametadata.rs @@ -0,0 +1,93 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataInit; +use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::mediasession::MediaSession; +use crate::dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct MediaMetadata { + reflector_: Reflector, + session: MutNullableDom<MediaSession>, + title: DomRefCell<DOMString>, + artist: DomRefCell<DOMString>, + album: DomRefCell<DOMString>, +} + +impl MediaMetadata { + fn new_inherited(init: &MediaMetadataInit) -> MediaMetadata { + MediaMetadata { + reflector_: Reflector::new(), + session: Default::default(), + title: DomRefCell::new(init.title.clone()), + artist: DomRefCell::new(init.artist.clone()), + album: DomRefCell::new(init.album.clone()), + } + } + + pub fn new(global: &Window, init: &MediaMetadataInit) -> DomRoot<MediaMetadata> { + reflect_dom_object(Box::new(MediaMetadata::new_inherited(init)), global) + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-mediametadata + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + init: &MediaMetadataInit, + ) -> Fallible<DomRoot<MediaMetadata>> { + Ok(MediaMetadata::new(window, init)) + } + + fn queue_update_metadata_algorithm(&self) { + if self.session.get().is_none() { + return; + } + } + + pub fn set_session(&self, session: &MediaSession) { + self.session.set(Some(&session)); + } +} + +impl MediaMetadataMethods for MediaMetadata { + /// https://w3c.github.io/mediasession/#dom-mediametadata-title + fn Title(&self) -> DOMString { + self.title.borrow().clone() + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-title + fn SetTitle(&self, value: DOMString) { + *self.title.borrow_mut() = value; + self.queue_update_metadata_algorithm(); + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-artist + fn Artist(&self) -> DOMString { + self.artist.borrow().clone() + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-artist + fn SetArtist(&self, value: DOMString) { + *self.artist.borrow_mut() = value; + self.queue_update_metadata_algorithm(); + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-album + fn Album(&self) -> DOMString { + self.album.borrow().clone() + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-album + fn SetAlbum(&self, value: DOMString) { + *self.album.borrow_mut() = value; + self.queue_update_metadata_algorithm(); + } +} diff --git a/components/script/dom/mediaquerylist.rs b/components/script/dom/mediaquerylist.rs index dafc7602794..48080f95adf 100644 --- a/components/script/dom/mediaquerylist.rs +++ b/components/script/dom/mediaquerylist.rs @@ -1,28 +1,21 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; -use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; -use dom::bindings::codegen::Bindings::MediaQueryListBinding::{self, MediaQueryListMethods}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::DomObject; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::bindings::trace::JSTraceable; -use dom::bindings::weakref::{WeakRef, WeakRefVec}; -use dom::document::Document; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::mediaquerylistevent::MediaQueryListEvent; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; +use crate::dom::bindings::codegen::Bindings::EventTargetBinding::AddEventListenerOptions; +use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventListenerOptions; +use crate::dom::bindings::codegen::Bindings::MediaQueryListBinding::MediaQueryListMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::eventtarget::EventTarget; use dom_struct::dom_struct; -use js::jsapi::JSTracer; use std::cell::Cell; use std::rc::Rc; -use style::media_queries::{Device, MediaList, MediaType}; +use style::media_queries::MediaList; use style_traits::ToCss; pub enum MediaQueryListMatchState { @@ -33,30 +26,31 @@ pub enum MediaQueryListMatchState { #[dom_struct] pub struct MediaQueryList { eventtarget: EventTarget, - document: JS<Document>, + document: Dom<Document>, media_query_list: MediaList, - last_match_state: Cell<Option<bool>> + last_match_state: Cell<Option<bool>>, } impl MediaQueryList { fn new_inherited(document: &Document, media_query_list: MediaList) -> MediaQueryList { MediaQueryList { eventtarget: EventTarget::new_inherited(), - document: JS::from_ref(document), + document: Dom::from_ref(document), media_query_list: media_query_list, last_match_state: Cell::new(None), } } - pub fn new(document: &Document, media_query_list: MediaList) -> Root<MediaQueryList> { - reflect_dom_object(box MediaQueryList::new_inherited(document, media_query_list), - document.window(), - MediaQueryListBinding::Wrap) + pub fn new(document: &Document, media_query_list: MediaList) -> DomRoot<MediaQueryList> { + reflect_dom_object( + Box::new(MediaQueryList::new_inherited(document, media_query_list)), + document.window(), + ) } } impl MediaQueryList { - fn evaluate_changes(&self) -> MediaQueryListMatchState { + pub fn evaluate_changes(&self) -> MediaQueryListMatchState { let matches = self.evaluate(); let result = if let Some(old_matches) = self.last_match_state.get() { @@ -74,22 +68,15 @@ impl MediaQueryList { } pub fn evaluate(&self) -> bool { - if let Some(window_size) = self.document.window().window_size() { - let viewport_size = window_size.initial_viewport; - let device = Device::new(MediaType::Screen, viewport_size); - self.media_query_list.evaluate(&device) - } else { - false - } + self.media_query_list + .evaluate(&self.document.device(), self.document.quirks_mode()) } } impl MediaQueryListMethods for MediaQueryList { // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-media fn Media(&self) -> DOMString { - let mut s = String::new(); - self.media_query_list.to_css(&mut s).unwrap(); - DOMString::from_string(s) + self.media_query_list.to_css_string().into() } // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-matches @@ -102,61 +89,25 @@ impl MediaQueryListMethods for MediaQueryList { // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-addlistener fn AddListener(&self, listener: Option<Rc<EventListener>>) { - self.upcast::<EventTarget>().AddEventListener(DOMString::from_string("change".to_owned()), - listener, false); + self.upcast::<EventTarget>().add_event_listener( + DOMString::from_string("change".to_owned()), + listener, + AddEventListenerOptions { + parent: EventListenerOptions { capture: false }, + once: false, + }, + ); } // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-removelistener fn RemoveListener(&self, listener: Option<Rc<EventListener>>) { - self.upcast::<EventTarget>().RemoveEventListener(DOMString::from_string("change".to_owned()), - listener, false); + self.upcast::<EventTarget>().remove_event_listener( + DOMString::from_string("change".to_owned()), + listener, + EventListenerOptions { capture: false }, + ); } // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-onchange event_handler!(change, GetOnchange, SetOnchange); } - -#[derive(HeapSizeOf)] -pub struct WeakMediaQueryListVec { - cell: DOMRefCell<WeakRefVec<MediaQueryList>>, -} - -impl WeakMediaQueryListVec { - /// Create a new vector of weak references to MediaQueryList - pub fn new() -> Self { - WeakMediaQueryListVec { cell: DOMRefCell::new(WeakRefVec::new()) } - } - - pub fn push(&self, mql: &MediaQueryList) { - self.cell.borrow_mut().push(WeakRef::new(mql)); - } - - /// Evaluate media query lists and report changes - /// https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes - pub fn evaluate_and_report_changes(&self) { - rooted_vec!(let mut mql_list); - self.cell.borrow_mut().update(|mql| { - let mql = mql.root().unwrap(); - if let MediaQueryListMatchState::Changed(_) = mql.evaluate_changes() { - // Recording list of changed Media Queries - mql_list.push(JS::from_ref(&*mql)); - } - }); - // Sending change events for all changed Media Queries - for mql in mql_list.iter() { - let event = MediaQueryListEvent::new(&mql.global(), - atom!("change"), - false, false, - mql.Media(), - mql.Matches()); - event.upcast::<Event>().fire(mql.upcast::<EventTarget>()); - } - } -} - -#[allow(unsafe_code)] -unsafe impl JSTraceable for WeakMediaQueryListVec { - unsafe fn trace(&self, _: *mut JSTracer) { - self.cell.borrow_mut().retain_alive() - } -} diff --git a/components/script/dom/mediaquerylistevent.rs b/components/script/dom/mediaquerylistevent.rs index 18be8b1d1b5..c34c3d5116f 100644 --- a/components/script/dom/mediaquerylistevent.rs +++ b/components/script/dom/mediaquerylistevent.rs @@ -1,19 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::MediaQueryListEventBinding; -use dom::bindings::codegen::Bindings::MediaQueryListEventBinding::MediaQueryListEventInit; -use dom::bindings::codegen::Bindings::MediaQueryListEventBinding::MediaQueryListEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::globalscope::GlobalScope; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::MediaQueryListEventBinding::MediaQueryListEventInit; +use crate::dom::bindings::codegen::Bindings::MediaQueryListEventBinding::MediaQueryListEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; use std::cell::Cell; @@ -23,24 +22,31 @@ use std::cell::Cell; pub struct MediaQueryListEvent { event: Event, media: DOMString, - matches: Cell<bool> + matches: Cell<bool>, } impl MediaQueryListEvent { - pub fn new_initialized(global: &GlobalScope, - media: DOMString, - matches: bool) -> Root<MediaQueryListEvent> { - let ev = box MediaQueryListEvent { + pub fn new_initialized( + global: &GlobalScope, + media: DOMString, + matches: bool, + ) -> DomRoot<MediaQueryListEvent> { + let ev = Box::new(MediaQueryListEvent { event: Event::new_inherited(), media: media, - matches: Cell::new(matches) - }; - reflect_dom_object(ev, global, MediaQueryListEventBinding::Wrap) + matches: Cell::new(matches), + }); + reflect_dom_object(ev, global) } - pub fn new(global: &GlobalScope, type_: Atom, - bubbles: bool, cancelable: bool, - media: DOMString, matches: bool) -> Root<MediaQueryListEvent> { + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + media: DOMString, + matches: bool, + ) -> DomRoot<MediaQueryListEvent> { let ev = MediaQueryListEvent::new_initialized(global, media, matches); { let event = ev.upcast::<Event>(); @@ -49,13 +55,21 @@ impl MediaQueryListEvent { ev } - pub fn Constructor(window: &Window, type_: DOMString, - init: &MediaQueryListEventInit) - -> Fallible<Root<MediaQueryListEvent>> { + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &MediaQueryListEventInit, + ) -> Fallible<DomRoot<MediaQueryListEvent>> { let global = window.upcast::<GlobalScope>(); - Ok(MediaQueryListEvent::new(global, Atom::from(type_), - init.parent.bubbles, init.parent.cancelable, - init.media.clone(), init.matches)) + Ok(MediaQueryListEvent::new( + global, + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + init.media.clone(), + init.matches, + )) } } diff --git a/components/script/dom/mediasession.rs b/components/script/dom/mediasession.rs new file mode 100644 index 00000000000..49112ba921b --- /dev/null +++ b/components/script/dom/mediasession.rs @@ -0,0 +1,265 @@ +/* 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 crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods; +use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataInit; +use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataMethods; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaPositionState; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionAction; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionActionHandler; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionMethods; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionPlaybackState; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::mediametadata::MediaMetadata; +use crate::dom::window::Window; +use crate::realms::{enter_realm, InRealm}; +use dom_struct::dom_struct; +use embedder_traits::MediaMetadata as EmbedderMediaMetadata; +use embedder_traits::MediaSessionEvent; +use script_traits::MediaSessionActionType; +use script_traits::ScriptMsg; +use std::collections::HashMap; +use std::rc::Rc; + +#[dom_struct] +pub struct MediaSession { + reflector_: Reflector, + /// https://w3c.github.io/mediasession/#dom-mediasession-metadata + #[ignore_malloc_size_of = "defined in embedder_traits"] + metadata: DomRefCell<Option<EmbedderMediaMetadata>>, + /// https://w3c.github.io/mediasession/#dom-mediasession-playbackstate + playback_state: DomRefCell<MediaSessionPlaybackState>, + /// https://w3c.github.io/mediasession/#supported-media-session-actions + #[ignore_malloc_size_of = "Rc"] + action_handlers: DomRefCell<HashMap<MediaSessionActionType, Rc<MediaSessionActionHandler>>>, + /// The media instance controlled by this media session. + /// For now only HTMLMediaElements are controlled by media sessions. + media_instance: MutNullableDom<HTMLMediaElement>, +} + +impl MediaSession { + #[allow(unrooted_must_root)] + fn new_inherited() -> MediaSession { + let media_session = MediaSession { + reflector_: Reflector::new(), + metadata: DomRefCell::new(None), + playback_state: DomRefCell::new(MediaSessionPlaybackState::None), + action_handlers: DomRefCell::new(HashMap::new()), + media_instance: Default::default(), + }; + media_session + } + + pub fn new(window: &Window) -> DomRoot<MediaSession> { + reflect_dom_object(Box::new(MediaSession::new_inherited()), window) + } + + pub fn register_media_instance(&self, media_instance: &HTMLMediaElement) { + self.media_instance.set(Some(media_instance)); + } + + pub fn handle_action(&self, action: MediaSessionActionType) { + debug!("Handle media session action {:?}", action); + + if let Some(handler) = self.action_handlers.borrow().get(&action) { + if handler.Call__(ExceptionHandling::Report).is_err() { + warn!("Error calling MediaSessionActionHandler callback"); + } + return; + } + + // Default action. + if let Some(media) = self.media_instance.get() { + match action { + MediaSessionActionType::Play => { + let realm = enter_realm(self); + media.Play(InRealm::Entered(&realm)); + }, + MediaSessionActionType::Pause => { + media.Pause(); + }, + MediaSessionActionType::SeekBackward => {}, + MediaSessionActionType::SeekForward => {}, + MediaSessionActionType::PreviousTrack => {}, + MediaSessionActionType::NextTrack => {}, + MediaSessionActionType::SkipAd => {}, + MediaSessionActionType::Stop => {}, + MediaSessionActionType::SeekTo => {}, + } + } + } + + pub fn send_event(&self, event: MediaSessionEvent) { + let global = self.global(); + let window = global.as_window(); + let pipeline_id = window.pipeline_id(); + window.send_to_constellation(ScriptMsg::MediaSessionEvent(pipeline_id, event)); + } + + pub fn update_title(&self, title: String) { + let mut metadata = self.metadata.borrow_mut(); + if let Some(ref mut metadata) = *metadata { + // We only update the title with the data provided by the media + // player and iff the user did not provide a title. + if !metadata.title.is_empty() { + return; + } + metadata.title = title; + } else { + *metadata = Some(EmbedderMediaMetadata::new(title)); + } + self.send_event(MediaSessionEvent::SetMetadata( + metadata.as_ref().unwrap().clone(), + )); + } +} + +impl MediaSessionMethods for MediaSession { + /// https://w3c.github.io/mediasession/#dom-mediasession-metadata + fn GetMetadata(&self) -> Option<DomRoot<MediaMetadata>> { + if let Some(ref metadata) = *self.metadata.borrow() { + let mut init = MediaMetadataInit::empty(); + init.title = DOMString::from_string(metadata.title.clone()); + init.artist = DOMString::from_string(metadata.artist.clone()); + init.album = DOMString::from_string(metadata.album.clone()); + let global = self.global(); + Some(MediaMetadata::new(&global.as_window(), &init)) + } else { + None + } + } + + /// https://w3c.github.io/mediasession/#dom-mediasession-metadata + fn SetMetadata(&self, metadata: Option<&MediaMetadata>) { + if let Some(ref metadata) = metadata { + metadata.set_session(self); + } + + let global = self.global(); + let window = global.as_window(); + let _metadata = match metadata { + Some(m) => { + let title = if m.Title().is_empty() { + window.get_url().into_string() + } else { + m.Title().into() + }; + EmbedderMediaMetadata { + title, + artist: m.Artist().into(), + album: m.Album().into(), + } + }, + None => EmbedderMediaMetadata::new(window.get_url().into_string()), + }; + + *self.metadata.borrow_mut() = Some(_metadata.clone()); + + self.send_event(MediaSessionEvent::SetMetadata(_metadata)); + } + + /// https://w3c.github.io/mediasession/#dom-mediasession-playbackstate + fn PlaybackState(&self) -> MediaSessionPlaybackState { + *self.playback_state.borrow() + } + + /// https://w3c.github.io/mediasession/#dom-mediasession-playbackstate + fn SetPlaybackState(&self, state: MediaSessionPlaybackState) { + *self.playback_state.borrow_mut() = state; + } + + /// https://w3c.github.io/mediasession/#update-action-handler-algorithm + fn SetActionHandler( + &self, + action: MediaSessionAction, + handler: Option<Rc<MediaSessionActionHandler>>, + ) { + match handler { + Some(handler) => self + .action_handlers + .borrow_mut() + .insert(action.into(), handler.clone()), + None => self.action_handlers.borrow_mut().remove(&action.into()), + }; + } + + /// https://w3c.github.io/mediasession/#dom-mediasession-setpositionstate + fn SetPositionState(&self, state: &MediaPositionState) -> Fallible<()> { + // If the state is an empty dictionary then clear the position state. + if state.duration.is_none() && state.position.is_none() && state.playbackRate.is_none() { + if let Some(media_instance) = self.media_instance.get() { + media_instance.reset(); + } + return Ok(()); + } + + // If the duration is not present or its value is null, throw a TypeError. + if state.duration.is_none() { + return Err(Error::Type( + "duration is not present or its value is null".to_owned(), + )); + } + + // If the duration is negative, throw a TypeError. + if let Some(state_duration) = state.duration { + if *state_duration < 0.0 { + return Err(Error::Type("duration is negative".to_owned())); + } + } + + // If the position is negative or greater than duration, throw a TypeError. + if let Some(state_position) = state.position { + if *state_position < 0.0 { + return Err(Error::Type("position is negative".to_owned())); + } + if let Some(state_duration) = state.duration { + if *state_position > *state_duration { + return Err(Error::Type("position is greater than duration".to_owned())); + } + } + } + + // If the playbackRate is zero throw a TypeError. + if let Some(state_playback_rate) = state.playbackRate { + if *state_playback_rate <= 0.0 { + return Err(Error::Type("playbackRate is zero".to_owned())); + } + } + + // Update the position state and last position updated time. + if let Some(media_instance) = self.media_instance.get() { + media_instance.set_duration(state.duration.map(|v| *v).unwrap()); + // If the playbackRate is not present or its value is null, set it to 1.0. + let _ = + media_instance.SetPlaybackRate(state.playbackRate.unwrap_or(Finite::wrap(1.0)))?; + // If the position is not present or its value is null, set it to zero. + media_instance.SetCurrentTime(state.position.unwrap_or(Finite::wrap(0.0))); + } + + Ok(()) + } +} + +impl From<MediaSessionAction> for MediaSessionActionType { + fn from(action: MediaSessionAction) -> MediaSessionActionType { + match action { + MediaSessionAction::Play => MediaSessionActionType::Play, + MediaSessionAction::Pause => MediaSessionActionType::Pause, + MediaSessionAction::Seekbackward => MediaSessionActionType::SeekBackward, + MediaSessionAction::Seekforward => MediaSessionActionType::SeekForward, + MediaSessionAction::Previoustrack => MediaSessionActionType::PreviousTrack, + MediaSessionAction::Nexttrack => MediaSessionActionType::NextTrack, + MediaSessionAction::Skipad => MediaSessionActionType::SkipAd, + MediaSessionAction::Stop => MediaSessionActionType::Stop, + MediaSessionAction::Seekto => MediaSessionActionType::SeekTo, + } + } +} diff --git a/components/script/dom/mediastream.rs b/components/script/dom/mediastream.rs new file mode 100644 index 00000000000..ff2c8f3a990 --- /dev/null +++ b/components/script/dom/mediastream.rs @@ -0,0 +1,141 @@ +/* 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 crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::MediaStreamBinding::MediaStreamMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::mediastreamtrack::MediaStreamTrack; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::streams::registry::MediaStreamId; +use servo_media::streams::MediaStreamType; + +#[dom_struct] +pub struct MediaStream { + eventtarget: EventTarget, + tracks: DomRefCell<Vec<Dom<MediaStreamTrack>>>, +} + +#[allow(non_snake_case)] +impl MediaStream { + pub fn new_inherited() -> MediaStream { + MediaStream { + eventtarget: EventTarget::new_inherited(), + tracks: DomRefCell::new(vec![]), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<MediaStream> { + reflect_dom_object(Box::new(MediaStream::new_inherited()), global) + } + + pub fn new_single( + global: &GlobalScope, + id: MediaStreamId, + ty: MediaStreamType, + ) -> DomRoot<MediaStream> { + let this = Self::new(global); + let track = MediaStreamTrack::new(global, id, ty); + this.AddTrack(&track); + this + } + + pub fn Constructor(global: &Window) -> Fallible<DomRoot<MediaStream>> { + Ok(MediaStream::new(&global.global())) + } + + pub fn Constructor_(_: &Window, stream: &MediaStream) -> Fallible<DomRoot<MediaStream>> { + Ok(stream.Clone()) + } + + pub fn Constructor__( + global: &Window, + tracks: Vec<DomRoot<MediaStreamTrack>>, + ) -> Fallible<DomRoot<MediaStream>> { + let new = MediaStream::new(&global.global()); + for track in tracks { + // this is quadratic, but shouldn't matter much + // if this becomes a problem we can use a hash map + new.AddTrack(&track) + } + Ok(new) + } + + pub fn get_tracks(&self) -> Ref<[Dom<MediaStreamTrack>]> { + Ref::map(self.tracks.borrow(), |tracks| &**tracks) + } + + pub fn add_track(&self, track: &MediaStreamTrack) { + self.tracks.borrow_mut().push(Dom::from_ref(track)) + } +} + +impl MediaStreamMethods for MediaStream { + /// https://w3c.github.io/mediacapture-main/#dom-mediastream-gettracks + fn GetTracks(&self) -> Vec<DomRoot<MediaStreamTrack>> { + self.tracks + .borrow() + .iter() + .map(|x| DomRoot::from_ref(&**x)) + .collect() + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediastream-getaudiotracks + fn GetAudioTracks(&self) -> Vec<DomRoot<MediaStreamTrack>> { + self.tracks + .borrow() + .iter() + .filter(|x| x.ty() == MediaStreamType::Audio) + .map(|x| DomRoot::from_ref(&**x)) + .collect() + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediastream-getvideotracks + fn GetVideoTracks(&self) -> Vec<DomRoot<MediaStreamTrack>> { + self.tracks + .borrow() + .iter() + .filter(|x| x.ty() == MediaStreamType::Video) + .map(|x| DomRoot::from_ref(&**x)) + .collect() + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediastream-gettrackbyid + fn GetTrackById(&self, id: DOMString) -> Option<DomRoot<MediaStreamTrack>> { + self.tracks + .borrow() + .iter() + .find(|x| x.id().id().to_string() == &*id) + .map(|x| DomRoot::from_ref(&**x)) + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediastream-addtrack + fn AddTrack(&self, track: &MediaStreamTrack) { + let existing = self.tracks.borrow().iter().find(|x| *x == &track).is_some(); + + if existing { + return; + } + self.add_track(track) + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack + fn RemoveTrack(&self, track: &MediaStreamTrack) { + self.tracks.borrow_mut().retain(|x| *x != track); + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediastream-clone + fn Clone(&self) -> DomRoot<MediaStream> { + let new = MediaStream::new(&self.global()); + for track in &*self.tracks.borrow() { + new.add_track(&track) + } + new + } +} diff --git a/components/script/dom/mediastreamaudiodestinationnode.rs b/components/script/dom/mediastreamaudiodestinationnode.rs new file mode 100644 index 00000000000..d402f07b590 --- /dev/null +++ b/components/script/dom/mediastreamaudiodestinationnode.rs @@ -0,0 +1,81 @@ +/* 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 crate::dom::audiocontext::AudioContext; +use crate::dom::audionode::AudioNode; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::AudioNodeOptions; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::Bindings::MediaStreamAudioDestinationNodeBinding::MediaStreamAudioDestinationNodeMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::mediastream::MediaStream; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::node::AudioNodeInit; +use servo_media::streams::MediaStreamType; +use servo_media::ServoMedia; + +#[dom_struct] +pub struct MediaStreamAudioDestinationNode { + node: AudioNode, + stream: Dom<MediaStream>, +} + +impl MediaStreamAudioDestinationNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + context: &AudioContext, + options: &AudioNodeOptions, + ) -> Fallible<MediaStreamAudioDestinationNode> { + let media = ServoMedia::get().unwrap(); + let (socket, id) = media.create_stream_and_socket(MediaStreamType::Audio); + let stream = MediaStream::new_single(&context.global(), id, MediaStreamType::Audio); + let node_options = options.unwrap_or( + 2, + ChannelCountMode::Explicit, + ChannelInterpretation::Speakers, + ); + let node = AudioNode::new_inherited( + AudioNodeInit::MediaStreamDestinationNode(socket), + &context.upcast(), + node_options, + 1, // inputs + 0, // outputs + )?; + Ok(MediaStreamAudioDestinationNode { + node, + stream: Dom::from_ref(&stream), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &AudioContext, + options: &AudioNodeOptions, + ) -> Fallible<DomRoot<MediaStreamAudioDestinationNode>> { + let node = MediaStreamAudioDestinationNode::new_inherited(context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &AudioContext, + options: &AudioNodeOptions, + ) -> Fallible<DomRoot<MediaStreamAudioDestinationNode>> { + MediaStreamAudioDestinationNode::new(window, context, options) + } +} + +impl MediaStreamAudioDestinationNodeMethods for MediaStreamAudioDestinationNode { + /// https://webaudio.github.io/web-audio-api/#dom-mediastreamaudiodestinationnode-stream + fn Stream(&self) -> DomRoot<MediaStream> { + DomRoot::from_ref(&self.stream) + } +} diff --git a/components/script/dom/mediastreamaudiosourcenode.rs b/components/script/dom/mediastreamaudiosourcenode.rs new file mode 100644 index 00000000000..f6925af75c3 --- /dev/null +++ b/components/script/dom/mediastreamaudiosourcenode.rs @@ -0,0 +1,76 @@ +/* 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 crate::dom::audiocontext::AudioContext; +use crate::dom::audionode::AudioNode; +use crate::dom::bindings::codegen::Bindings::MediaStreamAudioSourceNodeBinding::{ + MediaStreamAudioSourceNodeMethods, MediaStreamAudioSourceOptions, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::mediastream::MediaStream; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::node::AudioNodeInit; +use servo_media::streams::MediaStreamType; + +#[dom_struct] +pub struct MediaStreamAudioSourceNode { + node: AudioNode, + stream: Dom<MediaStream>, +} + +impl MediaStreamAudioSourceNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + context: &AudioContext, + stream: &MediaStream, + ) -> Fallible<MediaStreamAudioSourceNode> { + let track = stream + .get_tracks() + .iter() + .find(|t| t.ty() == MediaStreamType::Audio) + .ok_or(Error::InvalidState)? + .id(); + let node = AudioNode::new_inherited( + AudioNodeInit::MediaStreamSourceNode(track), + &context.upcast(), + Default::default(), + 0, // inputs + 1, // outputs + )?; + Ok(MediaStreamAudioSourceNode { + node, + stream: Dom::from_ref(&stream), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &AudioContext, + stream: &MediaStream, + ) -> Fallible<DomRoot<MediaStreamAudioSourceNode>> { + let node = MediaStreamAudioSourceNode::new_inherited(context, stream)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &AudioContext, + options: &MediaStreamAudioSourceOptions, + ) -> Fallible<DomRoot<MediaStreamAudioSourceNode>> { + MediaStreamAudioSourceNode::new(window, context, &options.mediaStream) + } +} + +impl MediaStreamAudioSourceNodeMethods for MediaStreamAudioSourceNode { + /// https://webaudio.github.io/web-audio-api/#dom-MediaStreamAudioSourceNode-stream + fn MediaStream(&self) -> DomRoot<MediaStream> { + DomRoot::from_ref(&self.stream) + } +} diff --git a/components/script/dom/mediastreamtrack.rs b/components/script/dom/mediastreamtrack.rs new file mode 100644 index 00000000000..c5178b3682b --- /dev/null +++ b/components/script/dom/mediastreamtrack.rs @@ -0,0 +1,68 @@ +/* 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 crate::dom::bindings::codegen::Bindings::MediaStreamTrackBinding::MediaStreamTrackMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use servo_media::streams::registry::MediaStreamId; +use servo_media::streams::MediaStreamType; + +#[dom_struct] +pub struct MediaStreamTrack { + eventtarget: EventTarget, + #[ignore_malloc_size_of = "defined in servo-media"] + id: MediaStreamId, + #[ignore_malloc_size_of = "defined in servo-media"] + ty: MediaStreamType, +} + +impl MediaStreamTrack { + pub fn new_inherited(id: MediaStreamId, ty: MediaStreamType) -> MediaStreamTrack { + MediaStreamTrack { + eventtarget: EventTarget::new_inherited(), + id, + ty, + } + } + + pub fn new( + global: &GlobalScope, + id: MediaStreamId, + ty: MediaStreamType, + ) -> DomRoot<MediaStreamTrack> { + reflect_dom_object(Box::new(MediaStreamTrack::new_inherited(id, ty)), global) + } + + pub fn id(&self) -> MediaStreamId { + self.id + } + + pub fn ty(&self) -> MediaStreamType { + self.ty + } +} + +impl MediaStreamTrackMethods for MediaStreamTrack { + /// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-kind + fn Kind(&self) -> DOMString { + match self.ty { + MediaStreamType::Video => "video".into(), + MediaStreamType::Audio => "audio".into(), + } + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-id + fn Id(&self) -> DOMString { + self.id.id().to_string().into() + } + + /// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-clone + fn Clone(&self) -> DomRoot<MediaStreamTrack> { + MediaStreamTrack::new(&self.global(), self.id, self.ty) + } +} diff --git a/components/script/dom/mediastreamtrackaudiosourcenode.rs b/components/script/dom/mediastreamtrackaudiosourcenode.rs new file mode 100644 index 00000000000..6399ee5f6a4 --- /dev/null +++ b/components/script/dom/mediastreamtrackaudiosourcenode.rs @@ -0,0 +1,60 @@ +/* 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 crate::dom::audiocontext::AudioContext; +use crate::dom::audionode::AudioNode; +use crate::dom::bindings::codegen::Bindings::MediaStreamTrackAudioSourceNodeBinding::MediaStreamTrackAudioSourceOptions; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::mediastreamtrack::MediaStreamTrack; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::node::AudioNodeInit; + +#[dom_struct] +pub struct MediaStreamTrackAudioSourceNode { + node: AudioNode, + track: Dom<MediaStreamTrack>, +} + +impl MediaStreamTrackAudioSourceNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + context: &AudioContext, + track: &MediaStreamTrack, + ) -> Fallible<MediaStreamTrackAudioSourceNode> { + let node = AudioNode::new_inherited( + AudioNodeInit::MediaStreamSourceNode(track.id()), + &context.upcast(), + Default::default(), + 0, // inputs + 1, // outputs + )?; + Ok(MediaStreamTrackAudioSourceNode { + node, + track: Dom::from_ref(&track), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &AudioContext, + track: &MediaStreamTrack, + ) -> Fallible<DomRoot<MediaStreamTrackAudioSourceNode>> { + let node = MediaStreamTrackAudioSourceNode::new_inherited(context, track)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &AudioContext, + options: &MediaStreamTrackAudioSourceOptions, + ) -> Fallible<DomRoot<MediaStreamTrackAudioSourceNode>> { + MediaStreamTrackAudioSourceNode::new(window, context, &options.mediaStreamTrack) + } +} diff --git a/components/script/dom/messagechannel.rs b/components/script/dom/messagechannel.rs new file mode 100644 index 00000000000..3075dac3088 --- /dev/null +++ b/components/script/dom/messagechannel.rs @@ -0,0 +1,69 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::MessageChannelBinding::MessageChannelMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageport::MessagePort; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct MessageChannel { + reflector_: Reflector, + port1: Dom<MessagePort>, + port2: Dom<MessagePort>, +} + +impl MessageChannel { + /// <https://html.spec.whatwg.org/multipage/#dom-messagechannel> + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> DomRoot<MessageChannel> { + MessageChannel::new(global) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messagechannel> + pub fn new(incumbent: &GlobalScope) -> DomRoot<MessageChannel> { + // Step 1 + let port1 = MessagePort::new(&incumbent); + + // Step 2 + let port2 = MessagePort::new(&incumbent); + + incumbent.track_message_port(&*port1, None); + incumbent.track_message_port(&*port2, None); + + // Step 3 + incumbent.entangle_ports( + port1.message_port_id().clone(), + port2.message_port_id().clone(), + ); + + // Steps 4-6 + reflect_dom_object( + Box::new(MessageChannel::new_inherited(&*port1, &*port2)), + incumbent, + ) + } + + pub fn new_inherited(port1: &MessagePort, port2: &MessagePort) -> MessageChannel { + MessageChannel { + reflector_: Reflector::new(), + port1: Dom::from_ref(port1), + port2: Dom::from_ref(port2), + } + } +} + +impl MessageChannelMethods for MessageChannel { + /// <https://html.spec.whatwg.org/multipage/#dom-messagechannel-port1> + fn Port1(&self) -> DomRoot<MessagePort> { + DomRoot::from_ref(&*self.port1) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messagechannel-port2> + fn Port2(&self) -> DomRoot<MessagePort> { + DomRoot::from_ref(&*self.port2) + } +} diff --git a/components/script/dom/messageevent.rs b/components/script/dom/messageevent.rs index 0bbeeb05cd7..c00e2bc5dec 100644 --- a/components/script/dom/messageevent.rs +++ b/components/script/dom/messageevent.rs @@ -1,58 +1,138 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::MessageEventBinding; -use dom::bindings::codegen::Bindings::MessageEventBinding::MessageEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::bindings::trace::RootedTraceableBox; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::MessageEventBinding; +use crate::dom::bindings::codegen::Bindings::MessageEventBinding::MessageEventMethods; +use crate::dom::bindings::codegen::UnionTypes::WindowProxyOrMessagePortOrServiceWorker; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::bindings::utils::to_frozen_array; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageport::MessagePort; +use crate::dom::serviceworker::ServiceWorker; +use crate::dom::windowproxy::WindowProxy; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{HandleValue, Heap, JSContext}; +use js::jsapi::Heap; use js::jsval::JSVal; +use js::rust::HandleValue; use servo_atoms::Atom; +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +enum SrcObject { + WindowProxy(Dom<WindowProxy>), + MessagePort(Dom<MessagePort>), + ServiceWorker(Dom<ServiceWorker>), +} + +impl From<&WindowProxyOrMessagePortOrServiceWorker> for SrcObject { + #[allow(unrooted_must_root)] + fn from(src_object: &WindowProxyOrMessagePortOrServiceWorker) -> SrcObject { + match src_object { + WindowProxyOrMessagePortOrServiceWorker::WindowProxy(blob) => { + SrcObject::WindowProxy(Dom::from_ref(&*blob)) + }, + WindowProxyOrMessagePortOrServiceWorker::MessagePort(stream) => { + SrcObject::MessagePort(Dom::from_ref(&*stream)) + }, + WindowProxyOrMessagePortOrServiceWorker::ServiceWorker(stream) => { + SrcObject::ServiceWorker(Dom::from_ref(&*stream)) + }, + } + } +} + #[dom_struct] +#[allow(non_snake_case)] pub struct MessageEvent { event: Event, + #[ignore_malloc_size_of = "mozjs"] data: Heap<JSVal>, - origin: DOMString, - lastEventId: DOMString, + origin: DomRefCell<DOMString>, + source: DomRefCell<Option<SrcObject>>, + lastEventId: DomRefCell<DOMString>, + ports: DomRefCell<Vec<Dom<MessagePort>>>, + #[ignore_malloc_size_of = "mozjs"] + frozen_ports: DomRefCell<Option<Heap<JSVal>>>, } +#[allow(non_snake_case)] impl MessageEvent { - pub fn new_uninitialized(global: &GlobalScope) -> Root<MessageEvent> { - MessageEvent::new_initialized(global, - HandleValue::undefined(), - DOMString::new(), - DOMString::new()) + pub fn new_inherited( + origin: DOMString, + source: Option<&WindowProxyOrMessagePortOrServiceWorker>, + lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, + ) -> MessageEvent { + MessageEvent { + event: Event::new_inherited(), + data: Heap::default(), + source: DomRefCell::new(source.map(|source| source.into())), + origin: DomRefCell::new(origin), + lastEventId: DomRefCell::new(lastEventId), + ports: DomRefCell::new( + ports + .into_iter() + .map(|port| Dom::from_ref(&*port)) + .collect(), + ), + frozen_ports: DomRefCell::new(None), + } } - pub fn new_initialized(global: &GlobalScope, - data: HandleValue, - origin: DOMString, - lastEventId: DOMString) -> Root<MessageEvent> { - let ev = box MessageEvent { - event: Event::new_inherited(), - data: Heap::new(data.get()), - origin: origin, - lastEventId: lastEventId, - }; - reflect_dom_object(ev, global, MessageEventBinding::Wrap) + pub fn new_uninitialized(global: &GlobalScope) -> DomRoot<MessageEvent> { + MessageEvent::new_initialized( + global, + HandleValue::undefined(), + DOMString::new(), + None, + DOMString::new(), + vec![], + ) + } + + pub fn new_initialized( + global: &GlobalScope, + data: HandleValue, + origin: DOMString, + source: Option<&WindowProxyOrMessagePortOrServiceWorker>, + lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, + ) -> DomRoot<MessageEvent> { + let ev = Box::new(MessageEvent::new_inherited( + origin, + source, + lastEventId, + ports, + )); + let ev = reflect_dom_object(ev, global); + ev.data.set(data.get()); + + ev } - pub fn new(global: &GlobalScope, type_: Atom, - bubbles: bool, cancelable: bool, - data: HandleValue, origin: DOMString, lastEventId: DOMString) - -> Root<MessageEvent> { - let ev = MessageEvent::new_initialized(global, data, origin, lastEventId); + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + data: HandleValue, + origin: DOMString, + source: Option<&WindowProxyOrMessagePortOrServiceWorker>, + lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, + ) -> DomRoot<MessageEvent> { + let ev = MessageEvent::new_initialized(global, data, origin, source, lastEventId, ports); { let event = ev.upcast::<Event>(); event.init_event(type_, bubbles, cancelable); @@ -60,56 +140,156 @@ impl MessageEvent { ev } - pub fn Constructor(global: &GlobalScope, - type_: DOMString, - init: RootedTraceableBox<MessageEventBinding::MessageEventInit>) - -> Fallible<Root<MessageEvent>> { - let ev = MessageEvent::new(global, - Atom::from(type_), - init.parent.bubbles, - init.parent.cancelable, - init.data.handle(), - init.origin.clone(), - init.lastEventId.clone()); + pub fn Constructor( + global: &GlobalScope, + type_: DOMString, + init: RootedTraceableBox<MessageEventBinding::MessageEventInit>, + ) -> Fallible<DomRoot<MessageEvent>> { + let ev = MessageEvent::new( + global, + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + init.data.handle(), + init.origin.clone(), + init.source.as_ref(), + init.lastEventId.clone(), + init.ports.clone(), + ); Ok(ev) } } impl MessageEvent { - pub fn dispatch_jsval(target: &EventTarget, - scope: &GlobalScope, - message: HandleValue) { + pub fn dispatch_jsval( + target: &EventTarget, + scope: &GlobalScope, + message: HandleValue, + origin: Option<&str>, + source: Option<&WindowProxy>, + ports: Vec<DomRoot<MessagePort>>, + ) { let messageevent = MessageEvent::new( scope, atom!("message"), false, false, message, + DOMString::from(origin.unwrap_or("")), + source + .map(|source| { + WindowProxyOrMessagePortOrServiceWorker::WindowProxy(DomRoot::from_ref(source)) + }) + .as_ref(), DOMString::new(), - DOMString::new()); + ports, + ); + messageevent.upcast::<Event>().fire(target); + } + + pub fn dispatch_error(target: &EventTarget, scope: &GlobalScope) { + let init = MessageEventBinding::MessageEventInit::empty(); + let messageevent = MessageEvent::new( + scope, + atom!("messageerror"), + init.parent.bubbles, + init.parent.cancelable, + init.data.handle(), + init.origin.clone(), + init.source.as_ref(), + init.lastEventId.clone(), + init.ports.clone(), + ); messageevent.upcast::<Event>().fire(target); } } impl MessageEventMethods for MessageEvent { - #[allow(unsafe_code)] - // https://html.spec.whatwg.org/multipage/#dom-messageevent-data - unsafe fn Data(&self, _cx: *mut JSContext) -> JSVal { + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-data> + fn Data(&self, _cx: JSContext) -> JSVal { self.data.get() } - // https://html.spec.whatwg.org/multipage/#dom-messageevent-origin + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-origin> fn Origin(&self) -> DOMString { - self.origin.clone() + self.origin.borrow().clone() + } + + // https://html.spec.whatwg.org/multipage/#dom-messageevent-source + fn GetSource(&self) -> Option<WindowProxyOrMessagePortOrServiceWorker> { + match &*self.source.borrow() { + Some(SrcObject::WindowProxy(i)) => Some( + WindowProxyOrMessagePortOrServiceWorker::WindowProxy(DomRoot::from_ref(&*i)), + ), + Some(SrcObject::MessagePort(i)) => Some( + WindowProxyOrMessagePortOrServiceWorker::MessagePort(DomRoot::from_ref(&*i)), + ), + Some(SrcObject::ServiceWorker(i)) => Some( + WindowProxyOrMessagePortOrServiceWorker::ServiceWorker(DomRoot::from_ref(&*i)), + ), + None => None, + } } - // https://html.spec.whatwg.org/multipage/#dom-messageevent-lasteventid + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-lasteventid> fn LastEventId(&self) -> DOMString { - self.lastEventId.clone() + self.lastEventId.borrow().clone() } - // https://dom.spec.whatwg.org/#dom-event-istrusted + /// <https://dom.spec.whatwg.org/#dom-event-istrusted> fn IsTrusted(&self) -> bool { self.event.IsTrusted() } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-ports> + fn Ports(&self, cx: JSContext) -> JSVal { + if let Some(ports) = &*self.frozen_ports.borrow() { + return ports.get(); + } + + let ports: Vec<DomRoot<MessagePort>> = self + .ports + .borrow() + .iter() + .map(|port| DomRoot::from_ref(&**port)) + .collect(); + let frozen_ports = to_frozen_array(ports.as_slice(), cx); + + // Safety: need to create the Heap value in its final memory location before setting it. + *self.frozen_ports.borrow_mut() = Some(Heap::default()); + self.frozen_ports + .borrow() + .as_ref() + .unwrap() + .set(frozen_ports); + + frozen_ports + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-initmessageevent> + #[allow(non_snake_case)] + fn InitMessageEvent( + &self, + _cx: JSContext, + type_: DOMString, + bubbles: bool, + cancelable: bool, + data: HandleValue, + origin: DOMString, + lastEventId: DOMString, + source: Option<WindowProxyOrMessagePortOrServiceWorker>, + ports: Vec<DomRoot<MessagePort>>, + ) { + self.data.set(data.get()); + *self.origin.borrow_mut() = origin.clone(); + *self.source.borrow_mut() = source.as_ref().map(|source| source.into()); + *self.lastEventId.borrow_mut() = lastEventId.clone(); + *self.ports.borrow_mut() = ports + .into_iter() + .map(|port| Dom::from_ref(&*port)) + .collect(); + *self.frozen_ports.borrow_mut() = None; + self.event + .init_event(Atom::from(type_), bubbles, cancelable); + } } diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs new file mode 100644 index 00000000000..fee7c75a9eb --- /dev/null +++ b/components/script/dom/messageport.rs @@ -0,0 +1,342 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::{ + MessagePortMethods, PostMessageOptions, +}; +use crate::dom::bindings::conversions::root_from_object; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::structuredclone::{self, StructuredDataHolder}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::bindings::transferable::Transferable; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext as SafeJSContext; +use dom_struct::dom_struct; +use js::jsapi::Heap; +use js::jsapi::{JSObject, MutableHandleObject}; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; +use msg::constellation_msg::{MessagePortId, MessagePortIndex, PipelineNamespaceId}; +use script_traits::PortMessageTask; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::convert::TryInto; +use std::num::NonZeroU32; +use std::rc::Rc; + +#[dom_struct] +/// The MessagePort used in the DOM. +pub struct MessagePort { + eventtarget: EventTarget, + message_port_id: MessagePortId, + entangled_port: RefCell<Option<MessagePortId>>, + detached: Cell<bool>, +} + +impl MessagePort { + fn new_inherited(message_port_id: MessagePortId) -> MessagePort { + MessagePort { + eventtarget: EventTarget::new_inherited(), + entangled_port: RefCell::new(None), + detached: Cell::new(false), + message_port_id, + } + } + + /// <https://html.spec.whatwg.org/multipage/#create-a-new-messageport-object> + pub fn new(owner: &GlobalScope) -> DomRoot<MessagePort> { + let port_id = MessagePortId::new(); + reflect_dom_object(Box::new(MessagePort::new_inherited(port_id)), owner) + } + + /// Create a new port for an incoming transfer-received one. + fn new_transferred( + owner: &GlobalScope, + transferred_port: MessagePortId, + entangled_port: Option<MessagePortId>, + ) -> DomRoot<MessagePort> { + reflect_dom_object( + Box::new(MessagePort { + message_port_id: transferred_port, + eventtarget: EventTarget::new_inherited(), + detached: Cell::new(false), + entangled_port: RefCell::new(entangled_port), + }), + owner, + ) + } + + /// <https://html.spec.whatwg.org/multipage/#entangle> + pub fn entangle(&self, other_id: MessagePortId) { + *self.entangled_port.borrow_mut() = Some(other_id); + } + + pub fn message_port_id(&self) -> &MessagePortId { + &self.message_port_id + } + + pub fn detached(&self) -> bool { + self.detached.get() + } + + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> + fn set_onmessage(&self, listener: Option<Rc<EventHandlerNonNull>>) { + let eventtarget = self.upcast::<EventTarget>(); + eventtarget.set_event_handler_common("message", listener); + } + + /// <https://html.spec.whatwg.org/multipage/#message-port-post-message-steps> + fn post_message_impl( + &self, + cx: SafeJSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + if self.detached.get() { + return Ok(()); + } + + // Step 1 is the transfer argument. + + let target_port = self.entangled_port.borrow(); + + // Step 3 + let mut doomed = false; + + let ports = transfer + .iter() + .filter_map(|&obj| root_from_object::<MessagePort>(obj, *cx).ok()); + for port in ports { + // Step 2 + if port.message_port_id() == self.message_port_id() { + return Err(Error::DataClone); + } + + // Step 4 + if let Some(target_id) = target_port.as_ref() { + if port.message_port_id() == target_id { + doomed = true; + } + } + } + + // Step 5 + let data = structuredclone::write(cx, message, Some(transfer))?; + + if doomed { + // TODO: The spec says to optionally report such a case to a dev console. + return Ok(()); + } + + // Step 6, done in MessagePortImpl. + + let incumbent = match GlobalScope::incumbent() { + None => unreachable!("postMessage called with no incumbent global"), + Some(incumbent) => incumbent, + }; + + // Step 7 + let task = PortMessageTask { + origin: incumbent.origin().immutable().clone(), + data, + }; + + // Have the global proxy this call to the corresponding MessagePortImpl. + self.global() + .post_messageport_msg(self.message_port_id().clone(), task); + Ok(()) + } +} + +impl Transferable for MessagePort { + /// <https://html.spec.whatwg.org/multipage/#message-ports:transfer-steps> + fn transfer(&self, sc_holder: &mut StructuredDataHolder) -> Result<u64, ()> { + if self.detached.get() { + return Err(()); + } + + let port_impls = match sc_holder { + StructuredDataHolder::Write { ports, .. } => ports, + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; + + self.detached.set(true); + let id = self.message_port_id(); + + // 1. Run local transfer logic, and return the object to be transferred. + let transferred_port = self.global().mark_port_as_transferred(id); + + // 2. Store the transferred object at a given key. + if let Some(ports) = port_impls.as_mut() { + ports.insert(id.clone(), transferred_port); + } else { + let mut ports = HashMap::new(); + ports.insert(id.clone(), transferred_port); + *port_impls = Some(ports); + } + + let PipelineNamespaceId(name_space) = id.clone().namespace_id; + let MessagePortIndex(index) = id.clone().index; + let index = index.get(); + + let mut big: [u8; 8] = [0; 8]; + let name_space = name_space.to_ne_bytes(); + let index = index.to_ne_bytes(); + + let (left, right) = big.split_at_mut(4); + left.copy_from_slice(&name_space); + right.copy_from_slice(&index); + + // 3. Return a u64 representation of the key where the object is stored. + Ok(u64::from_ne_bytes(big)) + } + + /// https://html.spec.whatwg.org/multipage/#message-ports:transfer-receiving-steps + fn transfer_receive( + owner: &GlobalScope, + sc_holder: &mut StructuredDataHolder, + extra_data: u64, + return_object: MutableHandleObject, + ) -> Result<(), ()> { + let (message_ports, port_impls) = match sc_holder { + StructuredDataHolder::Read { + message_ports, + port_impls, + .. + } => (message_ports, port_impls), + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; + + // 1. Re-build the key for the storage location + // of the transferred object. + let big: [u8; 8] = extra_data.to_ne_bytes(); + let (name_space, index) = big.split_at(4); + + let namespace_id = PipelineNamespaceId(u32::from_ne_bytes( + name_space + .try_into() + .expect("name_space to be a slice of four."), + )); + let index = MessagePortIndex( + NonZeroU32::new(u32::from_ne_bytes( + index.try_into().expect("index to be a slice of four."), + )) + .expect("Index to be non-zero"), + ); + + let id = MessagePortId { + namespace_id, + index, + }; + + // 2. Get the transferred object from its storage, using the key. + // Assign the transfer-received port-impl, and total number of transferred ports. + let (ports_len, port_impl) = if let Some(ports) = port_impls.as_mut() { + let ports_len = ports.len(); + let port_impl = ports.remove(&id).expect("Transferred port to be stored"); + if ports.is_empty() { + *port_impls = None; + } + (ports_len, port_impl) + } else { + panic!("A messageport was transfer-received, yet the SC holder does not have any port impls"); + }; + + let transferred_port = + MessagePort::new_transferred(&*owner, id.clone(), port_impl.entangled_port_id()); + owner.track_message_port(&transferred_port, Some(port_impl)); + + return_object.set(transferred_port.reflector().rootable().get()); + + // Store the DOM port where it will be passed along to script in the message-event. + if let Some(ports) = message_ports.as_mut() { + ports.push(transferred_port); + } else { + let mut ports = Vec::with_capacity(ports_len); + ports.push(transferred_port); + *message_ports = Some(ports); + } + + Ok(()) + } +} + +impl MessagePortMethods for MessagePort { + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + fn PostMessage( + &self, + cx: SafeJSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + if self.detached.get() { + return Ok(()); + } + self.post_message_impl(cx, message, transfer) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + fn PostMessage_( + &self, + cx: SafeJSContext, + message: HandleValue, + options: RootedTraceableBox<PostMessageOptions>, + ) -> ErrorResult { + if self.detached.get() { + return Ok(()); + } + let mut rooted = CustomAutoRooter::new( + options + .transfer + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); + self.post_message_impl(cx, message, guard) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start> + fn Start(&self) { + if self.detached.get() { + return; + } + self.global().start_message_port(self.message_port_id()); + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-close> + fn Close(&self) { + if self.detached.get() { + return; + } + self.detached.set(true); + self.global().close_message_port(self.message_port_id()); + } + + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> + fn GetOnmessage(&self) -> Option<Rc<EventHandlerNonNull>> { + if self.detached.get() { + return None; + } + let eventtarget = self.upcast::<EventTarget>(); + eventtarget.get_event_handler_common("message") + } + + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> + fn SetOnmessage(&self, listener: Option<Rc<EventHandlerNonNull>>) { + if self.detached.get() { + return; + } + self.set_onmessage(listener); + // Note: we cannot use the event_handler macro, due to the need to start the port. + self.global().start_message_port(self.message_port_id()); + } + + // <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessageerror> + event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); +} diff --git a/components/script/dom/mimetype.rs b/components/script/dom/mimetype.rs index daa49908408..5e4db84fe27 100644 --- a/components/script/dom/mimetype.rs +++ b/components/script/dom/mimetype.rs @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::MimeTypeBinding::MimeTypeMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::Reflector; -use dom::bindings::str::DOMString; -use dom::plugin::Plugin; +use crate::dom::bindings::codegen::Bindings::MimeTypeBinding::MimeTypeMethods; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::plugin::Plugin; use dom_struct::dom_struct; #[dom_struct] @@ -31,7 +31,7 @@ impl MimeTypeMethods for MimeType { } // https://html.spec.whatwg.org/multipage/#dom-mimetype-enabledplugin - fn EnabledPlugin(&self) -> Root<Plugin> { + fn EnabledPlugin(&self) -> DomRoot<Plugin> { unreachable!() } } diff --git a/components/script/dom/mimetypearray.rs b/components/script/dom/mimetypearray.rs index 4bc33e9b237..9bbda92e231 100644 --- a/components/script/dom/mimetypearray.rs +++ b/components/script/dom/mimetypearray.rs @@ -1,14 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::MimeTypeArrayBinding; -use dom::bindings::codegen::Bindings::MimeTypeArrayBinding::MimeTypeArrayMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::globalscope::GlobalScope; -use dom::mimetype::MimeType; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::MimeTypeArrayBinding::MimeTypeArrayMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::mimetype::MimeType; use dom_struct::dom_struct; #[dom_struct] @@ -19,14 +18,12 @@ pub struct MimeTypeArray { impl MimeTypeArray { pub fn new_inherited() -> MimeTypeArray { MimeTypeArray { - reflector_: Reflector::new() + reflector_: Reflector::new(), } } - pub fn new(global: &GlobalScope) -> Root<MimeTypeArray> { - reflect_dom_object(box MimeTypeArray::new_inherited(), - global, - MimeTypeArrayBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<MimeTypeArray> { + reflect_dom_object(Box::new(MimeTypeArray::new_inherited()), global) } } @@ -37,22 +34,22 @@ impl MimeTypeArrayMethods for MimeTypeArray { } // https://html.spec.whatwg.org/multipage/#dom-mimetypearray-item - fn Item(&self, _index: u32) -> Option<Root<MimeType>> { + fn Item(&self, _index: u32) -> Option<DomRoot<MimeType>> { None } // https://html.spec.whatwg.org/multipage/#dom-mimetypearray-nameditem - fn NamedItem(&self, _name: DOMString) -> Option<Root<MimeType>> { + fn NamedItem(&self, _name: DOMString) -> Option<DomRoot<MimeType>> { None } // https://html.spec.whatwg.org/multipage/#dom-mimetypearray-item - fn IndexedGetter(&self, _index: u32) -> Option<Root<MimeType>> { + fn IndexedGetter(&self, _index: u32) -> Option<DomRoot<MimeType>> { None } // check-tidy: no specs after this line - fn NamedGetter(&self, _name: DOMString) -> Option<Root<MimeType>> { + fn NamedGetter(&self, _name: DOMString) -> Option<DomRoot<MimeType>> { None } diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 70d1f30bd4a..9120ce7c9f4 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The implementation of the DOM. //! @@ -30,10 +30,10 @@ //! For more information, see: //! //! * rooting pointers on the stack: -//! the [`Root`](bindings/js/struct.Root.html) smart pointer; -//! * tracing pointers in member fields: the [`JS`](bindings/js/struct.JS.html), -//! [`MutNullableJS`](bindings/js/struct.MutNullableJS.html) and -//! [`MutJS`](bindings/js/struct.MutJS.html) smart pointers and +//! the [`Root`](bindings/root/struct.Root.html) smart pointer; +//! * tracing pointers in member fields: the [`Dom`](bindings/root/struct.Dom.html), +//! [`MutNullableDom`](bindings/root/struct.MutNullableDom.html) and +//! [`MutDom`](bindings/root/struct.MutDom.html) smart pointers and //! [the tracing implementation](bindings/trace/index.html); //! * rooting pointers from across thread boundaries or in channels: the //! [`Trusted`](bindings/refcounted/struct.Trusted.html) smart pointer; @@ -44,7 +44,7 @@ //! Rust does not support struct inheritance, as would be used for the //! object-oriented DOM APIs. To work around this issue, Servo stores an //! instance of the superclass in the first field of its subclasses. (Note that -//! it is stored by value, rather than in a smart pointer such as `JS<T>`.) +//! it is stored by value, rather than in a smart pointer such as `Dom<T>`.) //! //! This implies that a pointer to an object can safely be cast to a pointer //! to all its classes. @@ -94,7 +94,7 @@ //! DOM objects of type `T` in Servo have two constructors: //! //! * a `T::new_inherited` static method that returns a plain `T`, and -//! * a `T::new` static method that returns `Root<T>`. +//! * a `T::new` static method that returns `DomRoot<T>`. //! //! (The result of either method can be wrapped in `Result`, if that is //! appropriate for the type in question.) @@ -123,7 +123,7 @@ //! //! Reflectors are JavaScript objects, and as such can be freely aliased. As //! Rust does not allow mutable aliasing, mutable borrows of DOM objects are -//! not allowed. In particular, any mutable fields use `Cell` or `DOMRefCell` +//! not allowed. In particular, any mutable fields use `Cell` or `DomRefCell` //! to manage their mutability. //! //! `Reflector` and `DomObject` @@ -195,29 +195,40 @@ //! ================================= //! //! Layout code can access the DOM through the -//! [`LayoutJS`](bindings/js/struct.LayoutJS.html) smart pointer. This does not +//! [`LayoutDom`](bindings/root/struct.LayoutDom.html) smart pointer. This does not //! keep the DOM object alive; we ensure that no DOM code (Garbage Collection //! in particular) runs while the layout thread is accessing the DOM. //! -//! Methods accessible to layout are implemented on `LayoutJS<Foo>` using +//! Methods accessible to layout are implemented on `LayoutDom<Foo>` using //! `LayoutFooHelpers` traits. #[macro_use] pub mod macros; pub mod types { - #[cfg(not(target_env = "msvc"))] include!(concat!(env!("OUT_DIR"), "/InterfaceTypes.rs")); - #[cfg(target_env = "msvc")] - include!(concat!(env!("OUT_DIR"), "/build/InterfaceTypes.rs")); } pub mod abstractworker; pub mod abstractworkerglobalscope; pub mod activation; +pub mod analysernode; +pub mod animationevent; pub mod attr; +pub mod audiobuffer; +pub mod audiobuffersourcenode; +pub mod audiocontext; +pub mod audiodestinationnode; +pub mod audiolistener; +pub mod audionode; +pub mod audioparam; +pub mod audioscheduledsourcenode; +pub mod audiotrack; +pub mod audiotracklist; +pub mod baseaudiocontext; pub mod beforeunloadevent; pub mod bindings; +pub mod biquadfilternode; pub mod blob; pub mod bluetooth; pub mod bluetoothadvertisingevent; @@ -229,15 +240,20 @@ pub mod bluetoothremotegattdescriptor; pub mod bluetoothremotegattserver; pub mod bluetoothremotegattservice; pub mod bluetoothuuid; -pub mod browsingcontext; +pub mod broadcastchannel; pub mod canvasgradient; pub mod canvaspattern; pub mod canvasrenderingcontext2d; +pub mod cdatasection; +pub mod channelmergernode; +pub mod channelsplitternode; pub mod characterdata; pub mod client; pub mod closeevent; pub mod comment; +pub mod compositionevent; pub mod console; +pub mod constantsourcenode; mod create; pub mod crypto; pub mod css; @@ -254,14 +270,17 @@ pub mod cssrulelist; pub mod cssstyledeclaration; pub mod cssstylerule; pub mod cssstylesheet; +pub mod cssstylevalue; pub mod csssupportsrule; pub mod cssviewportrule; +pub mod customelementregistry; pub mod customevent; pub mod dedicatedworkerglobalscope; pub mod dissimilaroriginlocation; pub mod dissimilaroriginwindow; pub mod document; pub mod documentfragment; +pub mod documentorshadowroot; pub mod documenttype; pub mod domexception; pub mod domimplementation; @@ -273,8 +292,10 @@ pub mod dompointreadonly; pub mod domquad; pub mod domrect; pub mod domrectreadonly; +pub mod domstringlist; pub mod domstringmap; pub mod domtokenlist; +pub mod dynamicmoduleowner; pub mod element; pub mod errorevent; pub mod event; @@ -282,24 +303,58 @@ pub mod eventsource; pub mod eventtarget; pub mod extendableevent; pub mod extendablemessageevent; +pub mod fakexrdevice; +pub mod fakexrinputcontroller; pub mod file; pub mod filelist; pub mod filereader; pub mod filereadersync; pub mod focusevent; -pub mod forcetouchevent; pub mod formdata; +pub mod formdataevent; +pub mod gainnode; pub mod gamepad; pub mod gamepadbutton; pub mod gamepadbuttonlist; pub mod gamepadevent; pub mod gamepadlist; +pub mod gamepadpose; pub mod globalscope; +pub mod gpu; +pub mod gpuadapter; +pub mod gpubindgroup; +pub mod gpubindgrouplayout; +pub mod gpubuffer; +pub mod gpubufferusage; +pub mod gpucanvascontext; +pub mod gpucolorwrite; +pub mod gpucommandbuffer; +pub mod gpucommandencoder; +pub mod gpucomputepassencoder; +pub mod gpucomputepipeline; +pub mod gpudevice; +pub mod gpudevicelostinfo; +pub mod gpumapmode; +pub mod gpuoutofmemoryerror; +pub mod gpupipelinelayout; +pub mod gpuqueue; +pub mod gpurenderbundle; +pub mod gpurenderbundleencoder; +pub mod gpurenderpassencoder; +pub mod gpurenderpipeline; +pub mod gpusampler; +pub mod gpushadermodule; +pub mod gpushaderstage; +pub mod gpuswapchain; +pub mod gputexture; +pub mod gputextureusage; +pub mod gputextureview; +pub mod gpuuncapturederrorevent; +pub mod gpuvalidationerror; pub mod hashchangeevent; pub mod headers; pub mod history; pub mod htmlanchorelement; -pub mod htmlappletelement; pub mod htmlareaelement; pub mod htmlaudioelement; pub mod htmlbaseelement; @@ -336,6 +391,7 @@ pub mod htmllielement; pub mod htmllinkelement; pub mod htmlmapelement; pub mod htmlmediaelement; +pub mod htmlmenuelement; pub mod htmlmetaelement; pub mod htmlmeterelement; pub mod htmlmodelement; @@ -347,6 +403,7 @@ pub mod htmloptionscollection; pub mod htmloutputelement; pub mod htmlparagraphelement; pub mod htmlparamelement; +pub mod htmlpictureelement; pub mod htmlpreelement; pub mod htmlprogresselement; pub mod htmlquoteelement; @@ -358,9 +415,7 @@ pub mod htmlstyleelement; pub mod htmltablecaptionelement; pub mod htmltablecellelement; pub mod htmltablecolelement; -pub mod htmltabledatacellelement; pub mod htmltableelement; -pub mod htmltableheadercellelement; pub mod htmltablerowelement; pub mod htmltablesectionelement; pub mod htmltemplateelement; @@ -371,28 +426,62 @@ pub mod htmltrackelement; pub mod htmlulistelement; pub mod htmlunknownelement; pub mod htmlvideoelement; +pub mod identityhub; +pub mod imagebitmap; pub mod imagedata; +pub mod inputevent; pub mod keyboardevent; pub mod location; +pub mod mediadeviceinfo; +pub mod mediadevices; +pub mod mediaelementaudiosourcenode; pub mod mediaerror; +pub mod mediafragmentparser; pub mod medialist; +pub mod mediametadata; pub mod mediaquerylist; pub mod mediaquerylistevent; +pub mod mediasession; +pub mod mediastream; +pub mod mediastreamaudiodestinationnode; +pub mod mediastreamaudiosourcenode; +pub mod mediastreamtrack; +pub mod mediastreamtrackaudiosourcenode; +pub mod messagechannel; pub mod messageevent; +pub mod messageport; pub mod mimetype; pub mod mimetypearray; pub mod mouseevent; pub mod mutationobserver; pub mod mutationrecord; pub mod namednodemap; +pub mod navigationpreloadmanager; pub mod navigator; pub mod navigatorinfo; pub mod node; pub mod nodeiterator; pub mod nodelist; +pub mod offlineaudiocompletionevent; +pub mod offlineaudiocontext; +pub mod offscreencanvas; +pub mod offscreencanvasrenderingcontext2d; +pub mod oscillatornode; pub mod pagetransitionevent; +pub mod paintrenderingcontext2d; +pub mod paintsize; +pub mod paintworkletglobalscope; +pub mod pannernode; pub mod performance; -pub mod performancetiming; +pub mod performanceentry; +pub mod performancemark; +pub mod performancemeasure; +pub mod performancenavigation; +pub mod performancenavigationtiming; +pub mod performanceobserver; +pub mod performanceobserverentrylist; +pub mod performancepainttiming; +pub mod performanceresourcetiming; pub mod permissions; pub mod permissionstatus; pub mod plugin; @@ -402,20 +491,39 @@ pub mod processinginstruction; pub mod progressevent; pub mod promise; pub mod promisenativehandler; +pub mod promiserejectionevent; pub mod radionodelist; pub mod range; +pub mod raredata; +pub mod readablestream; pub mod request; pub mod response; +pub mod rtcdatachannel; +pub mod rtcdatachannelevent; +pub mod rtcerror; +pub mod rtcerrorevent; +pub mod rtcicecandidate; +pub mod rtcpeerconnection; +pub mod rtcpeerconnectioniceevent; +pub(crate) mod rtcrtpsender; +pub(crate) mod rtcrtptransceiver; +pub mod rtcsessiondescription; +pub mod rtctrackevent; pub mod screen; +pub mod selection; pub mod serviceworker; pub mod serviceworkercontainer; pub mod serviceworkerglobalscope; pub mod serviceworkerregistration; pub mod servoparser; +pub mod shadowroot; +pub mod stereopannernode; pub mod storage; pub mod storageevent; +pub mod stylepropertymapreadonly; pub mod stylesheet; pub mod stylesheetlist; +pub mod submitevent; pub mod svgelement; pub mod svggraphicselement; pub mod svgsvgelement; @@ -424,12 +532,22 @@ pub mod testbindingiterable; pub mod testbindingpairiterable; pub mod testbindingproxy; pub mod testrunner; +pub mod testworklet; +pub mod testworkletglobalscope; pub mod text; +pub mod textcontrol; pub mod textdecoder; pub mod textencoder; +pub mod textmetrics; +pub mod texttrack; +pub mod texttrackcue; +pub mod texttrackcuelist; +pub mod texttracklist; +pub mod timeranges; pub mod touch; pub mod touchevent; pub mod touchlist; +pub mod trackevent; pub mod transitionevent; pub mod treewalker; pub mod uievent; @@ -440,16 +558,14 @@ pub mod userscripts; pub mod validation; pub mod validitystate; pub mod values; +pub mod vertexarrayobject; +pub mod videotrack; +pub mod videotracklist; pub mod virtualmethods; -pub mod vr; -pub mod vrdisplay; -pub mod vrdisplaycapabilities; -pub mod vrdisplayevent; -pub mod vreyeparameters; -pub mod vrfieldofview; -pub mod vrframedata; -pub mod vrpose; -pub mod vrstageparameters; +pub mod vttcue; +pub mod vttregion; +pub mod webgl2renderingcontext; +pub mod webgl_extensions; pub mod webgl_validations; pub mod webglactiveinfo; pub mod webglbuffer; @@ -457,19 +573,67 @@ pub mod webglcontextevent; pub mod webglframebuffer; pub mod webglobject; pub mod webglprogram; +pub mod webglquery; pub mod webglrenderbuffer; pub mod webglrenderingcontext; +pub mod webglsampler; pub mod webglshader; pub mod webglshaderprecisionformat; +pub mod webglsync; pub mod webgltexture; +pub mod webgltransformfeedback; pub mod webgluniformlocation; +pub mod webglvertexarrayobject; +pub mod webglvertexarrayobjectoes; pub mod websocket; +pub mod wheelevent; pub mod window; +pub mod windowproxy; pub mod worker; pub mod workerglobalscope; pub mod workerlocation; pub mod workernavigator; +pub mod worklet; +pub mod workletglobalscope; pub mod xmldocument; pub mod xmlhttprequest; pub mod xmlhttprequesteventtarget; pub mod xmlhttprequestupload; +pub mod xmlserializer; +pub mod xrcompositionlayer; +pub mod xrcubelayer; +pub mod xrcylinderlayer; +pub mod xrequirectlayer; +pub mod xrframe; +pub mod xrhand; +pub mod xrhittestresult; +pub mod xrhittestsource; +pub mod xrinputsource; +pub mod xrinputsourcearray; +pub mod xrinputsourceevent; +pub mod xrinputsourceschangeevent; +pub mod xrjointpose; +pub mod xrjointspace; +pub mod xrlayer; +pub mod xrlayerevent; +pub mod xrmediabinding; +pub mod xrpose; +pub mod xrprojectionlayer; +pub mod xrquadlayer; +pub mod xrray; +pub mod xrreferencespace; +pub mod xrrenderstate; +pub mod xrrigidtransform; +pub mod xrsession; +pub mod xrsessionevent; +pub mod xrspace; +pub mod xrsubimage; +pub mod xrsystem; +pub mod xrtest; +pub mod xrview; +pub mod xrviewerpose; +pub mod xrviewport; +pub mod xrwebglbinding; +pub mod xrwebgllayer; +pub mod xrwebglsubimage; +pub use self::webgl_extensions::ext::*; diff --git a/components/script/dom/mouseevent.rs b/components/script/dom/mouseevent.rs index f19c37df868..d5fbe6b15e8 100644 --- a/components/script/dom/mouseevent.rs +++ b/components/script/dom/mouseevent.rs @@ -1,21 +1,24 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::MouseEventBinding; -use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; -use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root, RootedReference}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::eventtarget::EventTarget; -use dom::uievent::UIEvent; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::MouseEventBinding; +use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; +use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::node::Node; +use crate::dom::uievent::UIEvent; +use crate::dom::window::Window; use dom_struct::dom_struct; -use servo_config::prefs::PREFS; +use euclid::default::Point2D; +use servo_config::pref; use std::cell::Cell; use std::default::Default; @@ -26,79 +29,131 @@ pub struct MouseEvent { screen_y: Cell<i32>, client_x: Cell<i32>, client_y: Cell<i32>, + page_x: Cell<i32>, + page_y: Cell<i32>, + x: Cell<i32>, + y: Cell<i32>, + offset_x: Cell<i32>, + offset_y: Cell<i32>, ctrl_key: Cell<bool>, shift_key: Cell<bool>, alt_key: Cell<bool>, meta_key: Cell<bool>, button: Cell<i16>, - related_target: MutNullableJS<EventTarget>, + buttons: Cell<u16>, + related_target: MutNullableDom<EventTarget>, + point_in_target: Cell<Option<Point2D<f32>>>, } impl MouseEvent { - fn new_inherited() -> MouseEvent { + pub fn new_inherited() -> MouseEvent { MouseEvent { uievent: UIEvent::new_inherited(), screen_x: Cell::new(0), screen_y: Cell::new(0), client_x: Cell::new(0), client_y: Cell::new(0), + page_x: Cell::new(0), + page_y: Cell::new(0), + x: Cell::new(0), + y: Cell::new(0), + offset_x: Cell::new(0), + offset_y: Cell::new(0), ctrl_key: Cell::new(false), shift_key: Cell::new(false), alt_key: Cell::new(false), meta_key: Cell::new(false), button: Cell::new(0), + buttons: Cell::new(0), related_target: Default::default(), + point_in_target: Cell::new(None), } } - pub fn new_uninitialized(window: &Window) -> Root<MouseEvent> { - reflect_dom_object(box MouseEvent::new_inherited(), - window, - MouseEventBinding::Wrap) - } - - pub fn new(window: &Window, - type_: DOMString, - can_bubble: EventBubbles, - cancelable: EventCancelable, - view: Option<&Window>, - detail: i32, - screen_x: i32, - screen_y: i32, - client_x: i32, - client_y: i32, - ctrl_key: bool, - alt_key: bool, - shift_key: bool, - meta_key: bool, - button: i16, - related_target: Option<&EventTarget>) -> Root<MouseEvent> { + pub fn new_uninitialized(window: &Window) -> DomRoot<MouseEvent> { + reflect_dom_object(Box::new(MouseEvent::new_inherited()), window) + } + + pub fn new( + window: &Window, + type_: DOMString, + can_bubble: EventBubbles, + cancelable: EventCancelable, + view: Option<&Window>, + detail: i32, + screen_x: i32, + screen_y: i32, + client_x: i32, + client_y: i32, + ctrl_key: bool, + alt_key: bool, + shift_key: bool, + meta_key: bool, + button: i16, + buttons: u16, + related_target: Option<&EventTarget>, + point_in_target: Option<Point2D<f32>>, + ) -> DomRoot<MouseEvent> { let ev = MouseEvent::new_uninitialized(window); - ev.InitMouseEvent(type_, bool::from(can_bubble), bool::from(cancelable), - view, detail, - screen_x, screen_y, client_x, client_y, - ctrl_key, alt_key, shift_key, meta_key, - button, related_target); + ev.InitMouseEvent( + type_, + bool::from(can_bubble), + bool::from(cancelable), + view, + detail, + screen_x, + screen_y, + client_x, + client_y, + ctrl_key, + alt_key, + shift_key, + meta_key, + button, + related_target, + ); + ev.buttons.set(buttons); + ev.point_in_target.set(point_in_target); + // TODO: Set proper values in https://github.com/servo/servo/issues/24415 + ev.page_x.set(client_x); + ev.page_y.set(client_y); ev } - pub fn Constructor(window: &Window, - type_: DOMString, - init: &MouseEventBinding::MouseEventInit) -> Fallible<Root<MouseEvent>> { + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &MouseEventBinding::MouseEventInit, + ) -> Fallible<DomRoot<MouseEvent>> { let bubbles = EventBubbles::from(init.parent.parent.parent.bubbles); let cancelable = EventCancelable::from(init.parent.parent.parent.cancelable); - let event = MouseEvent::new(window, - type_, - bubbles, - cancelable, - init.parent.parent.view.r(), - init.parent.parent.detail, - init.screenX, init.screenY, - init.clientX, init.clientY, init.parent.ctrlKey, - init.parent.altKey, init.parent.shiftKey, init.parent.metaKey, - init.button, init.relatedTarget.r()); + let event = MouseEvent::new( + window, + type_, + bubbles, + cancelable, + init.parent.parent.view.as_deref(), + init.parent.parent.detail, + init.screenX, + init.screenY, + init.clientX, + init.clientY, + init.parent.ctrlKey, + init.parent.altKey, + init.parent.shiftKey, + init.parent.metaKey, + init.button, + init.buttons, + init.relatedTarget.as_deref(), + None, + ); Ok(event) } + + pub fn point_in_target(&self) -> Option<Point2D<f32>> { + self.point_in_target.get() + } } impl MouseEventMethods for MouseEvent { @@ -122,6 +177,78 @@ impl MouseEventMethods for MouseEvent { self.client_y.get() } + // https://drafts.csswg.org/cssom-view/#dom-mouseevent-pagex + fn PageX(&self) -> i32 { + if self.upcast::<Event>().dispatching() { + self.page_x.get() + } else { + let global = self.global(); + let window = global.as_window(); + window.current_viewport().origin.x.to_px() + self.client_x.get() + } + } + + // https://drafts.csswg.org/cssom-view/#dom-mouseevent-pagey + fn PageY(&self) -> i32 { + if self.upcast::<Event>().dispatching() { + self.page_y.get() + } else { + let global = self.global(); + let window = global.as_window(); + window.current_viewport().origin.y.to_px() + self.client_y.get() + } + } + + // https://drafts.csswg.org/cssom-view/#dom-mouseevent-x + fn X(&self) -> i32 { + self.client_x.get() + } + + // https://drafts.csswg.org/cssom-view/#dom-mouseevent-y + fn Y(&self) -> i32 { + self.client_y.get() + } + + // https://drafts.csswg.org/cssom-view/#dom-mouseevent-offsetx + fn OffsetX(&self) -> i32 { + let event = self.upcast::<Event>(); + if event.dispatching() { + match event.GetTarget() { + Some(target) => { + if let Some(node) = target.downcast::<Node>() { + let rect = node.client_rect(); + self.client_x.get() - rect.origin.x + } else { + self.offset_x.get() + } + }, + None => self.offset_x.get(), + } + } else { + self.PageX() + } + } + + // https://drafts.csswg.org/cssom-view/#dom-mouseevent-offsety + fn OffsetY(&self) -> i32 { + let event = self.upcast::<Event>(); + if event.dispatching() { + match event.GetTarget() { + Some(target) => { + if let Some(node) = target.downcast::<Node>() { + let rect = node.client_rect(); + self.client_y.get() - rect.origin.y + } else { + self.offset_y.get() + } + }, + None => self.offset_y.get(), + } + } else { + self.PageY() + } + } + // https://w3c.github.io/uievents/#widl-MouseEvent-ctrlKey fn CtrlKey(&self) -> bool { self.ctrl_key.get() @@ -147,8 +274,13 @@ impl MouseEventMethods for MouseEvent { self.button.get() } + // https://w3c.github.io/uievents/#dom-mouseevent-buttons + fn Buttons(&self) -> u16 { + self.buttons.get() + } + // https://w3c.github.io/uievents/#widl-MouseEvent-relatedTarget - fn GetRelatedTarget(&self) -> Option<Root<EventTarget>> { + fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> { self.related_target.get() } @@ -158,7 +290,7 @@ impl MouseEventMethods for MouseEvent { // This returns the same result as current gecko. // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which fn Which(&self) -> i32 { - if PREFS.get("dom.mouseevent.which.enabled").as_boolean().unwrap_or(false) { + if pref!(dom.mouse_event.which.enabled) { (self.button.get() + 1) as i32 } else { 0 @@ -166,28 +298,35 @@ impl MouseEventMethods for MouseEvent { } // https://w3c.github.io/uievents/#widl-MouseEvent-initMouseEvent - fn InitMouseEvent(&self, - type_arg: DOMString, - can_bubble_arg: bool, - cancelable_arg: bool, - view_arg: Option<&Window>, - detail_arg: i32, - screen_x_arg: i32, - screen_y_arg: i32, - client_x_arg: i32, - client_y_arg: i32, - ctrl_key_arg: bool, - alt_key_arg: bool, - shift_key_arg: bool, - meta_key_arg: bool, - button_arg: i16, - related_target_arg: Option<&EventTarget>) { + fn InitMouseEvent( + &self, + type_arg: DOMString, + can_bubble_arg: bool, + cancelable_arg: bool, + view_arg: Option<&Window>, + detail_arg: i32, + screen_x_arg: i32, + screen_y_arg: i32, + client_x_arg: i32, + client_y_arg: i32, + ctrl_key_arg: bool, + alt_key_arg: bool, + shift_key_arg: bool, + meta_key_arg: bool, + button_arg: i16, + related_target_arg: Option<&EventTarget>, + ) { if self.upcast::<Event>().dispatching() { return; } - self.upcast::<UIEvent>() - .InitUIEvent(type_arg, can_bubble_arg, cancelable_arg, view_arg, detail_arg); + self.upcast::<UIEvent>().InitUIEvent( + type_arg, + can_bubble_arg, + cancelable_arg, + view_arg, + detail_arg, + ); self.screen_x.set(screen_x_arg); self.screen_y.set(screen_y_arg); self.client_x.set(client_x_arg); diff --git a/components/script/dom/mutationobserver.rs b/components/script/dom/mutationobserver.rs index 4dc745c1ce8..e4141af14a6 100644 --- a/components/script/dom/mutationobserver.rs +++ b/components/script/dom/mutationobserver.rs @@ -1,40 +1,363 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::MutationObserverBinding; -use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationCallback; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::MutationCallback; +use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverBinding::MutationObserverMethods; +use crate::dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverInit; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::mutationrecord::MutationRecord; +use crate::dom::node::{Node, ShadowIncluding}; +use crate::dom::window::Window; +use crate::microtask::Microtask; +use crate::script_thread::ScriptThread; use dom_struct::dom_struct; -use script_thread::ScriptThread; +use html5ever::{LocalName, Namespace}; use std::rc::Rc; #[dom_struct] pub struct MutationObserver { reflector_: Reflector, - #[ignore_heap_size_of = "can't measure Rc values"] + #[ignore_malloc_size_of = "can't measure Rc values"] callback: Rc<MutationCallback>, + record_queue: DomRefCell<Vec<DomRoot<MutationRecord>>>, + node_list: DomRefCell<Vec<DomRoot<Node>>>, +} + +pub enum Mutation<'a> { + Attribute { + name: LocalName, + namespace: Namespace, + old_value: Option<DOMString>, + }, + CharacterData { + old_value: DOMString, + }, + ChildList { + added: Option<&'a [&'a Node]>, + removed: Option<&'a [&'a Node]>, + prev: Option<&'a Node>, + next: Option<&'a Node>, + }, +} + +#[derive(JSTraceable, MallocSizeOf)] +pub struct RegisteredObserver { + pub observer: DomRoot<MutationObserver>, + options: ObserverOptions, +} + +#[derive(JSTraceable, MallocSizeOf)] +pub struct ObserverOptions { + attribute_old_value: bool, + attributes: bool, + character_data: bool, + character_data_old_value: bool, + child_list: bool, + subtree: bool, + attribute_filter: Vec<DOMString>, } impl MutationObserver { - fn new(global: &Window, callback: Rc<MutationCallback>) -> Root<MutationObserver> { - let boxed_observer = box MutationObserver::new_inherited(callback); - reflect_dom_object(boxed_observer, global, MutationObserverBinding::Wrap) + fn new(global: &Window, callback: Rc<MutationCallback>) -> DomRoot<MutationObserver> { + let boxed_observer = Box::new(MutationObserver::new_inherited(callback)); + reflect_dom_object(boxed_observer, global) } fn new_inherited(callback: Rc<MutationCallback>) -> MutationObserver { MutationObserver { reflector_: Reflector::new(), callback: callback, + record_queue: DomRefCell::new(vec![]), + node_list: DomRefCell::new(vec![]), } } - pub fn Constructor(global: &Window, callback: Rc<MutationCallback>) -> Fallible<Root<MutationObserver>> { + #[allow(non_snake_case)] + pub fn Constructor( + global: &Window, + callback: Rc<MutationCallback>, + ) -> Fallible<DomRoot<MutationObserver>> { + global.set_exists_mut_observer(); let observer = MutationObserver::new(global, callback); ScriptThread::add_mutation_observer(&*observer); Ok(observer) } + + /// <https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask> + pub fn queue_mutation_observer_microtask() { + // Step 1 + if ScriptThread::is_mutation_observer_microtask_queued() { + return; + } + // Step 2 + ScriptThread::set_mutation_observer_microtask_queued(true); + // Step 3 + ScriptThread::enqueue_microtask(Microtask::NotifyMutationObservers); + } + + /// <https://dom.spec.whatwg.org/#notify-mutation-observers> + pub fn notify_mutation_observers() { + // Step 1 + ScriptThread::set_mutation_observer_microtask_queued(false); + // Step 2 + let notify_list = ScriptThread::get_mutation_observers(); + // TODO: steps 3-4 (slots) + // Step 5 + for mo in ¬ify_list { + let queue: Vec<DomRoot<MutationRecord>> = mo.record_queue.borrow().clone(); + mo.record_queue.borrow_mut().clear(); + // TODO: Step 5.3 Remove all transient registered observers whose observer is mo. + if !queue.is_empty() { + let _ = mo + .callback + .Call_(&**mo, queue, &**mo, ExceptionHandling::Report); + } + } + // TODO: Step 6 (slot signals) + } + + /// <https://dom.spec.whatwg.org/#queueing-a-mutation-record> + pub fn queue_a_mutation_record(target: &Node, attr_type: Mutation) { + if !target.global().as_window().get_exists_mut_observer() { + return; + } + // Step 1 + let mut interested_observers: Vec<(DomRoot<MutationObserver>, Option<DOMString>)> = vec![]; + + // Step 2 & 3 + for node in target.inclusive_ancestors(ShadowIncluding::No) { + let registered = node.registered_mutation_observers(); + if registered.is_none() { + continue; + } + + for registered in &*registered.unwrap() { + if &*node != target && !registered.options.subtree { + continue; + } + + match attr_type { + Mutation::Attribute { + ref name, + ref namespace, + ref old_value, + } => { + // Step 3.1 + if !registered.options.attributes { + continue; + } + if !registered.options.attribute_filter.is_empty() { + if *namespace != ns!() { + continue; + } + if !registered + .options + .attribute_filter + .iter() + .any(|s| &**s == &**name) + { + continue; + } + } + // Step 3.1.2 + let paired_string = if registered.options.attribute_old_value { + old_value.clone() + } else { + None + }; + // Step 3.1.1 + let idx = interested_observers.iter().position(|&(ref o, _)| { + &**o as *const _ == &*registered.observer as *const _ + }); + if let Some(idx) = idx { + interested_observers[idx].1 = paired_string; + } else { + interested_observers + .push((DomRoot::from_ref(&*registered.observer), paired_string)); + } + }, + Mutation::CharacterData { ref old_value } => { + if !registered.options.character_data { + continue; + } + // Step 3.1.2 + let paired_string = if registered.options.character_data_old_value { + Some(old_value.clone()) + } else { + None + }; + // Step 3.1.1 + let idx = interested_observers.iter().position(|&(ref o, _)| { + &**o as *const _ == &*registered.observer as *const _ + }); + if let Some(idx) = idx { + interested_observers[idx].1 = paired_string; + } else { + interested_observers + .push((DomRoot::from_ref(&*registered.observer), paired_string)); + } + }, + Mutation::ChildList { .. } => { + if !registered.options.child_list { + continue; + } + interested_observers.push((DomRoot::from_ref(&*registered.observer), None)); + }, + } + } + } + + // Step 4 + for (observer, paired_string) in interested_observers { + // Steps 4.1-4.7 + let record = match attr_type { + Mutation::Attribute { + ref name, + ref namespace, + .. + } => { + let namespace = if *namespace != ns!() { + Some(namespace) + } else { + None + }; + MutationRecord::attribute_mutated(target, name, namespace, paired_string) + }, + Mutation::CharacterData { .. } => { + MutationRecord::character_data_mutated(target, paired_string) + }, + Mutation::ChildList { + ref added, + ref removed, + ref next, + ref prev, + } => MutationRecord::child_list_mutated(target, *added, *removed, *next, *prev), + }; + // Step 4.8 + observer.record_queue.borrow_mut().push(record); + } + + // Step 5 + MutationObserver::queue_mutation_observer_microtask(); + } +} + +impl MutationObserverMethods for MutationObserver { + /// <https://dom.spec.whatwg.org/#dom-mutationobserver-observe> + fn Observe(&self, target: &Node, options: &MutationObserverInit) -> Fallible<()> { + let attribute_filter = options.attributeFilter.clone().unwrap_or(vec![]); + let attribute_old_value = options.attributeOldValue.unwrap_or(false); + let mut attributes = options.attributes.unwrap_or(false); + let mut character_data = options.characterData.unwrap_or(false); + let character_data_old_value = options.characterDataOldValue.unwrap_or(false); + let child_list = options.childList; + let subtree = options.subtree; + + // Step 1 + if (options.attributeOldValue.is_some() || options.attributeFilter.is_some()) && + options.attributes.is_none() + { + attributes = true; + } + + // Step 2 + if options.characterDataOldValue.is_some() && options.characterData.is_none() { + character_data = true; + } + + // Step 3 + if !child_list && !attributes && !character_data { + return Err(Error::Type( + "One of childList, attributes, or characterData must be true".into(), + )); + } + + // Step 4 + if attribute_old_value && !attributes { + return Err(Error::Type( + "attributeOldValue is true but attributes is false".into(), + )); + } + + // Step 5 + if options.attributeFilter.is_some() && !attributes { + return Err(Error::Type( + "attributeFilter is present but attributes is false".into(), + )); + } + + // Step 6 + if character_data_old_value && !character_data { + return Err(Error::Type( + "characterDataOldValue is true but characterData is false".into(), + )); + } + + // Step 7 + let add_new_observer = { + let mut replaced = false; + for registered in &mut *target.registered_mutation_observers_mut() { + if &*registered.observer as *const MutationObserver != + self as *const MutationObserver + { + continue; + } + // TODO: remove matching transient registered observers + registered.options.attribute_old_value = attribute_old_value; + registered.options.attributes = attributes; + registered.options.character_data = character_data; + registered.options.character_data_old_value = character_data_old_value; + registered.options.child_list = child_list; + registered.options.subtree = subtree; + registered.options.attribute_filter = attribute_filter.clone(); + replaced = true; + } + !replaced + }; + + // Step 8 + if add_new_observer { + target.add_mutation_observer(RegisteredObserver { + observer: DomRoot::from_ref(self), + options: ObserverOptions { + attributes, + attribute_old_value, + character_data, + character_data_old_value, + subtree, + attribute_filter, + child_list, + }, + }); + + self.node_list.borrow_mut().push(DomRoot::from_ref(target)); + } + + Ok(()) + } + + /// https://dom.spec.whatwg.org/#dom-mutationobserver-takerecords + fn TakeRecords(&self) -> Vec<DomRoot<MutationRecord>> { + let records: Vec<DomRoot<MutationRecord>> = self.record_queue.borrow().clone(); + self.record_queue.borrow_mut().clear(); + records + } + + /// https://dom.spec.whatwg.org/#dom-mutationobserver-disconnect + fn Disconnect(&self) { + // Step 1 + let mut nodes = self.node_list.borrow_mut(); + for node in nodes.drain(..) { + node.remove_mutation_observer(self); + } + + // Step 2 + self.record_queue.borrow_mut().clear(); + } } diff --git a/components/script/dom/mutationrecord.rs b/components/script/dom/mutationrecord.rs index c39d61ef18a..d880f44a8ae 100644 --- a/components/script/dom/mutationrecord.rs +++ b/components/script/dom/mutationrecord.rs @@ -1,23 +1,124 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::MutationRecordBinding::MutationRecordBinding::MutationRecordMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::Reflector; -use dom::bindings::str::DOMString; -use dom::node::Node; +use crate::dom::bindings::codegen::Bindings::MutationRecordBinding::MutationRecordBinding::MutationRecordMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::node::{window_from_node, Node}; +use crate::dom::nodelist::NodeList; use dom_struct::dom_struct; +use html5ever::{LocalName, Namespace}; #[dom_struct] pub struct MutationRecord { reflector_: Reflector, - - //property for record type record_type: DOMString, + target: Dom<Node>, + attribute_name: Option<DOMString>, + attribute_namespace: Option<DOMString>, + old_value: Option<DOMString>, + added_nodes: MutNullableDom<NodeList>, + removed_nodes: MutNullableDom<NodeList>, + next_sibling: Option<Dom<Node>>, + prev_sibling: Option<Dom<Node>>, +} + +impl MutationRecord { + #[allow(unrooted_must_root)] + pub fn attribute_mutated( + target: &Node, + attribute_name: &LocalName, + attribute_namespace: Option<&Namespace>, + old_value: Option<DOMString>, + ) -> DomRoot<MutationRecord> { + let record = Box::new(MutationRecord::new_inherited( + "attributes", + target, + Some(DOMString::from(&**attribute_name)), + attribute_namespace.map(|n| DOMString::from(&**n)), + old_value, + None, + None, + None, + None, + )); + reflect_dom_object(record, &*window_from_node(target)) + } + + pub fn character_data_mutated( + target: &Node, + old_value: Option<DOMString>, + ) -> DomRoot<MutationRecord> { + reflect_dom_object( + Box::new(MutationRecord::new_inherited( + "characterData", + target, + None, + None, + old_value, + None, + None, + None, + None, + )), + &*window_from_node(target), + ) + } - //property for target node - target: JS<Node>, + pub fn child_list_mutated( + target: &Node, + added_nodes: Option<&[&Node]>, + removed_nodes: Option<&[&Node]>, + next_sibling: Option<&Node>, + prev_sibling: Option<&Node>, + ) -> DomRoot<MutationRecord> { + let window = window_from_node(target); + let added_nodes = added_nodes.map(|list| NodeList::new_simple_list_slice(&window, list)); + let removed_nodes = + removed_nodes.map(|list| NodeList::new_simple_list_slice(&window, list)); + + reflect_dom_object( + Box::new(MutationRecord::new_inherited( + "childList", + target, + None, + None, + None, + added_nodes.as_ref().map(|list| &**list), + removed_nodes.as_ref().map(|list| &**list), + next_sibling, + prev_sibling, + )), + &*window, + ) + } + + fn new_inherited( + record_type: &str, + target: &Node, + attribute_name: Option<DOMString>, + attribute_namespace: Option<DOMString>, + old_value: Option<DOMString>, + added_nodes: Option<&NodeList>, + removed_nodes: Option<&NodeList>, + next_sibling: Option<&Node>, + prev_sibling: Option<&Node>, + ) -> MutationRecord { + MutationRecord { + reflector_: Reflector::new(), + record_type: DOMString::from(record_type), + target: Dom::from_ref(target), + attribute_name: attribute_name, + attribute_namespace: attribute_namespace, + old_value: old_value, + added_nodes: MutNullableDom::new(added_nodes), + removed_nodes: MutNullableDom::new(removed_nodes), + next_sibling: next_sibling.map(Dom::from_ref), + prev_sibling: prev_sibling.map(Dom::from_ref), + } + } } impl MutationRecordMethods for MutationRecord { @@ -27,8 +128,52 @@ impl MutationRecordMethods for MutationRecord { } // https://dom.spec.whatwg.org/#dom-mutationrecord-target - fn Target(&self) -> Root<Node> { - return Root::from_ref(&*self.target); + fn Target(&self) -> DomRoot<Node> { + DomRoot::from_ref(&*self.target) + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-attributename + fn GetAttributeName(&self) -> Option<DOMString> { + self.attribute_name.clone() } + // https://dom.spec.whatwg.org/#dom-mutationrecord-attributenamespace + fn GetAttributeNamespace(&self) -> Option<DOMString> { + self.attribute_namespace.clone() + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-oldvalue + fn GetOldValue(&self) -> Option<DOMString> { + self.old_value.clone() + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-addednodes + fn AddedNodes(&self) -> DomRoot<NodeList> { + self.added_nodes.or_init(|| { + let window = window_from_node(&*self.target); + NodeList::empty(&window) + }) + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-removednodes + fn RemovedNodes(&self) -> DomRoot<NodeList> { + self.removed_nodes.or_init(|| { + let window = window_from_node(&*self.target); + NodeList::empty(&window) + }) + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-previoussibling + fn GetPreviousSibling(&self) -> Option<DomRoot<Node>> { + self.prev_sibling + .as_ref() + .map(|node| DomRoot::from_ref(&**node)) + } + + // https://dom.spec.whatwg.org/#dom-mutationrecord-previoussibling + fn GetNextSibling(&self) -> Option<DomRoot<Node>> { + self.next_sibling + .as_ref() + .map(|node| DomRoot::from_ref(&**node)) + } } diff --git a/components/script/dom/namednodemap.rs b/components/script/dom/namednodemap.rs index 273b87c8d00..a7c3e4fef86 100644 --- a/components/script/dom/namednodemap.rs +++ b/components/script/dom/namednodemap.rs @@ -1,39 +1,36 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::NamedNodeMapBinding; -use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::bindings::xmlname::namespace_from_domstring; -use dom::element::Element; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::xmlname::namespace_from_domstring; +use crate::dom::element::Element; +use crate::dom::window::Window; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use std::ascii::AsciiExt; +use html5ever::LocalName; #[dom_struct] pub struct NamedNodeMap { reflector_: Reflector, - owner: JS<Element>, + owner: Dom<Element>, } impl NamedNodeMap { fn new_inherited(elem: &Element) -> NamedNodeMap { NamedNodeMap { reflector_: Reflector::new(), - owner: JS::from_ref(elem), + owner: Dom::from_ref(elem), } } - pub fn new(window: &Window, elem: &Element) -> Root<NamedNodeMap> { - reflect_dom_object(box NamedNodeMap::new_inherited(elem), - window, NamedNodeMapBinding::Wrap) + pub fn new(window: &Window, elem: &Element) -> DomRoot<NamedNodeMap> { + reflect_dom_object(Box::new(NamedNodeMap::new_inherited(elem)), window) } } @@ -44,64 +41,76 @@ impl NamedNodeMapMethods for NamedNodeMap { } // https://dom.spec.whatwg.org/#dom-namednodemap-item - fn Item(&self, index: u32) -> Option<Root<Attr>> { - self.owner.attrs().get(index as usize).map(|js| Root::from_ref(&**js)) + fn Item(&self, index: u32) -> Option<DomRoot<Attr>> { + self.owner + .attrs() + .get(index as usize) + .map(|js| DomRoot::from_ref(&**js)) } // https://dom.spec.whatwg.org/#dom-namednodemap-getnameditem - fn GetNamedItem(&self, name: DOMString) -> Option<Root<Attr>> { + fn GetNamedItem(&self, name: DOMString) -> Option<DomRoot<Attr>> { self.owner.get_attribute_by_name(name) } // https://dom.spec.whatwg.org/#dom-namednodemap-getnameditemns - fn GetNamedItemNS(&self, namespace: Option<DOMString>, local_name: DOMString) - -> Option<Root<Attr>> { + fn GetNamedItemNS( + &self, + namespace: Option<DOMString>, + local_name: DOMString, + ) -> Option<DomRoot<Attr>> { let ns = namespace_from_domstring(namespace); self.owner.get_attribute(&ns, &LocalName::from(local_name)) } // https://dom.spec.whatwg.org/#dom-namednodemap-setnameditem - fn SetNamedItem(&self, attr: &Attr) -> Fallible<Option<Root<Attr>>> { + fn SetNamedItem(&self, attr: &Attr) -> Fallible<Option<DomRoot<Attr>>> { self.owner.SetAttributeNode(attr) } // https://dom.spec.whatwg.org/#dom-namednodemap-setnameditemns - fn SetNamedItemNS(&self, attr: &Attr) -> Fallible<Option<Root<Attr>>> { + fn SetNamedItemNS(&self, attr: &Attr) -> Fallible<Option<DomRoot<Attr>>> { self.SetNamedItem(attr) } // https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem - fn RemoveNamedItem(&self, name: DOMString) -> Fallible<Root<Attr>> { + fn RemoveNamedItem(&self, name: DOMString) -> Fallible<DomRoot<Attr>> { let name = self.owner.parsed_name(name); - self.owner.remove_attribute_by_name(&name).ok_or(Error::NotFound) + self.owner + .remove_attribute_by_name(&name) + .ok_or(Error::NotFound) } // https://dom.spec.whatwg.org/#dom-namednodemap-removenameditemns - fn RemoveNamedItemNS(&self, namespace: Option<DOMString>, local_name: DOMString) - -> Fallible<Root<Attr>> { + fn RemoveNamedItemNS( + &self, + namespace: Option<DOMString>, + local_name: DOMString, + ) -> Fallible<DomRoot<Attr>> { let ns = namespace_from_domstring(namespace); - self.owner.remove_attribute(&ns, &LocalName::from(local_name)) + self.owner + .remove_attribute(&ns, &LocalName::from(local_name)) .ok_or(Error::NotFound) } // https://dom.spec.whatwg.org/#dom-namednodemap-item - fn IndexedGetter(&self, index: u32) -> Option<Root<Attr>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Attr>> { self.Item(index) } // check-tidy: no specs after this line - fn NamedGetter(&self, name: DOMString) -> Option<Root<Attr>> { + fn NamedGetter(&self, name: DOMString) -> Option<DomRoot<Attr>> { self.GetNamedItem(name) } // https://heycam.github.io/webidl/#dfn-supported-property-names fn SupportedPropertyNames(&self) -> Vec<DOMString> { - let mut names = vec!(); + let mut names = vec![]; let html_element_in_html_document = self.owner.html_element_in_html_document(); for attr in self.owner.attrs().iter() { let s = &**attr.name(); if html_element_in_html_document && !s.bytes().all(|b| b.to_ascii_lowercase() == b) { - continue + continue; } if !names.iter().any(|name| &*name == s) { diff --git a/components/script/dom/navigationpreloadmanager.rs b/components/script/dom/navigationpreloadmanager.rs new file mode 100644 index 00000000000..d45fa9ac5f8 --- /dev/null +++ b/components/script/dom/navigationpreloadmanager.rs @@ -0,0 +1,136 @@ +/* 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 crate::dom::bindings::codegen::Bindings::NavigationPreloadManagerBinding::NavigationPreloadManagerMethods; +use crate::dom::bindings::codegen::Bindings::NavigationPreloadManagerBinding::NavigationPreloadState; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::ByteString; +use crate::dom::domexception::{DOMErrorName, DOMException}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; +use crate::realms::InRealm; +use dom_struct::dom_struct; +use js::jsval::UndefinedValue; +use std::rc::Rc; + +#[dom_struct] +pub struct NavigationPreloadManager { + reflector_: Reflector, + serviceworker_registration: Dom<ServiceWorkerRegistration>, +} + +impl NavigationPreloadManager { + fn new_inherited(registration: &ServiceWorkerRegistration) -> NavigationPreloadManager { + NavigationPreloadManager { + reflector_: Reflector::new(), + serviceworker_registration: Dom::from_ref(registration), + } + } + + #[allow(unrooted_must_root)] + pub fn new( + global: &GlobalScope, + registration: &ServiceWorkerRegistration, + ) -> DomRoot<NavigationPreloadManager> { + let manager = NavigationPreloadManager::new_inherited(&*registration); + reflect_dom_object(Box::new(manager), global) + } +} + +impl NavigationPreloadManagerMethods for NavigationPreloadManager { + // https://w3c.github.io/ServiceWorker/#navigation-preload-manager-enable + fn Enable(&self, comp: InRealm) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&*self.global(), comp); + + // 2. + if self.serviceworker_registration.is_active() { + promise.reject_native(&DOMException::new( + &self.global(), + DOMErrorName::InvalidStateError, + )); + } else { + // 3. + self.serviceworker_registration + .set_navigation_preload_enabled(true); + + // 4. + promise.resolve_native(&UndefinedValue()); + } + + promise + } + + // https://w3c.github.io/ServiceWorker/#navigation-preload-manager-disable + fn Disable(&self, comp: InRealm) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&*self.global(), comp); + + // 2. + if self.serviceworker_registration.is_active() { + promise.reject_native(&DOMException::new( + &self.global(), + DOMErrorName::InvalidStateError, + )); + } else { + // 3. + self.serviceworker_registration + .set_navigation_preload_enabled(false); + + // 4. + promise.resolve_native(&UndefinedValue()); + } + + promise + } + + // https://w3c.github.io/ServiceWorker/#navigation-preload-manager-setheadervalue + fn SetHeaderValue(&self, value: ByteString, comp: InRealm) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&*self.global(), comp); + + // 2. + if self.serviceworker_registration.is_active() { + promise.reject_native(&DOMException::new( + &self.global(), + DOMErrorName::InvalidStateError, + )); + } else { + // 3. + self.serviceworker_registration + .set_navigation_preload_header_value(value); + + // 4. + promise.resolve_native(&UndefinedValue()); + } + + promise + } + + // https://w3c.github.io/ServiceWorker/#navigation-preload-manager-getstate + fn GetState(&self, comp: InRealm) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&*self.global(), comp); + // 2. + let mut state = NavigationPreloadState::empty(); + + // 3. + if self.serviceworker_registration.is_active() { + if self + .serviceworker_registration + .get_navigation_preload_enabled() + { + state.enabled = true; + } + } + + // 4. + state.headerValue = self + .serviceworker_registration + .get_navigation_preload_header_value(); + + // 5. + promise.resolve_native(&state); + + promise + } +} diff --git a/components/script/dom/navigator.rs b/components/script/dom/navigator.rs index 32dd2900e17..07abebe667f 100644 --- a/components/script/dom/navigator.rs +++ b/components/script/dom/navigator.rs @@ -1,33 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::NavigatorBinding; -use dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::{Reflector, DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::bluetooth::Bluetooth; -use dom::gamepadlist::GamepadList; -use dom::mimetypearray::MimeTypeArray; -use dom::navigatorinfo; -use dom::permissions::Permissions; -use dom::pluginarray::PluginArray; -use dom::serviceworkercontainer::ServiceWorkerContainer; -use dom::vr::VR; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::utils::to_frozen_array; +use crate::dom::bluetooth::Bluetooth; +use crate::dom::gamepadlist::GamepadList; +use crate::dom::gpu::GPU; +use crate::dom::mediadevices::MediaDevices; +use crate::dom::mediasession::MediaSession; +use crate::dom::mimetypearray::MimeTypeArray; +use crate::dom::navigatorinfo; +use crate::dom::permissions::Permissions; +use crate::dom::pluginarray::PluginArray; +use crate::dom::serviceworkercontainer::ServiceWorkerContainer; +use crate::dom::window::Window; +use crate::dom::xrsystem::XRSystem; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; +use js::jsval::JSVal; #[dom_struct] pub struct Navigator { reflector_: Reflector, - bluetooth: MutNullableJS<Bluetooth>, - plugins: MutNullableJS<PluginArray>, - mime_types: MutNullableJS<MimeTypeArray>, - service_worker: MutNullableJS<ServiceWorkerContainer>, - vr: MutNullableJS<VR>, - gamepads: MutNullableJS<GamepadList>, - permissions: MutNullableJS<Permissions>, + bluetooth: MutNullableDom<Bluetooth>, + plugins: MutNullableDom<PluginArray>, + mime_types: MutNullableDom<MimeTypeArray>, + service_worker: MutNullableDom<ServiceWorkerContainer>, + xr: MutNullableDom<XRSystem>, + mediadevices: MutNullableDom<MediaDevices>, + gamepads: MutNullableDom<GamepadList>, + permissions: MutNullableDom<Permissions>, + mediasession: MutNullableDom<MediaSession>, + gpu: MutNullableDom<GPU>, } impl Navigator { @@ -38,16 +46,21 @@ impl Navigator { plugins: Default::default(), mime_types: Default::default(), service_worker: Default::default(), - vr: Default::default(), + xr: Default::default(), + mediadevices: Default::default(), gamepads: Default::default(), permissions: Default::default(), + mediasession: Default::default(), + gpu: Default::default(), } } - pub fn new(window: &Window) -> Root<Navigator> { - reflect_dom_object(box Navigator::new_inherited(), - window, - NavigatorBinding::Wrap) + pub fn new(window: &Window) -> DomRoot<Navigator> { + reflect_dom_object(Box::new(Navigator::new_inherited()), window) + } + + pub fn xr(&self) -> Option<DomRoot<XRSystem>> { + self.xr.get() } } @@ -57,6 +70,21 @@ impl NavigatorMethods for Navigator { navigatorinfo::Product() } + // https://html.spec.whatwg.org/multipage/#dom-navigator-productsub + fn ProductSub(&self) -> DOMString { + navigatorinfo::ProductSub() + } + + // https://html.spec.whatwg.org/multipage/#dom-navigator-vendor + fn Vendor(&self) -> DOMString { + navigatorinfo::Vendor() + } + + // https://html.spec.whatwg.org/multipage/#dom-navigator-vendorsub + fn VendorSub(&self) -> DOMString { + navigatorinfo::VendorSub() + } + // https://html.spec.whatwg.org/multipage/#dom-navigator-taintenabled fn TaintEnabled(&self) -> bool { navigatorinfo::TaintEnabled() @@ -79,7 +107,7 @@ impl NavigatorMethods for Navigator { // https://html.spec.whatwg.org/multipage/#dom-navigator-useragent fn UserAgent(&self) -> DOMString { - navigatorinfo::UserAgent() + navigatorinfo::UserAgent(self.global().get_user_agent()) } // https://html.spec.whatwg.org/multipage/#dom-navigator-appversion @@ -88,7 +116,7 @@ impl NavigatorMethods for Navigator { } // https://webbluetoothcg.github.io/web-bluetooth/#dom-navigator-bluetooth - fn Bluetooth(&self) -> Root<Bluetooth> { + fn Bluetooth(&self) -> DomRoot<Bluetooth> { self.bluetooth.or_init(|| Bluetooth::new(&self.global())) } @@ -97,14 +125,21 @@ impl NavigatorMethods for Navigator { navigatorinfo::Language() } + // https://html.spec.whatwg.org/multipage/#dom-navigator-languages + #[allow(unsafe_code)] + fn Languages(&self, cx: JSContext) -> JSVal { + to_frozen_array(&[self.Language()], cx) + } + // https://html.spec.whatwg.org/multipage/#dom-navigator-plugins - fn Plugins(&self) -> Root<PluginArray> { + fn Plugins(&self) -> DomRoot<PluginArray> { self.plugins.or_init(|| PluginArray::new(&self.global())) } // https://html.spec.whatwg.org/multipage/#dom-navigator-mimetypes - fn MimeTypes(&self) -> Root<MimeTypeArray> { - self.mime_types.or_init(|| MimeTypeArray::new(&self.global())) + fn MimeTypes(&self) -> DomRoot<MimeTypeArray> { + self.mime_types + .or_init(|| MimeTypeArray::new(&self.global())) } // https://html.spec.whatwg.org/multipage/#dom-navigator-javaenabled @@ -113,10 +148,9 @@ impl NavigatorMethods for Navigator { } // https://w3c.github.io/ServiceWorker/#navigator-service-worker-attribute - fn ServiceWorker(&self) -> Root<ServiceWorkerContainer> { - self.service_worker.or_init(|| { - ServiceWorkerContainer::new(&self.global()) - }) + fn ServiceWorker(&self) -> DomRoot<ServiceWorkerContainer> { + self.service_worker + .or_init(|| ServiceWorkerContainer::new(&self.global())) } // https://html.spec.whatwg.org/multipage/#dom-navigator-cookieenabled @@ -124,25 +158,51 @@ impl NavigatorMethods for Navigator { true } - #[allow(unrooted_must_root)] - // https://w3c.github.io/webvr/#interface-navigator - fn Vr(&self) -> Root<VR> { - self.vr.or_init(|| VR::new(&self.global())) - } - // https://www.w3.org/TR/gamepad/#navigator-interface-extension - fn GetGamepads(&self) -> Root<GamepadList> { - let root = self.gamepads.or_init(|| { - GamepadList::new(&self.global(), &[]) - }); - - let vr_gamepads = self.Vr().get_gamepads(); - root.add_if_not_exists(&vr_gamepads); - // TODO: Add not VR related gamepads + fn GetGamepads(&self) -> DomRoot<GamepadList> { + let root = self + .gamepads + .or_init(|| GamepadList::new(&self.global(), &[])); + + // TODO: Add gamepads root } // https://w3c.github.io/permissions/#navigator-and-workernavigator-extension - fn Permissions(&self) -> Root<Permissions> { - self.permissions.or_init(|| Permissions::new(&self.global())) + fn Permissions(&self) -> DomRoot<Permissions> { + self.permissions + .or_init(|| Permissions::new(&self.global())) + } + + /// https://immersive-web.github.io/webxr/#dom-navigator-xr + fn Xr(&self) -> DomRoot<XRSystem> { + self.xr + .or_init(|| XRSystem::new(&self.global().as_window())) + } + + /// https://w3c.github.io/mediacapture-main/#dom-navigator-mediadevices + fn MediaDevices(&self) -> DomRoot<MediaDevices> { + self.mediadevices + .or_init(|| MediaDevices::new(&self.global())) + } + + /// https://w3c.github.io/mediasession/#dom-navigator-mediasession + fn MediaSession(&self) -> DomRoot<MediaSession> { + self.mediasession.or_init(|| { + // There is a single MediaSession instance per Pipeline + // and only one active MediaSession globally. + // + // MediaSession creation can happen in two cases: + // + // - If content gets `navigator.mediaSession` + // - If a media instance (HTMLMediaElement so far) starts playing media. + let global = self.global(); + let window = global.as_window(); + MediaSession::new(window) + }) + } + + // https://gpuweb.github.io/gpuweb/#dom-navigator-gpu + fn Gpu(&self) -> DomRoot<GPU> { + self.gpu.or_init(|| GPU::new(&self.global())) } } diff --git a/components/script/dom/navigatorinfo.rs b/components/script/dom/navigatorinfo.rs index f0b331c49e6..e674834f14b 100644 --- a/components/script/dom/navigatorinfo.rs +++ b/components/script/dom/navigatorinfo.rs @@ -1,49 +1,80 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::str::DOMString; -use servo_config::opts; +use crate::dom::bindings::str::DOMString; +use std::borrow::Cow; +#[allow(non_snake_case)] pub fn Product() -> DOMString { DOMString::from("Gecko") } +#[allow(non_snake_case)] +pub fn ProductSub() -> DOMString { + DOMString::from("20100101") +} + +#[allow(non_snake_case)] +pub fn Vendor() -> DOMString { + DOMString::from("") +} + +#[allow(non_snake_case)] +pub fn VendorSub() -> DOMString { + DOMString::from("") +} + +#[allow(non_snake_case)] pub fn TaintEnabled() -> bool { false } +#[allow(non_snake_case)] pub fn AppName() -> DOMString { DOMString::from("Netscape") // Like Gecko/Webkit } +#[allow(non_snake_case)] pub fn AppCodeName() -> DOMString { DOMString::from("Mozilla") } +#[allow(non_snake_case)] #[cfg(target_os = "windows")] pub fn Platform() -> DOMString { DOMString::from("Win32") } +#[allow(non_snake_case)] #[cfg(any(target_os = "android", target_os = "linux"))] pub fn Platform() -> DOMString { DOMString::from("Linux") } +#[allow(non_snake_case)] #[cfg(target_os = "macos")] pub fn Platform() -> DOMString { DOMString::from("Mac") } -pub fn UserAgent() -> DOMString { - DOMString::from(&*opts::get().user_agent) +#[allow(non_snake_case)] +#[cfg(target_os = "ios")] +pub fn Platform() -> DOMString { + DOMString::from("iOS") +} + +#[allow(non_snake_case)] +pub fn UserAgent(user_agent: Cow<'static, str>) -> DOMString { + DOMString::from(&*user_agent) } +#[allow(non_snake_case)] pub fn AppVersion() -> DOMString { DOMString::from("4.0") } +#[allow(non_snake_case)] pub fn Language() -> DOMString { DOMString::from("en-US") } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 0384c4ed19d..62f340fb215 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -1,89 +1,104 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements. +use crate::document_loader::DocumentLoader; +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut}; +use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; +use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; +use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::{ + GetRootNodeOptions, NodeConstants, NodeMethods, +}; +use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; +use crate::dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootBinding::ShadowRootMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::InheritTypes::DocumentFragmentTypeId; +use crate::dom::bindings::codegen::UnionTypes::NodeOrString; +use crate::dom::bindings::conversions::{self, DerivedFrom}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::{Castable, CharacterDataTypeId, ElementTypeId}; +use crate::dom::bindings::inheritance::{EventTargetTypeId, HTMLElementTypeId, NodeTypeId}; +use crate::dom::bindings::inheritance::{SVGElementTypeId, SVGGraphicsElementTypeId, TextTypeId}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, DomObjectWrap}; +use crate::dom::bindings::root::{Dom, DomRoot, DomSlice, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::xmlname::namespace_from_domstring; +use crate::dom::characterdata::{CharacterData, LayoutCharacterDataHelpers}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::customelementregistry::{try_upgrade_element, CallbackReaction}; +use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::documenttype::DocumentType; +use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +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::htmllinkelement::HTMLLinkElement; +use crate::dom::htmlmediaelement::{HTMLMediaElement, LayoutHTMLMediaElementHelpers}; +use crate::dom::htmlmetaelement::HTMLMetaElement; +use crate::dom::htmlstyleelement::HTMLStyleElement; +use crate::dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; +use crate::dom::mouseevent::MouseEvent; +use crate::dom::mutationobserver::{Mutation, MutationObserver, RegisteredObserver}; +use crate::dom::nodelist::NodeList; +use crate::dom::processinginstruction::ProcessingInstruction; +use crate::dom::range::WeakRangeVec; +use crate::dom::raredata::NodeRareData; +use crate::dom::shadowroot::{LayoutShadowRootHelpers, ShadowRoot}; +use crate::dom::stylesheetlist::StyleSheetListOwner; +use crate::dom::svgsvgelement::{LayoutSVGSVGElementHelpers, SVGSVGElement}; +use crate::dom::text::Text; +use crate::dom::virtualmethods::{vtable_for, VirtualMethods}; +use crate::dom::window::Window; +use crate::script_thread::ScriptThread; use app_units::Au; use devtools_traits::NodeInfo; -use document_loader::DocumentLoader; -use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; -use dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods}; -use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; -use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::UnionTypes::NodeOrString; -use dom::bindings::conversions::{self, DerivedFrom}; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::{Castable, CharacterDataTypeId, ElementTypeId}; -use dom::bindings::inheritance::{EventTargetTypeId, HTMLElementTypeId, NodeTypeId}; -use dom::bindings::inheritance::{SVGElementTypeId, SVGGraphicsElementTypeId}; -use dom::bindings::js::{JS, LayoutJS, MutNullableJS}; -use dom::bindings::js::Root; -use dom::bindings::js::RootedReference; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::bindings::xmlname::namespace_from_domstring; -use dom::characterdata::{CharacterData, LayoutCharacterDataHelpers}; -use dom::cssstylesheet::CSSStyleSheet; -use dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; -use dom::documentfragment::DocumentFragment; -use dom::documenttype::DocumentType; -use dom::element::{Element, ElementCreator}; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::htmlbodyelement::HTMLBodyElement; -use dom::htmlcanvaselement::{HTMLCanvasElement, LayoutHTMLCanvasElementHelpers}; -use dom::htmlcollection::HTMLCollection; -use dom::htmlelement::HTMLElement; -use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods}; -use dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; -use dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; -use dom::htmllinkelement::HTMLLinkElement; -use dom::htmlmetaelement::HTMLMetaElement; -use dom::htmlstyleelement::HTMLStyleElement; -use dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; -use dom::nodelist::NodeList; -use dom::processinginstruction::ProcessingInstruction; -use dom::range::WeakRangeVec; -use dom::svgsvgelement::{SVGSVGElement, LayoutSVGSVGElementHelpers}; -use dom::text::Text; -use dom::virtualmethods::{VirtualMethods, vtable_for}; -use dom::window::Window; use dom_struct::dom_struct; -use euclid::point::Point2D; -use euclid::rect::Rect; -use euclid::size::Size2D; -use heapsize::{HeapSizeOf, heap_size_of}; -use html5ever_atoms::{Prefix, Namespace, QualName}; -use js::jsapi::{JSContext, JSObject, JSRuntime}; +use euclid::default::{Point2D, Rect, Size2D, Vector2D}; +use html5ever::{Namespace, Prefix, QualName}; +use js::jsapi::JSObject; use libc::{self, c_void, uintptr_t}; -use msg::constellation_msg::PipelineId; -use ref_slice::ref_slice; -use script_layout_interface::{HTMLCanvasData, OpaqueStyleAndLayoutData, SVGSVGData}; -use script_layout_interface::{LayoutElementType, LayoutNodeType, TrustedNodeAddress}; -use script_layout_interface::message::Msg; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use msg::constellation_msg::{BrowsingContextId, PipelineId}; +use net_traits::image::base::{Image, ImageMetadata}; +use script_layout_interface::message::QueryMsg; +use script_layout_interface::{HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType}; +use script_layout_interface::{SVGSVGData, StyleAndOpaqueLayoutData, TrustedNodeAddress}; use script_traits::DocumentActivity; use script_traits::UntrustedNodeAddress; -use selectors::matching::matches_selector_list; +use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode}; use selectors::parser::SelectorList; +use servo_arc::Arc; +use servo_atoms::Atom; use servo_url::ServoUrl; -use std::borrow::ToOwned; +use smallvec::SmallVec; +use std::borrow::Cow; use std::cell::{Cell, UnsafeCell}; -use std::cmp::max; +use std::cmp; use std::default::Default; use std::iter; use std::mem; use std::ops::Range; -use std::sync::Arc; +use std::slice::from_ref; +use std::sync::Arc as StdArc; use style::context::QuirksMode; use style::dom::OpaqueNode; +use style::properties::ComputedValues; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::stylesheets::Stylesheet; -use style::thread_state; use uuid::Uuid; // @@ -97,25 +112,28 @@ pub struct Node { eventtarget: EventTarget, /// The parent of this node. - parent_node: MutNullableJS<Node>, + parent_node: MutNullableDom<Node>, /// The first child of this node. - first_child: MutNullableJS<Node>, + first_child: MutNullableDom<Node>, /// The last child of this node. - last_child: MutNullableJS<Node>, + last_child: MutNullableDom<Node>, /// The next sibling of this node. - next_sibling: MutNullableJS<Node>, + next_sibling: MutNullableDom<Node>, /// The previous sibling of this node. - prev_sibling: MutNullableJS<Node>, + prev_sibling: MutNullableDom<Node>, /// The document that this node belongs to. - owner_doc: MutNullableJS<Document>, + owner_doc: MutNullableDom<Document>, + + /// Rare node data. + rare_data: DomRefCell<Option<Box<NodeRareData>>>, /// The live list of children return by .childNodes. - child_list: MutNullableJS<NodeList>, + child_list: MutNullableDom<NodeList>, /// The live count of children of this node. children_count: Cell<u32>, @@ -132,41 +150,53 @@ pub struct Node { /// are this node. ranges: WeakRangeVec, - /// Style+Layout information. Only the layout thread may touch this data. - /// - /// Must be sent back to the layout thread to be destroyed when this - /// node is finalized. - style_and_layout_data: Cell<Option<OpaqueStyleAndLayoutData>>, - - unique_id: UniqueId, + /// Style+Layout information. + #[ignore_malloc_size_of = "trait object"] + style_and_layout_data: DomRefCell<Option<Box<StyleAndOpaqueLayoutData>>>, } bitflags! { #[doc = "Flags for node items."] - #[derive(JSTraceable, HeapSizeOf)] - pub flags NodeFlags: u8 { + #[derive(JSTraceable, MallocSizeOf)] + pub struct NodeFlags: u16 { #[doc = "Specifies whether this node is in a document."] - const IS_IN_DOC = 0x01, + const IS_IN_DOC = 1 << 0; + #[doc = "Specifies whether this node needs style recalc on next reflow."] - const HAS_DIRTY_DESCENDANTS = 0x08, - // TODO: find a better place to keep this (#4105) - // https://critic.hoppipolla.co.uk/showcomment?chain=8873 - // Perhaps using a Set in Document? + const HAS_DIRTY_DESCENDANTS = 1 << 1; + #[doc = "Specifies whether or not there is an authentic click in progress on \ this element."] - const CLICK_IN_PROGRESS = 0x10, + const CLICK_IN_PROGRESS = 1 << 2; + #[doc = "Specifies whether this node is focusable and whether it is supposed \ to be reachable with using sequential focus navigation."] - const SEQUENTIALLY_FOCUSABLE = 0x20, + const SEQUENTIALLY_FOCUSABLE = 1 << 3; - /// Whether any ancestor is a fragmentation container - const CAN_BE_FRAGMENTED = 0x40, - #[doc = "Specifies whether this node needs to be dirted when viewport size changed."] - const DIRTY_ON_VIEWPORT_SIZE_CHANGE = 0x80, + // There are two free bits here. #[doc = "Specifies whether the parser has set an associated form owner for \ this element. Only applicable for form-associatable elements."] - const PARSER_ASSOCIATED_FORM_OWNER = 0x90, + const PARSER_ASSOCIATED_FORM_OWNER = 1 << 6; + + /// Whether this element has a snapshot stored due to a style or + /// attribute change. + /// + /// See the `style::restyle_hints` module. + const HAS_SNAPSHOT = 1 << 7; + + /// Whether this element has already handled the stored snapshot. + const HANDLED_SNAPSHOT = 1 << 8; + + /// Whether this node participates in a shadow tree. + const IS_IN_SHADOW_TREE = 1 << 9; + + /// Specifies whether this node's shadow-including root is a document. + const IS_CONNECTED = 1 << 10; + + /// Whether this node has a weird parser insertion mode. i.e whether setting innerHTML + /// needs extra work or not + const HAS_WEIRD_PARSER_INSERTION_MODE = 1 << 11; } } @@ -176,33 +206,16 @@ impl NodeFlags { } } -impl Drop for Node { - #[allow(unsafe_code)] - fn drop(&mut self) { - self.style_and_layout_data.get().map(|d| self.dispose(d)); - } -} - /// suppress observers flag -/// https://dom.spec.whatwg.org/#concept-node-insert -/// https://dom.spec.whatwg.org/#concept-node-remove -#[derive(Copy, Clone, HeapSizeOf)] +/// <https://dom.spec.whatwg.org/#concept-node-insert> +/// <https://dom.spec.whatwg.org/#concept-node-remove> +#[derive(Clone, Copy, MallocSizeOf)] enum SuppressObserver { Suppressed, - Unsuppressed + Unsuppressed, } impl Node { - /// Sends the style and layout data, if any, back to the layout thread to be destroyed. - pub fn dispose(&self, data: OpaqueStyleAndLayoutData) { - debug_assert!(thread_state::get().is_script()); - let win = window_from_node(self); - self.style_and_layout_data.set(None); - if win.layout_chan().send(Msg::ReapStyleAndLayoutData(data)).is_err() { - warn!("layout thread unreachable - leaking layout data"); - } - } - /// Adds a new child to the end of this node's list of children. /// /// Fails unless `new_child` is disconnected from the tree. @@ -212,11 +225,11 @@ impl Node { assert!(new_child.next_sibling.get().is_none()); match before { Some(ref before) => { - assert!(before.parent_node.get().r() == Some(self)); + assert!(before.parent_node.get().as_deref() == Some(self)); let prev_sibling = before.GetPreviousSibling(); match prev_sibling { None => { - assert!(Some(*before) == self.first_child.get().r()); + assert!(self.first_child.get().as_deref() == Some(*before)); self.first_child.set(Some(new_child)); }, Some(ref prev_sibling) => { @@ -235,7 +248,7 @@ impl Node { assert!(last_child.next_sibling.get().is_none()); last_child.next_sibling.set(Some(new_child)); new_child.prev_sibling.set(Some(&last_child)); - } + }, } self.last_child.set(Some(new_child)); @@ -246,66 +259,186 @@ impl Node { self.children_count.set(self.children_count.get() + 1); let parent_in_doc = self.is_in_doc(); - for node in new_child.traverse_preorder() { - node.set_flag(IS_IN_DOC, parent_in_doc); + let parent_in_shadow_tree = self.is_in_shadow_tree(); + let parent_is_connected = self.is_connected(); + + for node in new_child.traverse_preorder(ShadowIncluding::No) { + if parent_in_shadow_tree { + if let Some(shadow_root) = self.containing_shadow_root() { + node.set_containing_shadow_root(Some(&*shadow_root)); + } + debug_assert!(node.containing_shadow_root().is_some()); + } + node.set_flag(NodeFlags::IS_IN_DOC, parent_in_doc); + node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, parent_in_shadow_tree); + node.set_flag(NodeFlags::IS_CONNECTED, parent_is_connected); // Out-of-document elements never have the descendants flag set. - debug_assert!(!node.get_flag(HAS_DIRTY_DESCENDANTS)); - vtable_for(&&*node).bind_to_tree(parent_in_doc); + debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS)); + vtable_for(&&*node).bind_to_tree(&BindContext { + tree_connected: parent_is_connected, + tree_in_doc: parent_in_doc, + }); + } + } + + pub fn clean_up_layout_data(&self) { + self.owner_doc().cancel_animations_for_node(self); + self.style_and_layout_data.borrow_mut().take(); + } + + /// Clean up flags and unbind from tree. + pub fn complete_remove_subtree(root: &Node, context: &UnbindContext) { + for node in root.traverse_preorder(ShadowIncluding::Yes) { + // Out-of-document elements never have the descendants flag set. + node.set_flag( + NodeFlags::IS_IN_DOC | + NodeFlags::IS_CONNECTED | + NodeFlags::HAS_DIRTY_DESCENDANTS | + NodeFlags::HAS_SNAPSHOT | + NodeFlags::HANDLED_SNAPSHOT, + false, + ); + } + for node in root.traverse_preorder(ShadowIncluding::Yes) { + node.clean_up_layout_data(); + + // This needs to be in its own loop, because unbind_from_tree may + // rely on the state of IS_IN_DOC of the context node's descendants, + // e.g. when removing a <form>. + vtable_for(&&*node).unbind_from_tree(&context); + // https://dom.spec.whatwg.org/#concept-node-remove step 14 + if let Some(element) = node.as_custom_element() { + ScriptThread::enqueue_callback_reaction( + &*element, + CallbackReaction::Disconnected, + None, + ); + } } - let document = new_child.owner_doc(); - document.content_and_heritage_changed(new_child, NodeDamage::OtherNodeDamage); } /// Removes the given child from this node's list of children. /// /// Fails unless `child` is a child of this node. fn remove_child(&self, child: &Node, cached_index: Option<u32>) { - assert!(child.parent_node.get().r() == Some(self)); + assert!(child.parent_node.get().as_deref() == Some(self)); + self.note_dirty_descendants(); + let prev_sibling = child.GetPreviousSibling(); match prev_sibling { None => { - self.first_child.set(child.next_sibling.get().r()); - } + self.first_child.set(child.next_sibling.get().as_deref()); + }, Some(ref prev_sibling) => { - prev_sibling.next_sibling.set(child.next_sibling.get().r()); - } + prev_sibling + .next_sibling + .set(child.next_sibling.get().as_deref()); + }, } let next_sibling = child.GetNextSibling(); match next_sibling { None => { - self.last_child.set(child.prev_sibling.get().r()); - } + self.last_child.set(child.prev_sibling.get().as_deref()); + }, Some(ref next_sibling) => { - next_sibling.prev_sibling.set(child.prev_sibling.get().r()); - } + next_sibling + .prev_sibling + .set(child.prev_sibling.get().as_deref()); + }, } - let context = UnbindContext::new(self, prev_sibling.r(), cached_index); + let context = UnbindContext::new( + self, + prev_sibling.as_deref(), + next_sibling.as_deref(), + cached_index, + ); child.prev_sibling.set(None); child.next_sibling.set(None); child.parent_node.set(None); self.children_count.set(self.children_count.get() - 1); - for node in child.traverse_preorder() { - // Out-of-document elements never have the descendants flag set. - node.set_flag(IS_IN_DOC | HAS_DIRTY_DESCENDANTS, false); - } - for node in child.traverse_preorder() { - // This needs to be in its own loop, because unbind_from_tree may - // rely on the state of IS_IN_DOC of the context node's descendants, - // e.g. when removing a <form>. - vtable_for(&&*node).unbind_from_tree(&context); - node.style_and_layout_data.get().map(|d| node.dispose(d)); - } - - self.owner_doc().content_and_heritage_changed(self, NodeDamage::OtherNodeDamage); - child.owner_doc().content_and_heritage_changed(child, NodeDamage::OtherNodeDamage); + Self::complete_remove_subtree(child, &context); } pub fn to_untrusted_node_address(&self) -> UntrustedNodeAddress { UntrustedNodeAddress(self.reflector().get_jsobject().get() as *const c_void) } + + pub fn to_opaque(&self) -> OpaqueNode { + OpaqueNode(self.reflector().get_jsobject().get() as usize) + } + + pub fn as_custom_element(&self) -> Option<DomRoot<Element>> { + self.downcast::<Element>().and_then(|element| { + if element.get_custom_element_definition().is_some() { + Some(DomRoot::from_ref(element)) + } else { + None + } + }) + } + + // https://html.spec.whatg.org/#fire_a_synthetic_mouse_event + pub fn fire_synthetic_mouse_event_not_trusted(&self, name: DOMString) { + // Spec says the choice of which global to create + // the mouse event on is not well-defined, + // and refers to heycam/webidl#135 + let win = window_from_node(self); + + let mouse_event = MouseEvent::new( + &win, // ambiguous in spec + name, + EventBubbles::Bubbles, // Step 3: bubbles + EventCancelable::Cancelable, // Step 3: cancelable, + Some(&win), // Step 7: view (this is unambiguous in spec) + 0, // detail uninitialized + 0, // coordinates uninitialized + 0, // coordinates uninitialized + 0, // coordinates uninitialized + 0, // coordinates uninitialized + false, + false, + false, + false, // Step 6 modifier keys TODO compositor hook needed + 0, // button uninitialized (and therefore left) + 0, // buttons uninitialized (and therefore none) + None, // related_target uninitialized, + None, // point_in_target uninitialized, + ); + + // Step 4: TODO composed flag for shadow root + + // Step 5 + mouse_event.upcast::<Event>().set_trusted(false); + + // Step 8: TODO keyboard modifiers + + mouse_event + .upcast::<Event>() + .dispatch(self.upcast::<EventTarget>(), false); + } + + pub fn parent_directionality(&self) -> String { + let mut current = self.GetParentNode(); + + loop { + match current { + Some(node) => { + if let Some(directionality) = node + .downcast::<HTMLElement>() + .and_then(|html_element| html_element.directionality()) + { + return directionality; + } else { + current = node.GetParentNode(); + } + }, + None => return "ltr".to_owned(), + } + } + } } pub struct QuerySelectorIterator { @@ -314,8 +447,7 @@ pub struct QuerySelectorIterator { } impl<'a> QuerySelectorIterator { - fn new(iter: TreeIterator, selectors: SelectorList<SelectorImpl>) - -> QuerySelectorIterator { + fn new(iter: TreeIterator, selectors: SelectorList<SelectorImpl>) -> QuerySelectorIterator { QuerySelectorIterator { selectors: selectors, iterator: iter, @@ -324,30 +456,78 @@ impl<'a> QuerySelectorIterator { } impl<'a> Iterator for QuerySelectorIterator { - type Item = Root<Node>; - - fn next(&mut self) -> Option<Root<Node>> { - let selectors = &self.selectors.0; - // TODO(cgaebel): Is it worth it to build a bloom filter here - // (instead of passing `None`)? Probably. - self.iterator.by_ref().filter_map(|node| { - if let Some(element) = Root::downcast(node) { - if matches_selector_list(selectors, &element, None) { - return Some(Root::upcast(element)); + type Item = DomRoot<Node>; + + fn next(&mut self) -> Option<DomRoot<Node>> { + let selectors = &self.selectors; + + self.iterator + .by_ref() + .filter_map(|node| { + // TODO(cgaebel): Is it worth it to build a bloom filter here + // (instead of passing `None`)? Probably. + // + // FIXME(bholley): Consider an nth-index cache here. + let mut ctx = MatchingContext::new( + MatchingMode::Normal, + None, + None, + node.owner_doc().quirks_mode(), + ); + if let Some(element) = DomRoot::downcast(node) { + if matches_selector_list(selectors, &element, &mut ctx) { + return Some(DomRoot::upcast(element)); + } } - } - None - }).next() + None + }) + .next() } } - impl Node { - pub fn teardown(&self) { - self.style_and_layout_data.get().map(|d| self.dispose(d)); - for kid in self.children() { - kid.teardown(); + impl_rare_data!(NodeRareData); + + /// Returns true if this node is before `other` in the same connected DOM + /// tree. + pub fn is_before(&self, other: &Node) -> bool { + let cmp = other.CompareDocumentPosition(self); + if cmp & NodeConstants::DOCUMENT_POSITION_DISCONNECTED != 0 { + return false; } + + cmp & NodeConstants::DOCUMENT_POSITION_PRECEDING != 0 + } + + /// Return all registered mutation observers for this node. Lazily initialize the + /// raredata if it does not exist. + pub fn registered_mutation_observers_mut(&self) -> RefMut<Vec<RegisteredObserver>> { + RefMut::map(self.ensure_rare_data(), |rare_data| { + &mut rare_data.mutation_observers + }) + } + + pub fn registered_mutation_observers(&self) -> Option<Ref<Vec<RegisteredObserver>>> { + let rare_data: Ref<_> = self.rare_data.borrow(); + + if rare_data.is_none() { + return None; + } + Some(Ref::map(rare_data, |rare_data| { + &rare_data.as_ref().unwrap().mutation_observers + })) + } + + /// Add a new mutation observer for a given node. + pub fn add_mutation_observer(&self, observer: RegisteredObserver) { + self.ensure_rare_data().mutation_observers.push(observer); + } + + /// Removes the mutation observer for a given node. + pub fn remove_mutation_observer(&self, observer: &MutationObserver) { + self.ensure_rare_data() + .mutation_observers + .retain(|reg_obs| &*reg_obs.observer != observer) } /// Dumps the subtree rooted at this node, for debugging. @@ -377,7 +557,25 @@ impl Node { } pub fn is_in_doc(&self) -> bool { - self.flags.get().contains(IS_IN_DOC) + self.flags.get().contains(NodeFlags::IS_IN_DOC) + } + + pub fn is_in_shadow_tree(&self) -> bool { + self.flags.get().contains(NodeFlags::IS_IN_SHADOW_TREE) + } + + pub fn has_weird_parser_insertion_mode(&self) -> bool { + self.flags + .get() + .contains(NodeFlags::HAS_WEIRD_PARSER_INSERTION_MODE) + } + + pub fn set_weird_parser_insertion_mode(&self) { + self.set_flag(NodeFlags::HAS_WEIRD_PARSER_INSERTION_MODE, true) + } + + pub fn is_connected(&self) -> bool { + self.flags.get().contains(NodeFlags::IS_CONNECTED) } /// Returns the type ID of this node. @@ -392,9 +590,7 @@ impl Node { pub fn len(&self) -> u32 { match self.type_id() { NodeTypeId::DocumentType => 0, - NodeTypeId::CharacterData(_) => { - self.downcast::<CharacterData>().unwrap().Length() - }, + NodeTypeId::CharacterData(_) => self.downcast::<CharacterData>().unwrap().Length(), _ => self.children_count(), } } @@ -404,6 +600,11 @@ impl Node { self.preceding_siblings().count() as u32 } + /// Returns true if this node has a parent. + pub fn has_parent(&self) -> bool { + self.parent_node.get().is_some() + } + pub fn children_count(&self) -> u32 { self.children_count.get() } @@ -433,8 +634,13 @@ impl Node { self.flags.set(flags); } + // FIXME(emilio): This and the function below should move to Element. + pub fn note_dirty_descendants(&self) { + self.owner_doc().note_node_with_dirty_descendants(self); + } + pub fn has_dirty_descendants(&self) -> bool { - self.get_flag(HAS_DIRTY_DESCENDANTS) + self.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) } pub fn rev_version(&self) { @@ -442,9 +648,12 @@ impl Node { // its descendants version, and the document's version. Normally, this will just be // the document's version, but we do have to deal with the case where the node has moved // document, so may have a higher version count than its owning document. - let doc: Root<Node> = Root::upcast(self.owner_doc()); - let version = max(self.inclusive_descendants_version(), doc.inclusive_descendants_version()) + 1; - for ancestor in self.inclusive_ancestors() { + let doc: DomRoot<Node> = DomRoot::upcast(self.owner_doc()); + let version = cmp::max( + self.inclusive_descendants_version(), + doc.inclusive_descendants_version(), + ) + 1; + for ancestor in self.inclusive_ancestors(ShadowIncluding::No) { ancestor.inclusive_descendants_version.set(version); } doc.inclusive_descendants_version.set(version); @@ -452,15 +661,21 @@ impl Node { pub fn dirty(&self, damage: NodeDamage) { self.rev_version(); - if !self.is_in_doc() { + if !self.is_connected() { return; } match self.type_id() { - NodeTypeId::CharacterData(CharacterDataTypeId::Text) => - self.parent_node.get().unwrap().downcast::<Element>().unwrap().restyle(damage), - NodeTypeId::Element(_) => - self.downcast::<Element>().unwrap().restyle(damage), + NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => { + self.parent_node.get().unwrap().dirty(damage) + }, + NodeTypeId::Element(_) => self.downcast::<Element>().unwrap().restyle(damage), + NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) => self + .downcast::<ShadowRoot>() + .unwrap() + .Host() + .upcast::<Element>() + .restyle(damage), _ => {}, }; } @@ -471,22 +686,40 @@ impl Node { } /// Iterates over this node and all its descendants, in preorder. - pub fn traverse_preorder(&self) -> TreeIterator { - TreeIterator::new(self) + pub fn traverse_preorder(&self, shadow_including: ShadowIncluding) -> TreeIterator { + TreeIterator::new(self, shadow_including) } - pub fn inclusively_following_siblings(&self) -> NodeSiblingIterator { - NodeSiblingIterator { - current: Some(Root::from_ref(self)), + pub fn inclusively_following_siblings(&self) -> impl Iterator<Item = DomRoot<Node>> { + SimpleNodeIterator { + current: Some(DomRoot::from_ref(self)), + next_node: |n| n.GetNextSibling(), } } - pub fn inclusively_preceding_siblings(&self) -> ReverseSiblingIterator { - ReverseSiblingIterator { - current: Some(Root::from_ref(self)), + pub fn inclusively_preceding_siblings(&self) -> impl Iterator<Item = DomRoot<Node>> { + SimpleNodeIterator { + current: Some(DomRoot::from_ref(self)), + next_node: |n| n.GetPreviousSibling(), } } + pub fn common_ancestor( + &self, + other: &Node, + shadow_including: ShadowIncluding, + ) -> Option<DomRoot<Node>> { + for ancestor in self.inclusive_ancestors(shadow_including) { + if other + .inclusive_ancestors(shadow_including) + .any(|node| node == ancestor) + { + return Some(ancestor); + } + } + None + } + pub fn is_inclusive_ancestor_of(&self, parent: &Node) -> bool { self == parent || self.is_ancestor_of(parent) } @@ -495,40 +728,51 @@ impl Node { parent.ancestors().any(|ancestor| &*ancestor == self) } - pub fn following_siblings(&self) -> NodeSiblingIterator { - NodeSiblingIterator { + fn is_shadow_including_inclusive_ancestor_of(&self, node: &Node) -> bool { + node.inclusive_ancestors(ShadowIncluding::Yes) + .any(|ancestor| &*ancestor == self) + } + + pub fn following_siblings(&self) -> impl Iterator<Item = DomRoot<Node>> { + SimpleNodeIterator { current: self.GetNextSibling(), + next_node: |n| n.GetNextSibling(), } } - pub fn preceding_siblings(&self) -> ReverseSiblingIterator { - ReverseSiblingIterator { + pub fn preceding_siblings(&self) -> impl Iterator<Item = DomRoot<Node>> { + SimpleNodeIterator { current: self.GetPreviousSibling(), + next_node: |n| n.GetPreviousSibling(), } } pub fn following_nodes(&self, root: &Node) -> FollowingNodeIterator { FollowingNodeIterator { - current: Some(Root::from_ref(self)), - root: Root::from_ref(root), + current: Some(DomRoot::from_ref(self)), + root: DomRoot::from_ref(root), } } pub fn preceding_nodes(&self, root: &Node) -> PrecedingNodeIterator { PrecedingNodeIterator { - current: Some(Root::from_ref(self)), - root: Root::from_ref(root), + current: Some(DomRoot::from_ref(self)), + root: DomRoot::from_ref(root), } } - pub fn descending_last_children(&self) -> LastChildIterator { - LastChildIterator { + pub fn descending_last_children(&self) -> impl Iterator<Item = DomRoot<Node>> { + SimpleNodeIterator { current: self.GetLastChild(), + next_node: |n| n.GetLastChild(), } } pub fn is_parent_of(&self, child: &Node) -> bool { - child.parent_node.get().map_or(false, |parent| &*parent == self) + child + .parent_node + .get() + .map_or(false, |parent| &*parent == self) } pub fn to_trusted_node_address(&self) -> TrustedNodeAddress { @@ -538,8 +782,7 @@ impl Node { /// Returns the rendered bounding content box if the element is rendered, /// and none otherwise. pub fn bounding_content_box(&self) -> Option<Rect<Au>> { - window_from_node(self) - .content_box_query(self.to_trusted_node_address()) + window_from_node(self).content_box_query(self) } pub fn bounding_content_box_or_zero(&self) -> Rect<Au> { @@ -547,17 +790,15 @@ impl Node { } pub fn content_boxes(&self) -> Vec<Rect<Au>> { - window_from_node(self).content_boxes_query(self.to_trusted_node_address()) + window_from_node(self).content_boxes_query(self) } pub fn client_rect(&self) -> Rect<i32> { - window_from_node(self).client_rect_query(self.to_trusted_node_address()) + window_from_node(self).client_rect_query(self) } // https://drafts.csswg.org/cssom-view/#dom-element-scrollwidth // https://drafts.csswg.org/cssom-view/#dom-element-scrollheight - // https://drafts.csswg.org/cssom-view/#dom-element-scrolltop - // https://drafts.csswg.org/cssom-view/#dom-element-scrollleft pub fn scroll_area(&self) -> Rect<i32> { // Step 1 let document = self.owner_doc(); @@ -566,30 +807,37 @@ impl Node { let html_element = document.GetDocumentElement(); - let is_body_element = self.downcast::<HTMLBodyElement>() - .map_or(false, |e| e.is_the_html_body_element()); + let is_body_element = self + .downcast::<HTMLBodyElement>() + .map_or(false, |e| e.is_the_html_body_element()); - let scroll_area = window.scroll_area_query(self.to_trusted_node_address()); + let scroll_area = window.scroll_area_query(self); - match (document != window.Document(), is_body_element, document.quirks_mode(), - html_element.r() == self.downcast::<Element>()) { + match ( + document != window.Document(), + is_body_element, + document.quirks_mode(), + html_element.as_deref() == self.downcast::<Element>(), + ) { // Step 2 && Step 5 (true, _, _, _) | (_, false, QuirksMode::Quirks, true) => Rect::zero(), // Step 6 && Step 7 - (false, false, _, true) | (false, true, QuirksMode::Quirks, _) => { - Rect::new(Point2D::new(window.ScrollX(), window.ScrollY()), - Size2D::new(max(window.InnerWidth(), scroll_area.size.width), - max(window.InnerHeight(), scroll_area.size.height))) - }, + (false, false, _, true) | (false, true, QuirksMode::Quirks, _) => Rect::new( + Point2D::new(window.ScrollX(), window.ScrollY()), + Size2D::new( + cmp::max(window.InnerWidth(), scroll_area.size.width), + cmp::max(window.InnerHeight(), scroll_area.size.height), + ), + ), // Step 9 - _ => scroll_area + _ => scroll_area, } } - pub fn scroll_offset(&self) -> Point2D<f32> { + pub fn scroll_offset(&self) -> Vector2D<f32> { let document = self.owner_doc(); let window = document.window(); - window.scroll_offset_query(self) + window.scroll_offset_query(self).to_untyped() } // https://dom.spec.whatwg.org/#dom-childnode-before @@ -607,7 +855,7 @@ impl Node { let viable_previous_sibling = first_node_not_in(self.preceding_siblings(), &nodes); // Step 4. - let node = try!(self.owner_doc().node_from_nodes_and_strings(nodes)); + let node = self.owner_doc().node_from_nodes_and_strings(nodes)?; // Step 5. let viable_previous_sibling = match viable_previous_sibling { @@ -616,7 +864,7 @@ impl Node { }; // Step 6. - try!(Node::pre_insert(&node, &parent, viable_previous_sibling.r())); + Node::pre_insert(&node, &parent, viable_previous_sibling.as_deref())?; Ok(()) } @@ -636,10 +884,10 @@ impl Node { let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes); // Step 4. - let node = try!(self.owner_doc().node_from_nodes_and_strings(nodes)); + let node = self.owner_doc().node_from_nodes_and_strings(nodes)?; // Step 5. - try!(Node::pre_insert(&node, &parent, viable_next_sibling.r())); + Node::pre_insert(&node, &parent, viable_next_sibling.as_deref())?; Ok(()) } @@ -656,13 +904,13 @@ impl Node { // Step 3. let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes); // Step 4. - let node = try!(self.owner_doc().node_from_nodes_and_strings(nodes)); + let node = self.owner_doc().node_from_nodes_and_strings(nodes)?; if self.parent_node == Some(&*parent) { // Step 5. - try!(parent.ReplaceChild(&node, self)); + parent.ReplaceChild(&node, self)?; } else { // Step 6. - try!(Node::pre_insert(&node, &parent, viable_next_sibling.r())); + Node::pre_insert(&node, &parent, viable_next_sibling.as_deref())?; } Ok(()) } @@ -671,77 +919,109 @@ impl Node { pub fn prepend(&self, nodes: Vec<NodeOrString>) -> ErrorResult { // Step 1. let doc = self.owner_doc(); - let node = try!(doc.node_from_nodes_and_strings(nodes)); + let node = doc.node_from_nodes_and_strings(nodes)?; // Step 2. let first_child = self.first_child.get(); - Node::pre_insert(&node, self, first_child.r()).map(|_| ()) + Node::pre_insert(&node, self, first_child.as_deref()).map(|_| ()) } // https://dom.spec.whatwg.org/#dom-parentnode-append pub fn append(&self, nodes: Vec<NodeOrString>) -> ErrorResult { // Step 1. let doc = self.owner_doc(); - let node = try!(doc.node_from_nodes_and_strings(nodes)); + let node = doc.node_from_nodes_and_strings(nodes)?; // Step 2. self.AppendChild(&node).map(|_| ()) } + // https://dom.spec.whatwg.org/#dom-parentnode-replacechildren + pub fn replace_children(&self, nodes: Vec<NodeOrString>) -> ErrorResult { + // Step 1. + let doc = self.owner_doc(); + let node = doc.node_from_nodes_and_strings(nodes)?; + // Step 2. + Node::ensure_pre_insertion_validity(&node, self, None)?; + // Step 3. + Node::replace_all(Some(&node), self); + Ok(()) + } + // https://dom.spec.whatwg.org/#dom-parentnode-queryselector - pub fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> { + pub fn query_selector(&self, selectors: DOMString) -> Fallible<Option<DomRoot<Element>>> { // Step 1. match SelectorParser::parse_author_origin_no_namespace(&selectors) { // Step 2. - Err(()) => Err(Error::Syntax), + Err(_) => Err(Error::Syntax), // Step 3. Ok(selectors) => { - Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| { - matches_selector_list(&selectors.0, element, None) - })) - } + // FIXME(bholley): Consider an nth-index cache here. + let mut ctx = MatchingContext::new( + MatchingMode::Normal, + None, + None, + self.owner_doc().quirks_mode(), + ); + Ok(self + .traverse_preorder(ShadowIncluding::No) + .filter_map(DomRoot::downcast) + .find(|element| matches_selector_list(&selectors, element, &mut ctx))) + }, } } - /// https://dom.spec.whatwg.org/#scope-match-a-selectors-string + /// <https://dom.spec.whatwg.org/#scope-match-a-selectors-string> /// Get an iterator over all nodes which match a set of selectors /// Be careful not to do anything which may manipulate the DOM tree /// whilst iterating, otherwise the iterator may be invalidated. - pub fn query_selector_iter(&self, selectors: DOMString) - -> Fallible<QuerySelectorIterator> { + pub fn query_selector_iter(&self, selectors: DOMString) -> Fallible<QuerySelectorIterator> { // Step 1. match SelectorParser::parse_author_origin_no_namespace(&selectors) { // Step 2. - Err(()) => Err(Error::Syntax), + Err(_) => Err(Error::Syntax), // Step 3. Ok(selectors) => { - let mut descendants = self.traverse_preorder(); + let mut descendants = self.traverse_preorder(ShadowIncluding::No); // Skip the root of the tree. assert!(&*descendants.next().unwrap() == self); Ok(QuerySelectorIterator::new(descendants, selectors)) - } + }, } } // https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall #[allow(unsafe_code)] - pub fn query_selector_all(&self, selectors: DOMString) -> Fallible<Root<NodeList>> { + pub fn query_selector_all(&self, selectors: DOMString) -> Fallible<DomRoot<NodeList>> { let window = window_from_node(self); - let iter = try!(self.query_selector_iter(selectors)); + let iter = self.query_selector_iter(selectors)?; Ok(NodeList::new_simple_list(&window, iter)) } - pub fn ancestors(&self) -> AncestorIterator { - AncestorIterator { - current: self.GetParentNode() + pub fn ancestors(&self) -> impl Iterator<Item = DomRoot<Node>> { + SimpleNodeIterator { + current: self.GetParentNode(), + next_node: |n| n.GetParentNode(), } } - pub fn inclusive_ancestors(&self) -> AncestorIterator { - AncestorIterator { - current: Some(Root::from_ref(self)) + /// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor + pub fn inclusive_ancestors( + &self, + shadow_including: ShadowIncluding, + ) -> impl Iterator<Item = DomRoot<Node>> { + SimpleNodeIterator { + current: Some(DomRoot::from_ref(self)), + next_node: move |n| { + if shadow_including == ShadowIncluding::Yes { + if let Some(shadow_root) = n.downcast::<ShadowRoot>() { + return Some(DomRoot::from_ref(shadow_root.Host().upcast::<Node>())); + } + } + n.GetParentNode() + }, } } - pub fn owner_doc(&self) -> Root<Document> { + pub fn owner_doc(&self) -> DomRoot<Document> { self.owner_doc.get().unwrap() } @@ -749,28 +1029,44 @@ impl Node { self.owner_doc.set(Some(document)); } + pub fn containing_shadow_root(&self) -> Option<DomRoot<ShadowRoot>> { + self.rare_data() + .as_ref()? + .containing_shadow_root + .as_ref() + .map(|sr| DomRoot::from_ref(&**sr)) + } + + pub fn set_containing_shadow_root(&self, shadow_root: Option<&ShadowRoot>) { + self.ensure_rare_data().containing_shadow_root = shadow_root.map(Dom::from_ref); + } + pub fn is_in_html_doc(&self) -> bool { self.owner_doc().is_html_document() } - pub fn is_in_doc_with_browsing_context(&self) -> bool { - self.is_in_doc() && self.owner_doc().browsing_context().is_some() + pub fn is_connected_with_browsing_context(&self) -> bool { + self.is_connected() && self.owner_doc().browsing_context().is_some() } - pub fn children(&self) -> NodeSiblingIterator { - NodeSiblingIterator { + pub fn children(&self) -> impl Iterator<Item = DomRoot<Node>> { + SimpleNodeIterator { current: self.GetFirstChild(), + next_node: |n| n.GetNextSibling(), } } - pub fn rev_children(&self) -> ReverseSiblingIterator { - ReverseSiblingIterator { + pub fn rev_children(&self) -> impl Iterator<Item = DomRoot<Node>> { + SimpleNodeIterator { current: self.GetLastChild(), + next_node: |n| n.GetPreviousSibling(), } } - pub fn child_elements(&self) -> impl Iterator<Item=Root<Element>> { - self.children().filter_map(Root::downcast as fn(_) -> _).peekable() + pub fn child_elements(&self) -> impl Iterator<Item = DomRoot<Element>> { + self.children() + .filter_map(DomRoot::downcast as fn(_) -> _) + .peekable() } pub fn remove_self(&self) { @@ -780,7 +1076,20 @@ impl Node { } pub fn unique_id(&self) -> String { - self.unique_id.borrow().simple().to_string() + let mut rare_data = self.ensure_rare_data(); + + if rare_data.unique_id.is_none() { + let id = UniqueId::new(); + ScriptThread::save_node_id(id.borrow().to_simple().to_string()); + rare_data.unique_id = Some(id); + } + rare_data + .unique_id + .as_ref() + .unwrap() + .borrow() + .to_simple() + .to_string() } pub fn summarize(&self) -> NodeInfo { @@ -788,7 +1097,9 @@ impl Node { NodeInfo { uniqueId: self.unique_id(), baseURI: base_uri, - parent: self.GetParentNode().map_or("".to_owned(), |node| node.unique_id()), + parent: self + .GetParentNode() + .map_or("".to_owned(), |node| node.unique_id()), nodeType: self.NodeType(), namespaceURI: String::new(), //FIXME nodeName: String::from(self.NodeName()), @@ -800,10 +1111,10 @@ impl Node { systemId: String::new(), attrs: self.downcast().map(Element::summarize).unwrap_or(vec![]), - isDocumentElement: - self.owner_doc() - .GetDocumentElement() - .map_or(false, |elem| elem.upcast::<Node>() == self), + isDocumentElement: self + .owner_doc() + .GetDocumentElement() + .map_or(false, |elem| elem.upcast::<Node>() == self), shortValue: self.GetNodeValue().map(String::from).unwrap_or_default(), //FIXME: truncate incompleteValue: false, //FIXME: reflect truncation @@ -811,10 +1122,16 @@ impl Node { } /// Used by `HTMLTableSectionElement::InsertRow` and `HTMLTableRowElement::InsertCell` - pub fn insert_cell_or_row<F, G, I>(&self, index: i32, get_items: F, new_child: G) -> Fallible<Root<HTMLElement>> - where F: Fn() -> Root<HTMLCollection>, - G: Fn() -> Root<I>, - I: DerivedFrom<Node> + DerivedFrom<HTMLElement> + DomObject, + pub fn insert_cell_or_row<F, G, I>( + &self, + index: i32, + get_items: F, + new_child: G, + ) -> Fallible<DomRoot<HTMLElement>> + where + F: Fn() -> DomRoot<HTMLCollection>, + G: Fn() -> DomRoot<I>, + I: DerivedFrom<Node> + DerivedFrom<HTMLElement> + DomObject, { if index < -1 { return Err(Error::IndexSize); @@ -822,41 +1139,50 @@ impl Node { let tr = new_child(); - { let tr_node = tr.upcast::<Node>(); if index == -1 { - try!(self.InsertBefore(tr_node, None)); + self.InsertBefore(tr_node, None)?; } else { let items = get_items(); - let node = match items.elements_iter() - .map(Root::upcast::<Node>) - .map(Some) - .chain(iter::once(None)) - .nth(index as usize) { + let node = match items + .elements_iter() + .map(DomRoot::upcast::<Node>) + .map(Some) + .chain(iter::once(None)) + .nth(index as usize) + { None => return Err(Error::IndexSize), Some(node) => node, }; - try!(self.InsertBefore(tr_node, node.r())); + self.InsertBefore(tr_node, node.as_deref())?; } } - Ok(Root::upcast::<HTMLElement>(tr)) + Ok(DomRoot::upcast::<HTMLElement>(tr)) } /// Used by `HTMLTableSectionElement::DeleteRow` and `HTMLTableRowElement::DeleteCell` - pub fn delete_cell_or_row<F, G>(&self, index: i32, get_items: F, is_delete_type: G) -> ErrorResult - where F: Fn() -> Root<HTMLCollection>, - G: Fn(&Element) -> bool + pub fn delete_cell_or_row<F, G>( + &self, + index: i32, + get_items: F, + is_delete_type: G, + ) -> ErrorResult + where + F: Fn() -> DomRoot<HTMLCollection>, + G: Fn(&Element) -> bool, { let element = match index { index if index < -1 => return Err(Error::IndexSize), -1 => { let last_child = self.upcast::<Node>().GetLastChild(); - match last_child.and_then(|node| node.inclusively_preceding_siblings() - .filter_map(Root::downcast::<Element>) - .filter(|elem| is_delete_type(elem)) - .next()) { + match last_child.and_then(|node| { + node.inclusively_preceding_siblings() + .filter_map(DomRoot::downcast::<Element>) + .filter(|elem| is_delete_type(elem)) + .next() + }) { Some(element) => element, None => return Ok(()), } @@ -883,7 +1209,7 @@ impl Node { } } - pub fn get_cssom_stylesheet(&self) -> Option<Root<CSSStyleSheet>> { + pub fn get_cssom_stylesheet(&self) -> Option<DomRoot<CSSStyleSheet>> { if let Some(node) = self.downcast::<HTMLStyleElement>() { node.get_cssom_stylesheet() } else if let Some(node) = self.downcast::<HTMLLinkElement>() { @@ -894,19 +1220,100 @@ impl Node { None } } -} + /// https://dom.spec.whatwg.org/#retarget + pub fn retarget(&self, b: &Node) -> DomRoot<Node> { + let mut a = DomRoot::from_ref(&*self); + loop { + // Step 1. + let a_root = a.GetRootNode(&GetRootNodeOptions::empty()); + if !a_root.is::<ShadowRoot>() || a_root.is_shadow_including_inclusive_ancestor_of(b) { + return DomRoot::from_ref(&a); + } + + // Step 2. + a = DomRoot::from_ref( + a_root + .downcast::<ShadowRoot>() + .unwrap() + .Host() + .upcast::<Node>(), + ); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-document-nameditem-filter + pub fn is_document_named_item(&self, name: &Atom) -> bool { + let html_elem_type = match self.type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement(type_)) => type_, + _ => return false, + }; + let elem = self + .downcast::<Element>() + .expect("Node with an Element::HTMLElement NodeTypeID must be an Element"); + match html_elem_type { + HTMLElementTypeId::HTMLFormElement | HTMLElementTypeId::HTMLIFrameElement => { + elem.get_name().map_or(false, |n| n == *name) + }, + HTMLElementTypeId::HTMLImageElement => + // Images can match by id, but only when their name is non-empty. + { + elem.get_name().map_or(false, |n| { + n == *name || elem.get_id().map_or(false, |i| i == *name) + }) + }, + // TODO: Handle <embed> and <object>; these depend on + // whether the element is "exposed", a concept which + // doesn't fully make sense until embed/object behaviors + // are actually implemented. + _ => false, + } + } + + pub fn is_styled(&self) -> bool { + self.style_and_layout_data.borrow().is_some() + } + + pub fn is_display_none(&self) -> bool { + self.style_and_layout_data + .borrow() + .as_ref() + .map_or(true, |data| { + data.style_data + .element_data + .borrow() + .styles + .primary() + .get_box() + .display + .is_none() + }) + } + + pub fn style(&self) -> Option<Arc<ComputedValues>> { + if !window_from_node(self).layout_reflow(QueryMsg::StyleQuery) { + return None; + } + self.style_and_layout_data.borrow().as_ref().map(|data| { + data.style_data + .element_data + .borrow() + .styles + .primary() + .clone() + }) + } +} /// Iterate through `nodes` until we find a `Node` that is not in `not_in` -fn first_node_not_in<I>(mut nodes: I, not_in: &[NodeOrString]) -> Option<Root<Node>> - where I: Iterator<Item=Root<Node>> +fn first_node_not_in<I>(mut nodes: I, not_in: &[NodeOrString]) -> Option<DomRoot<Node>> +where + I: Iterator<Item = DomRoot<Node>>, { nodes.find(|node| { - not_in.iter().all(|n| { - match *n { - NodeOrString::Node(ref n) => n != node, - _ => true, - } + not_in.iter().all(|n| match *n { + NodeOrString::Node(ref n) => n != node, + _ => true, }) }) } @@ -914,111 +1321,143 @@ fn first_node_not_in<I>(mut nodes: I, not_in: &[NodeOrString]) -> Option<Root<No /// If the given untrusted node address represents a valid DOM node in the given runtime, /// returns it. #[allow(unsafe_code)] -pub fn from_untrusted_node_address(_runtime: *mut JSRuntime, candidate: UntrustedNodeAddress) - -> Root<Node> { - unsafe { - // https://github.com/servo/servo/issues/6383 - let candidate: uintptr_t = mem::transmute(candidate.0); -// let object: *mut JSObject = jsfriendapi::bindgen::JS_GetAddressableObject(runtime, -// candidate); - let object: *mut JSObject = mem::transmute(candidate); - if object.is_null() { - panic!("Attempted to create a `JS<Node>` from an invalid pointer!") - } - let boxed_node = conversions::private_from_object(object) as *const Node; - Root::from_ref(&*boxed_node) - } +pub unsafe fn from_untrusted_node_address(candidate: UntrustedNodeAddress) -> DomRoot<Node> { + // https://github.com/servo/servo/issues/6383 + let candidate: uintptr_t = mem::transmute(candidate.0); + // let object: *mut JSObject = jsfriendapi::bindgen::JS_GetAddressableObject(runtime, + // candidate); + let object: *mut JSObject = mem::transmute(candidate); + if object.is_null() { + panic!("Attempted to create a `Dom<Node>` from an invalid pointer!") + } + let boxed_node = conversions::private_from_object(object) as *const Node; + DomRoot::from_ref(&*boxed_node) } #[allow(unsafe_code)] -pub trait LayoutNodeHelpers { - unsafe fn type_id_for_layout(&self) -> NodeTypeId; - - unsafe fn parent_node_ref(&self) -> Option<LayoutJS<Node>>; - unsafe fn first_child_ref(&self) -> Option<LayoutJS<Node>>; - unsafe fn last_child_ref(&self) -> Option<LayoutJS<Node>>; - unsafe fn prev_sibling_ref(&self) -> Option<LayoutJS<Node>>; - unsafe fn next_sibling_ref(&self) -> Option<LayoutJS<Node>>; - - unsafe fn owner_doc_for_layout(&self) -> LayoutJS<Document>; - - unsafe fn is_element_for_layout(&self) -> bool; - unsafe fn get_flag(&self, flag: NodeFlags) -> bool; - unsafe fn set_flag(&self, flag: NodeFlags, value: bool); - - unsafe fn children_count(&self) -> u32; - - unsafe fn get_style_and_layout_data(&self) -> Option<OpaqueStyleAndLayoutData>; - unsafe fn init_style_and_layout_data(&self, OpaqueStyleAndLayoutData); - unsafe fn take_style_and_layout_data(&self) -> OpaqueStyleAndLayoutData; - - fn text_content(&self) -> String; - fn selection(&self) -> Option<Range<usize>>; - fn image_url(&self) -> Option<ServoUrl>; - fn canvas_data(&self) -> Option<HTMLCanvasData>; - fn svg_data(&self) -> Option<SVGSVGData>; - fn iframe_pipeline_id(&self) -> PipelineId; - fn opaque(&self) -> OpaqueNode; +pub trait LayoutNodeHelpers<'dom> { + fn type_id_for_layout(self) -> NodeTypeId; + + fn composed_parent_node_ref(self) -> Option<LayoutDom<'dom, Node>>; + fn first_child_ref(self) -> Option<LayoutDom<'dom, Node>>; + fn last_child_ref(self) -> Option<LayoutDom<'dom, Node>>; + fn prev_sibling_ref(self) -> Option<LayoutDom<'dom, Node>>; + fn next_sibling_ref(self) -> Option<LayoutDom<'dom, Node>>; + + fn owner_doc_for_layout(self) -> LayoutDom<'dom, Document>; + fn containing_shadow_root_for_layout(self) -> Option<LayoutDom<'dom, ShadowRoot>>; + + fn is_element_for_layout(self) -> bool; + unsafe fn get_flag(self, flag: NodeFlags) -> bool; + unsafe fn set_flag(self, flag: NodeFlags, value: bool); + + fn children_count(self) -> u32; + + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData>; + unsafe fn init_style_and_opaque_layout_data(self, data: Box<StyleAndOpaqueLayoutData>); + unsafe fn take_style_and_opaque_layout_data(self) -> Box<StyleAndOpaqueLayoutData>; + + 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 canvas_data(self) -> Option<HTMLCanvasData>; + fn media_data(self) -> Option<HTMLMediaData>; + fn svg_data(self) -> Option<SVGSVGData>; + fn iframe_browsing_context_id(self) -> Option<BrowsingContextId>; + fn iframe_pipeline_id(self) -> Option<PipelineId>; + fn opaque(self) -> OpaqueNode; } -impl LayoutNodeHelpers for LayoutJS<Node> { +impl<'dom> LayoutDom<'dom, Node> { #[inline] #[allow(unsafe_code)] - unsafe fn type_id_for_layout(&self) -> NodeTypeId { - (*self.unsafe_get()).type_id() + fn parent_node_ref(self) -> Option<LayoutDom<'dom, Node>> { + unsafe { self.unsafe_get().parent_node.get_inner_as_layout() } } +} +impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> { #[inline] #[allow(unsafe_code)] - unsafe fn is_element_for_layout(&self) -> bool { - (*self.unsafe_get()).is::<Element>() + fn type_id_for_layout(self) -> NodeTypeId { + unsafe { self.unsafe_get().type_id() } + } + + #[inline] + fn is_element_for_layout(self) -> bool { + self.is::<Element>() + } + + #[inline] + fn composed_parent_node_ref(self) -> Option<LayoutDom<'dom, Node>> { + let parent = self.parent_node_ref(); + if let Some(parent) = parent { + if let Some(shadow_root) = parent.downcast::<ShadowRoot>() { + return Some(shadow_root.get_host_for_layout().upcast()); + } + } + parent } #[inline] #[allow(unsafe_code)] - unsafe fn parent_node_ref(&self) -> Option<LayoutJS<Node>> { - (*self.unsafe_get()).parent_node.get_inner_as_layout() + fn first_child_ref(self) -> Option<LayoutDom<'dom, Node>> { + unsafe { self.unsafe_get().first_child.get_inner_as_layout() } } #[inline] #[allow(unsafe_code)] - unsafe fn first_child_ref(&self) -> Option<LayoutJS<Node>> { - (*self.unsafe_get()).first_child.get_inner_as_layout() + fn last_child_ref(self) -> Option<LayoutDom<'dom, Node>> { + unsafe { self.unsafe_get().last_child.get_inner_as_layout() } } #[inline] #[allow(unsafe_code)] - unsafe fn last_child_ref(&self) -> Option<LayoutJS<Node>> { - (*self.unsafe_get()).last_child.get_inner_as_layout() + fn prev_sibling_ref(self) -> Option<LayoutDom<'dom, Node>> { + unsafe { self.unsafe_get().prev_sibling.get_inner_as_layout() } } #[inline] #[allow(unsafe_code)] - unsafe fn prev_sibling_ref(&self) -> Option<LayoutJS<Node>> { - (*self.unsafe_get()).prev_sibling.get_inner_as_layout() + fn next_sibling_ref(self) -> Option<LayoutDom<'dom, Node>> { + unsafe { self.unsafe_get().next_sibling.get_inner_as_layout() } } #[inline] #[allow(unsafe_code)] - unsafe fn next_sibling_ref(&self) -> Option<LayoutJS<Node>> { - (*self.unsafe_get()).next_sibling.get_inner_as_layout() + fn owner_doc_for_layout(self) -> LayoutDom<'dom, Document> { + unsafe { self.unsafe_get().owner_doc.get_inner_as_layout().unwrap() } } #[inline] #[allow(unsafe_code)] - unsafe fn owner_doc_for_layout(&self) -> LayoutJS<Document> { - (*self.unsafe_get()).owner_doc.get_inner_as_layout().unwrap() + fn containing_shadow_root_for_layout(self) -> Option<LayoutDom<'dom, ShadowRoot>> { + unsafe { + self.unsafe_get() + .rare_data + .borrow_for_layout() + .as_ref()? + .containing_shadow_root + .as_ref() + .map(|sr| sr.to_layout()) + } } + // FIXME(nox): get_flag/set_flag (especially the latter) are not safe because + // they mutate stuff while values of this type can be used from multiple + // threads at once, this should be revisited. + #[inline] #[allow(unsafe_code)] - unsafe fn get_flag(&self, flag: NodeFlags) -> bool { + unsafe fn get_flag(self, flag: NodeFlags) -> bool { (*self.unsafe_get()).flags.get().contains(flag) } #[inline] #[allow(unsafe_code)] - unsafe fn set_flag(&self, flag: NodeFlags, value: bool) { + unsafe fn set_flag(self, flag: NodeFlags, value: bool) { let this = self.unsafe_get(); let mut flags = (*this).flags.get(); @@ -1033,150 +1472,140 @@ impl LayoutNodeHelpers for LayoutJS<Node> { #[inline] #[allow(unsafe_code)] - unsafe fn children_count(&self) -> u32 { - (*self.unsafe_get()).children_count.get() + fn children_count(self) -> u32 { + unsafe { self.unsafe_get().children_count.get() } } + // FIXME(nox): How we handle style and layout data needs to be completely + // revisited so we can do that more cleanly and safely in layout 2020. + #[inline] #[allow(unsafe_code)] - unsafe fn get_style_and_layout_data(&self) -> Option<OpaqueStyleAndLayoutData> { - (*self.unsafe_get()).style_and_layout_data.get() + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { + unsafe { + self.unsafe_get() + .style_and_layout_data + .borrow_for_layout() + .as_deref() + } } #[inline] #[allow(unsafe_code)] - unsafe fn init_style_and_layout_data(&self, val: OpaqueStyleAndLayoutData) { - debug_assert!((*self.unsafe_get()).style_and_layout_data.get().is_none()); - (*self.unsafe_get()).style_and_layout_data.set(Some(val)); + unsafe fn init_style_and_opaque_layout_data(self, val: Box<StyleAndOpaqueLayoutData>) { + let data = self + .unsafe_get() + .style_and_layout_data + .borrow_mut_for_layout(); + debug_assert!(data.is_none()); + *data = Some(val); } #[inline] #[allow(unsafe_code)] - unsafe fn take_style_and_layout_data(&self) -> OpaqueStyleAndLayoutData { - let val = (*self.unsafe_get()).style_and_layout_data.get().unwrap(); - (*self.unsafe_get()).style_and_layout_data.set(None); - val + unsafe fn take_style_and_opaque_layout_data(self) -> Box<StyleAndOpaqueLayoutData> { + self.unsafe_get() + .style_and_layout_data + .borrow_mut_for_layout() + .take() + .unwrap() } - #[allow(unsafe_code)] - fn text_content(&self) -> String { + fn text_content(self) -> Cow<'dom, str> { if let Some(text) = self.downcast::<Text>() { - return unsafe { text.upcast().data_for_layout().to_owned() }; + return text.upcast().data_for_layout().into(); } if let Some(input) = self.downcast::<HTMLInputElement>() { - return unsafe { input.value_for_layout() }; + return input.value_for_layout(); } if let Some(area) = self.downcast::<HTMLTextAreaElement>() { - return unsafe { area.get_value_for_layout() }; + return area.value_for_layout().into(); } panic!("not text!") } - #[allow(unsafe_code)] - fn selection(&self) -> Option<Range<usize>> { + fn selection(self) -> Option<Range<usize>> { if let Some(area) = self.downcast::<HTMLTextAreaElement>() { - return unsafe { area.selection_for_layout() }; + return area.selection_for_layout(); } if let Some(input) = self.downcast::<HTMLInputElement>() { - return unsafe { input.selection_for_layout() }; + return input.selection_for_layout(); } None } - #[allow(unsafe_code)] - fn image_url(&self) -> Option<ServoUrl> { - unsafe { - self.downcast::<HTMLImageElement>() - .expect("not an image!") - .image_url() - } + fn image_url(self) -> Option<ServoUrl> { + self.downcast::<HTMLImageElement>() + .expect("not an image!") + .image_url() + } + + fn image_data(self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)> { + self.downcast::<HTMLImageElement>().map(|e| e.image_data()) } - fn canvas_data(&self) -> Option<HTMLCanvasData> { + fn image_density(self) -> Option<f64> { + self.downcast::<HTMLImageElement>() + .expect("not an image!") + .image_density() + } + + fn canvas_data(self) -> Option<HTMLCanvasData> { self.downcast::<HTMLCanvasElement>() .map(|canvas| canvas.data()) } - fn svg_data(&self) -> Option<SVGSVGData> { - self.downcast::<SVGSVGElement>() - .map(|svg| svg.data()) + fn media_data(self) -> Option<HTMLMediaData> { + self.downcast::<HTMLMediaElement>() + .map(|media| media.data()) + } + + fn svg_data(self) -> Option<SVGSVGData> { + self.downcast::<SVGSVGElement>().map(|svg| svg.data()) + } + + fn iframe_browsing_context_id(self) -> Option<BrowsingContextId> { + let iframe_element = self + .downcast::<HTMLIFrameElement>() + .expect("not an iframe element!"); + iframe_element.browsing_context_id() } - fn iframe_pipeline_id(&self) -> PipelineId { - let iframe_element = self.downcast::<HTMLIFrameElement>() + fn iframe_pipeline_id(self) -> Option<PipelineId> { + let iframe_element = self + .downcast::<HTMLIFrameElement>() .expect("not an iframe element!"); - iframe_element.pipeline_id().unwrap() + iframe_element.pipeline_id() } #[allow(unsafe_code)] - fn opaque(&self) -> OpaqueNode { - unsafe { - OpaqueNode(self.get_jsobject() as usize) - } + fn opaque(self) -> OpaqueNode { + unsafe { OpaqueNode(self.get_jsobject() as usize) } } } - // // Iteration and traversal // -pub struct NodeSiblingIterator { - current: Option<Root<Node>>, -} - -impl Iterator for NodeSiblingIterator { - type Item = Root<Node>; - - fn next(&mut self) -> Option<Root<Node>> { - let current = match self.current.take() { - None => return None, - Some(current) => current, - }; - self.current = current.GetNextSibling(); - Some(current) - } -} - -pub struct ReverseSiblingIterator { - current: Option<Root<Node>>, -} - -impl Iterator for ReverseSiblingIterator { - type Item = Root<Node>; - - fn next(&mut self) -> Option<Root<Node>> { - let current = match self.current.take() { - None => return None, - Some(current) => current, - }; - self.current = current.GetPreviousSibling(); - Some(current) - } -} - pub struct FollowingNodeIterator { - current: Option<Root<Node>>, - root: Root<Node>, + current: Option<DomRoot<Node>>, + root: DomRoot<Node>, } impl FollowingNodeIterator { /// Skips iterating the children of the current node - pub fn next_skipping_children(&mut self) -> Option<Root<Node>> { - let current = match self.current.take() { - None => return None, - Some(current) => current, - }; - + pub fn next_skipping_children(&mut self) -> Option<DomRoot<Node>> { + let current = self.current.take()?; self.next_skipping_children_impl(current) } - fn next_skipping_children_impl(&mut self, current: Root<Node>) -> Option<Root<Node>> { + fn next_skipping_children_impl(&mut self, current: DomRoot<Node>) -> Option<DomRoot<Node>> { if self.root == current { self.current = None; return None; @@ -1184,16 +1613,16 @@ impl FollowingNodeIterator { if let Some(next_sibling) = current.GetNextSibling() { self.current = Some(next_sibling); - return current.GetNextSibling() + return current.GetNextSibling(); } - for ancestor in current.inclusive_ancestors() { + for ancestor in current.inclusive_ancestors(ShadowIncluding::No) { if self.root == ancestor { break; } if let Some(next_sibling) = ancestor.GetNextSibling() { self.current = Some(next_sibling); - return ancestor.GetNextSibling() + return ancestor.GetNextSibling(); } } self.current = None; @@ -1202,18 +1631,15 @@ impl FollowingNodeIterator { } impl Iterator for FollowingNodeIterator { - type Item = Root<Node>; + type Item = DomRoot<Node>; // https://dom.spec.whatwg.org/#concept-tree-following - fn next(&mut self) -> Option<Root<Node>> { - let current = match self.current.take() { - None => return None, - Some(current) => current, - }; + fn next(&mut self) -> Option<DomRoot<Node>> { + let current = self.current.take()?; if let Some(first_child) = current.GetFirstChild() { self.current = Some(first_child); - return current.GetFirstChild() + return current.GetFirstChild(); } self.next_skipping_children_impl(current) @@ -1221,19 +1647,16 @@ impl Iterator for FollowingNodeIterator { } pub struct PrecedingNodeIterator { - current: Option<Root<Node>>, - root: Root<Node>, + current: Option<DomRoot<Node>>, + root: DomRoot<Node>, } impl Iterator for PrecedingNodeIterator { - type Item = Root<Node>; + type Item = DomRoot<Node>; // https://dom.spec.whatwg.org/#concept-tree-preceding - fn next(&mut self) -> Option<Root<Node>> { - let current = match self.current.take() { - None => return None, - Some(current) => current, - }; + fn next(&mut self) -> Option<DomRoot<Node>> { + let current = self.current.take()?; self.current = if self.root == current { None @@ -1252,64 +1675,63 @@ impl Iterator for PrecedingNodeIterator { } } -pub struct LastChildIterator { - current: Option<Root<Node>>, +struct SimpleNodeIterator<I> +where + I: Fn(&Node) -> Option<DomRoot<Node>>, +{ + current: Option<DomRoot<Node>>, + next_node: I, } -impl Iterator for LastChildIterator { - type Item = Root<Node>; +impl<I> Iterator for SimpleNodeIterator<I> +where + I: Fn(&Node) -> Option<DomRoot<Node>>, +{ + type Item = DomRoot<Node>; - fn next(&mut self) -> Option<Root<Node>> { - let current = match self.current.take() { - None => return None, - Some(current) => current, - }; - self.current = current.GetLastChild(); - Some(current) + fn next(&mut self) -> Option<Self::Item> { + let current = self.current.take(); + self.current = current.as_ref().and_then(|c| (self.next_node)(c)); + current } } -pub struct AncestorIterator { - current: Option<Root<Node>>, -} - -impl Iterator for AncestorIterator { - type Item = Root<Node>; - - fn next(&mut self) -> Option<Root<Node>> { - let current = match self.current.take() { - None => return None, - Some(current) => current, - }; - self.current = current.GetParentNode(); - Some(current) - } +/// Whether a tree traversal should pass shadow tree boundaries. +#[derive(Clone, Copy, PartialEq)] +pub enum ShadowIncluding { + No, + Yes, } pub struct TreeIterator { - current: Option<Root<Node>>, + current: Option<DomRoot<Node>>, depth: usize, + shadow_including: bool, } impl TreeIterator { - fn new(root: &Node) -> TreeIterator { + fn new(root: &Node, shadow_including: ShadowIncluding) -> TreeIterator { TreeIterator { - current: Some(Root::from_ref(root)), + current: Some(DomRoot::from_ref(root)), depth: 0, + shadow_including: shadow_including == ShadowIncluding::Yes, } } - pub fn next_skipping_children(&mut self) -> Option<Root<Node>> { - let current = match self.current.take() { - None => return None, - Some(current) => current, - }; + pub fn next_skipping_children(&mut self) -> Option<DomRoot<Node>> { + let current = self.current.take()?; self.next_skipping_children_impl(current) } - fn next_skipping_children_impl(&mut self, current: Root<Node>) -> Option<Root<Node>> { - for ancestor in current.inclusive_ancestors() { + fn next_skipping_children_impl(&mut self, current: DomRoot<Node>) -> Option<DomRoot<Node>> { + let iter = current.inclusive_ancestors(if self.shadow_including { + ShadowIncluding::Yes + } else { + ShadowIncluding::No + }); + + for ancestor in iter { if self.depth == 0 { break; } @@ -1319,21 +1741,28 @@ impl TreeIterator { } self.depth -= 1; } - debug_assert!(self.depth == 0); + debug_assert_eq!(self.depth, 0); self.current = None; Some(current) } } impl Iterator for TreeIterator { - type Item = Root<Node>; + type Item = DomRoot<Node>; // https://dom.spec.whatwg.org/#concept-tree-order - fn next(&mut self) -> Option<Root<Node>> { - let current = match self.current.take() { - None => return None, - Some(current) => current, - }; + // https://dom.spec.whatwg.org/#concept-shadow-including-tree-order + fn next(&mut self) -> Option<DomRoot<Node>> { + let current = self.current.take()?; + + if !self.shadow_including { + if let Some(element) = current.downcast::<Element>() { + if element.is_shadow_host() { + return self.next_skipping_children_impl(current); + } + } + } + if let Some(first_child) = current.GetFirstChild() { self.current = Some(first_child); self.depth += 1; @@ -1345,24 +1774,23 @@ impl Iterator for TreeIterator { } /// Specifies whether children must be recursively cloned or not. -#[derive(Copy, Clone, PartialEq, HeapSizeOf)] +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub enum CloneChildrenFlag { CloneChildren, - DoNotCloneChildren + DoNotCloneChildren, } -fn as_uintptr<T>(t: &T) -> uintptr_t { t as *const T as uintptr_t } +fn as_uintptr<T>(t: &T) -> uintptr_t { + t as *const T as uintptr_t +} impl Node { - pub fn reflect_node<N>( - node: Box<N>, - document: &Document, - wrap_fn: unsafe extern "Rust" fn(*mut JSContext, &GlobalScope, Box<N>) -> Root<N>) - -> Root<N> - where N: DerivedFrom<Node> + DomObject + pub fn reflect_node<N>(node: Box<N>, document: &Document) -> DomRoot<N> + where + N: DerivedFrom<Node> + DomObject + DomObjectWrap, { let window = document.window(); - reflect_dom_object(node, window, wrap_fn) + reflect_dom_object(node, window) } pub fn new_inherited(doc: &Document) -> Node { @@ -1371,7 +1799,10 @@ impl Node { #[allow(unrooted_must_root)] pub fn new_document_node() -> Node { - Node::new_(NodeFlags::new() | IS_IN_DOC, None) + Node::new_( + NodeFlags::new() | NodeFlags::IS_IN_DOC | NodeFlags::IS_CONNECTED, + None, + ) } #[allow(unrooted_must_root)] @@ -1384,47 +1815,66 @@ impl Node { last_child: Default::default(), next_sibling: Default::default(), prev_sibling: Default::default(), - owner_doc: MutNullableJS::new(doc), + owner_doc: MutNullableDom::new(doc), + rare_data: Default::default(), child_list: Default::default(), children_count: Cell::new(0u32), flags: Cell::new(flags), inclusive_descendants_version: Cell::new(0), ranges: WeakRangeVec::new(), - style_and_layout_data: Cell::new(None), - - unique_id: UniqueId::new(), + style_and_layout_data: Default::default(), } } // https://dom.spec.whatwg.org/#concept-node-adopt pub fn adopt(node: &Node, document: &Document) { + document.add_script_and_layout_blocker(); + // Step 1. let old_doc = node.owner_doc(); + old_doc.add_script_and_layout_blocker(); // Step 2. node.remove_self(); + // Step 3. if &*old_doc != document { - // Step 3. - for descendant in node.traverse_preorder() { + // Step 3.1. + for descendant in node.traverse_preorder(ShadowIncluding::Yes) { descendant.set_owner_doc(document); } - // Step 4. - for descendant in node.traverse_preorder() { + for descendant in node + .traverse_preorder(ShadowIncluding::Yes) + .filter_map(|d| d.as_custom_element()) + { + // Step 3.2. + ScriptThread::enqueue_callback_reaction( + &*descendant, + CallbackReaction::Adopted(old_doc.clone(), DomRoot::from_ref(document)), + None, + ); + } + for descendant in node.traverse_preorder(ShadowIncluding::Yes) { + // Step 3.3. vtable_for(&descendant).adopting_steps(&old_doc); } } + + old_doc.remove_script_and_layout_blocker(); + document.remove_script_and_layout_blocker(); } // https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity - pub fn ensure_pre_insertion_validity(node: &Node, - parent: &Node, - child: Option<&Node>) -> ErrorResult { + pub fn ensure_pre_insertion_validity( + node: &Node, + parent: &Node, + child: Option<&Node>, + ) -> ErrorResult { // Step 1. match parent.type_id() { - NodeTypeId::Document(_) | - NodeTypeId::DocumentFragment | - NodeTypeId::Element(..) => (), - _ => return Err(Error::HierarchyRequest) + NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { + () + }, + _ => return Err(Error::HierarchyRequest), } // Step 2. @@ -1441,7 +1891,7 @@ impl Node { // Step 4-5. match node.type_id() { - NodeTypeId::CharacterData(CharacterDataTypeId::Text) => { + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => { if parent.is::<Document>() { return Err(Error::HierarchyRequest); } @@ -1451,22 +1901,21 @@ impl Node { return Err(Error::HierarchyRequest); } }, - NodeTypeId::DocumentFragment | + NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(_) | NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) | NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => (), - NodeTypeId::Document(_) => return Err(Error::HierarchyRequest) + NodeTypeId::Document(_) => return Err(Error::HierarchyRequest), + NodeTypeId::Attr => unreachable!(), } // Step 6. if parent.is::<Document>() { match node.type_id() { // Step 6.1 - NodeTypeId::DocumentFragment => { + NodeTypeId::DocumentFragment(_) => { // Step 6.1.1(b) - if node.children() - .any(|c| c.is::<Text>()) - { + if node.children().any(|c| c.is::<Text>()) { return Err(Error::HierarchyRequest); } match node.child_elements().count() { @@ -1477,9 +1926,11 @@ impl Node { return Err(Error::HierarchyRequest); } if let Some(child) = child { - if child.inclusively_following_siblings() - .any(|child| child.is_doctype()) { - return Err(Error::HierarchyRequest); + if child + .inclusively_following_siblings() + .any(|child| child.is_doctype()) + { + return Err(Error::HierarchyRequest); } } }, @@ -1493,24 +1944,25 @@ impl Node { return Err(Error::HierarchyRequest); } if let Some(ref child) = child { - if child.inclusively_following_siblings() - .any(|child| child.is_doctype()) { - return Err(Error::HierarchyRequest); + if child + .inclusively_following_siblings() + .any(|child| child.is_doctype()) + { + return Err(Error::HierarchyRequest); } } }, // Step 6.3 NodeTypeId::DocumentType => { - if parent.children() - .any(|c| c.is_doctype()) - { + if parent.children().any(|c| c.is_doctype()) { return Err(Error::HierarchyRequest); } match child { Some(child) => { - if parent.children() - .take_while(|c| &**c != child) - .any(|c| c.is::<Element>()) + if parent + .children() + .take_while(|c| &**c != child) + .any(|c| c.is::<Element>()) { return Err(Error::HierarchyRequest); } @@ -1524,25 +1976,25 @@ impl Node { }, NodeTypeId::CharacterData(_) => (), NodeTypeId::Document(_) => unreachable!(), + NodeTypeId::Attr => unreachable!(), } } Ok(()) } // https://dom.spec.whatwg.org/#concept-node-pre-insert - pub fn pre_insert(node: &Node, parent: &Node, child: Option<&Node>) - -> Fallible<Root<Node>> { + pub fn pre_insert(node: &Node, parent: &Node, child: Option<&Node>) -> Fallible<DomRoot<Node>> { // Step 1. - try!(Node::ensure_pre_insertion_validity(node, parent, child)); + Node::ensure_pre_insertion_validity(node, parent, child)?; // Steps 2-3. let reference_child_root; let reference_child = match child { Some(child) if child == node => { reference_child_root = node.GetNextSibling(); - reference_child_root.r() + reference_child_root.as_deref() }, - _ => child + _ => child, }; // Step 4. @@ -1550,19 +2002,28 @@ impl Node { Node::adopt(node, &document); // Step 5. - Node::insert(node, parent, reference_child, SuppressObserver::Unsuppressed); + Node::insert( + node, + parent, + reference_child, + SuppressObserver::Unsuppressed, + ); // Step 6. - Ok(Root::from_ref(node)) + Ok(DomRoot::from_ref(node)) } // https://dom.spec.whatwg.org/#concept-node-insert - fn insert(node: &Node, - parent: &Node, - child: Option<&Node>, - suppress_observers: SuppressObserver) { + fn insert( + node: &Node, + parent: &Node, + child: Option<&Node>, + suppress_observers: SuppressObserver, + ) { + node.owner_doc().add_script_and_layout_blocker(); debug_assert!(&*node.owner_doc() == &*parent.owner_doc()); - debug_assert!(child.map_or(true, |child| Some(parent) == child.GetParentNode().r())); + debug_assert!(child.map_or(true, |child| Some(parent) == + child.GetParentNode().as_deref())); // Step 1. let count = if node.is::<DocumentFragment>() { @@ -1579,27 +2040,34 @@ impl Node { } } rooted_vec!(let mut new_nodes); - let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() { + let new_nodes = if let NodeTypeId::DocumentFragment(_) = node.type_id() { // Step 3. - new_nodes.extend(node.children().map(|kid| JS::from_ref(&*kid))); - // Step 4: mutation observers. - // Step 5. - for kid in new_nodes.r() { - Node::remove(*kid, node, SuppressObserver::Suppressed); + new_nodes.extend(node.children().map(|kid| Dom::from_ref(&*kid))); + // Step 4. + for kid in &*new_nodes { + Node::remove(kid, node, SuppressObserver::Suppressed); } + // Step 5. vtable_for(&node).children_changed(&ChildrenMutation::replace_all(new_nodes.r(), &[])); + + let mutation = Mutation::ChildList { + added: None, + removed: Some(new_nodes.r()), + prev: None, + next: None, + }; + MutationObserver::queue_a_mutation_record(&node, mutation); + new_nodes.r() } else { // Step 3. - ref_slice(&node) + from_ref(&node) }; - // Step 6: mutation observers. + // Step 6. let previous_sibling = match suppress_observers { - SuppressObserver::Unsuppressed => { - match child { - Some(child) => child.GetPreviousSibling(), - None => parent.GetLastChild(), - } + SuppressObserver::Unsuppressed => match child { + Some(child) => child.GetPreviousSibling(), + None => parent.GetLastChild(), }, SuppressObserver::Suppressed => None, }; @@ -1607,16 +2075,48 @@ impl Node { for kid in new_nodes { // Step 7.1. parent.add_child(*kid, child); - // Step 7.2: insertion steps. + // Step 7.7. + for descendant in kid + .traverse_preorder(ShadowIncluding::Yes) + .filter_map(DomRoot::downcast::<Element>) + { + // Step 7.7.2. + if descendant.is_connected() { + if descendant.get_custom_element_definition().is_some() { + // Step 7.7.2.1. + ScriptThread::enqueue_callback_reaction( + &*descendant, + CallbackReaction::Connected, + None, + ); + } else { + // Step 7.7.2.2. + try_upgrade_element(&*descendant); + } + } + } } if let SuppressObserver::Unsuppressed = suppress_observers { - vtable_for(&parent).children_changed( - &ChildrenMutation::insert(previous_sibling.r(), new_nodes, child)); + vtable_for(&parent).children_changed(&ChildrenMutation::insert( + previous_sibling.as_deref(), + new_nodes, + child, + )); + + let mutation = Mutation::ChildList { + added: Some(new_nodes), + removed: None, + prev: previous_sibling.as_deref(), + next: child, + }; + MutationObserver::queue_a_mutation_record(&parent, mutation); } + node.owner_doc().remove_script_and_layout_blocker(); } // https://dom.spec.whatwg.org/#concept-node-replace-all pub fn replace_all(node: Option<&Node>, parent: &Node) { + parent.owner_doc().add_script_and_layout_blocker(); // Step 1. if let Some(node) = node { Node::adopt(node, &*parent.owner_doc()); @@ -1626,47 +2126,73 @@ impl Node { // Step 3. rooted_vec!(let mut added_nodes); let added_nodes = if let Some(node) = node.as_ref() { - if let NodeTypeId::DocumentFragment = node.type_id() { - added_nodes.extend(node.children().map(|child| JS::from_ref(&*child))); + if let NodeTypeId::DocumentFragment(_) = node.type_id() { + added_nodes.extend(node.children().map(|child| Dom::from_ref(&*child))); added_nodes.r() } else { - ref_slice(node) + from_ref(node) } } else { &[] as &[&Node] }; // Step 4. - for child in removed_nodes.r() { - Node::remove(*child, parent, SuppressObserver::Suppressed); + for child in &*removed_nodes { + Node::remove(child, parent, SuppressObserver::Suppressed); } // Step 5. if let Some(node) = node { Node::insert(node, parent, None, SuppressObserver::Suppressed); } - // Step 6: mutation observers. - vtable_for(&parent).children_changed( - &ChildrenMutation::replace_all(removed_nodes.r(), added_nodes)); + // Step 6. + vtable_for(&parent).children_changed(&ChildrenMutation::replace_all( + removed_nodes.r(), + added_nodes, + )); + + if !removed_nodes.is_empty() || !added_nodes.is_empty() { + let mutation = Mutation::ChildList { + added: Some(added_nodes), + removed: Some(removed_nodes.r()), + prev: None, + next: None, + }; + MutationObserver::queue_a_mutation_record(&parent, mutation); + } + parent.owner_doc().remove_script_and_layout_blocker(); + } + + // https://dom.spec.whatwg.org/multipage/#string-replace-all + pub fn string_replace_all(string: DOMString, parent: &Node) { + if string.len() == 0 { + Node::replace_all(None, parent); + } else { + let text = Text::new(string, &document_from_node(parent)); + Node::replace_all(Some(text.upcast::<Node>()), parent); + }; } // https://dom.spec.whatwg.org/#concept-node-pre-remove - fn pre_remove(child: &Node, parent: &Node) -> Fallible<Root<Node>> { + fn pre_remove(child: &Node, parent: &Node) -> Fallible<DomRoot<Node>> { // Step 1. match child.GetParentNode() { Some(ref node) if &**node != parent => return Err(Error::NotFound), None => return Err(Error::NotFound), - _ => () + _ => (), } // Step 2. Node::remove(child, parent, SuppressObserver::Unsuppressed); // Step 3. - Ok(Root::from_ref(child)) + Ok(DomRoot::from_ref(child)) } // https://dom.spec.whatwg.org/#concept-node-remove fn remove(node: &Node, parent: &Node, suppress_observers: SuppressObserver) { - assert!(node.GetParentNode().map_or(false, |node_parent| &*node_parent == parent)); + parent.owner_doc().add_script_and_layout_blocker(); + assert!(node + .GetParentNode() + .map_or(false, |node_parent| &*node_parent == parent)); let cached_index = { if parent.ranges.is_empty() { None @@ -1692,36 +2218,66 @@ impl Node { // Step 11. transient registered observers // Step 12. if let SuppressObserver::Unsuppressed = suppress_observers { - vtable_for(&parent).children_changed( - &ChildrenMutation::replace(old_previous_sibling.r(), - &Some(&node), &[], - old_next_sibling.r())); + vtable_for(&parent).children_changed(&ChildrenMutation::replace( + old_previous_sibling.as_deref(), + &Some(&node), + &[], + old_next_sibling.as_deref(), + )); + + let removed = [node]; + let mutation = Mutation::ChildList { + added: None, + removed: Some(&removed), + prev: old_previous_sibling.as_deref(), + next: old_next_sibling.as_deref(), + }; + MutationObserver::queue_a_mutation_record(&parent, mutation); } + parent.owner_doc().remove_script_and_layout_blocker(); } // https://dom.spec.whatwg.org/#concept-node-clone - pub fn clone(node: &Node, maybe_doc: Option<&Document>, - clone_children: CloneChildrenFlag) -> Root<Node> { + pub fn clone( + node: &Node, + maybe_doc: Option<&Document>, + clone_children: CloneChildrenFlag, + ) -> DomRoot<Node> { // Step 1. let document = match maybe_doc { - Some(doc) => Root::from_ref(doc), - None => node.owner_doc() + Some(doc) => DomRoot::from_ref(doc), + None => node.owner_doc(), }; // Step 2. // XXXabinader: clone() for each node as trait? - let copy: Root<Node> = match node.type_id() { + let copy: DomRoot<Node> = match node.type_id() { NodeTypeId::DocumentType => { let doctype = node.downcast::<DocumentType>().unwrap(); - let doctype = DocumentType::new(doctype.name().clone(), - Some(doctype.public_id().clone()), - Some(doctype.system_id().clone()), - &document); - Root::upcast::<Node>(doctype) + let doctype = DocumentType::new( + doctype.name().clone(), + Some(doctype.public_id().clone()), + Some(doctype.system_id().clone()), + &document, + ); + DomRoot::upcast::<Node>(doctype) + }, + NodeTypeId::Attr => { + let attr = node.downcast::<Attr>().unwrap(); + let attr = Attr::new( + &document, + attr.local_name().clone(), + attr.value().clone(), + attr.name().clone(), + attr.namespace().clone(), + attr.prefix().cloned(), + None, + ); + DomRoot::upcast::<Node>(attr) }, - NodeTypeId::DocumentFragment => { + NodeTypeId::DocumentFragment(_) => { let doc_fragment = DocumentFragment::new(&document); - Root::upcast::<Node>(doc_fragment) + DomRoot::upcast::<Node>(doc_fragment) }, NodeTypeId::CharacterData(_) => { let cdata = node.downcast::<CharacterData>().unwrap(); @@ -1736,33 +2292,46 @@ impl Node { }; let window = document.window(); let loader = DocumentLoader::new(&*document.loader()); - let document = Document::new(window, HasBrowsingContext::No, - Some(document.url()), - // https://github.com/whatwg/dom/issues/378 - document.origin().clone(), - is_html_doc, None, - None, DocumentActivity::Inactive, - DocumentSource::NotFromParser, loader, - None, None); - Root::upcast::<Node>(document) + let document = Document::new( + window, + HasBrowsingContext::No, + Some(document.url()), + // https://github.com/whatwg/dom/issues/378 + document.origin().clone(), + is_html_doc, + None, + None, + DocumentActivity::Inactive, + DocumentSource::NotFromParser, + loader, + None, + None, + Default::default(), + ); + DomRoot::upcast::<Node>(document) }, NodeTypeId::Element(..) => { let element = node.downcast::<Element>().unwrap(); let name = QualName { + prefix: element.prefix().as_ref().map(|p| Prefix::from(&**p)), ns: element.namespace().clone(), - local: element.local_name().clone() + local: element.local_name().clone(), }; - let element = Element::create(name, - element.prefix().map(|p| Prefix::from(&**p)), - &document, ElementCreator::ScriptCreated); - Root::upcast::<Node>(element) + let element = Element::create( + name, + element.get_is(), + &document, + ElementCreator::ScriptCreated, + CustomElementCreationMode::Asynchronous, + ); + DomRoot::upcast::<Node>(element) }, }; // Step 3. let document = match copy.downcast::<Document>() { - Some(doc) => Root::from_ref(doc), - None => Root::from_ref(&*document), + Some(doc) => DomRoot::from_ref(doc), + None => DomRoot::from_ref(&*document), }; assert!(copy.owner_doc() == document); @@ -1779,14 +2348,16 @@ impl Node { let copy_elem = copy.downcast::<Element>().unwrap(); for attr in node_elem.attrs().iter() { - copy_elem.push_new_attribute(attr.local_name().clone(), - attr.value().clone(), - attr.name().clone(), - attr.namespace().clone(), - attr.prefix().cloned()); + copy_elem.push_new_attribute( + attr.local_name().clone(), + attr.value().clone(), + attr.name().clone(), + attr.namespace().clone(), + attr.prefix().cloned(), + ); } }, - _ => () + _ => (), } // Step 5: cloning steps. @@ -1795,8 +2366,7 @@ impl Node { // Step 6. if clone_children == CloneChildrenFlag::CloneChildren { for child in node.children() { - let child_copy = Node::clone(&child, Some(&document), - clone_children); + let child_copy = Node::clone(&child, Some(&document), clone_children); let _inserted_node = Node::pre_insert(&child_copy, ©, None); } } @@ -1805,12 +2375,17 @@ impl Node { copy } - /// https://html.spec.whatwg.org/multipage/#child-text-content + /// <https://html.spec.whatwg.org/multipage/#child-text-content> pub fn child_text_content(&self) -> DOMString { Node::collect_text_contents(self.children()) } - pub fn collect_text_contents<T: Iterator<Item=Root<Node>>>(iterator: T) -> DOMString { + /// <https://html.spec.whatwg.org/multipage/#descendant-text-content> + pub fn descendant_text_content(&self) -> DOMString { + Node::collect_text_contents(self.traverse_preorder(ShadowIncluding::No)) + } + + pub fn collect_text_contents<T: Iterator<Item = DomRoot<Node>>>(iterator: T) -> DOMString { let mut content = String::new(); for node in iterator { if let Some(ref text) = node.downcast::<Text>() { @@ -1824,26 +2399,31 @@ impl Node { match namespace { ns!() => None, // FIXME(ajeffrey): convert directly from Namespace to DOMString - _ => Some(DOMString::from(&*namespace)) + _ => Some(DOMString::from(&*namespace)), } } // https://dom.spec.whatwg.org/#locate-a-namespace pub fn locate_namespace(node: &Node, prefix: Option<DOMString>) -> Namespace { match node.type_id() { - NodeTypeId::Element(_) => { - node.downcast::<Element>().unwrap().locate_namespace(prefix) - }, - NodeTypeId::Document(_) => { - node.downcast::<Document>().unwrap() - .GetDocumentElement().as_ref() - .map_or(ns!(), |elem| elem.locate_namespace(prefix)) - }, - NodeTypeId::DocumentType | NodeTypeId::DocumentFragment => ns!(), - _ => { - node.GetParentElement().as_ref() - .map_or(ns!(), |elem| elem.locate_namespace(prefix)) - } + NodeTypeId::Element(_) => node.downcast::<Element>().unwrap().locate_namespace(prefix), + NodeTypeId::Attr => node + .downcast::<Attr>() + .unwrap() + .GetOwnerElement() + .as_ref() + .map_or(ns!(), |elem| elem.locate_namespace(prefix)), + NodeTypeId::Document(_) => node + .downcast::<Document>() + .unwrap() + .GetDocumentElement() + .as_ref() + .map_or(ns!(), |elem| elem.locate_namespace(prefix)), + NodeTypeId::DocumentType | NodeTypeId::DocumentFragment(_) => ns!(), + _ => node + .GetParentElement() + .as_ref() + .map_or(ns!(), |elem| elem.locate_namespace(prefix)), } } } @@ -1852,39 +2432,42 @@ impl NodeMethods for Node { // https://dom.spec.whatwg.org/#dom-node-nodetype fn NodeType(&self) -> u16 { match self.type_id() { - NodeTypeId::CharacterData(CharacterDataTypeId::Text) => - NodeConstants::TEXT_NODE, - NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => - NodeConstants::PROCESSING_INSTRUCTION_NODE, - NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => - NodeConstants::COMMENT_NODE, - NodeTypeId::Document(_) => - NodeConstants::DOCUMENT_NODE, - NodeTypeId::DocumentType => - NodeConstants::DOCUMENT_TYPE_NODE, - NodeTypeId::DocumentFragment => - NodeConstants::DOCUMENT_FRAGMENT_NODE, - NodeTypeId::Element(_) => - NodeConstants::ELEMENT_NODE, + NodeTypeId::Attr => NodeConstants::ATTRIBUTE_NODE, + NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => { + NodeConstants::TEXT_NODE + }, + NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::CDATASection)) => { + NodeConstants::CDATA_SECTION_NODE + }, + NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => { + NodeConstants::PROCESSING_INSTRUCTION_NODE + }, + NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => NodeConstants::COMMENT_NODE, + NodeTypeId::Document(_) => NodeConstants::DOCUMENT_NODE, + NodeTypeId::DocumentType => NodeConstants::DOCUMENT_TYPE_NODE, + NodeTypeId::DocumentFragment(_) => NodeConstants::DOCUMENT_FRAGMENT_NODE, + NodeTypeId::Element(_) => NodeConstants::ELEMENT_NODE, } } // https://dom.spec.whatwg.org/#dom-node-nodename fn NodeName(&self) -> DOMString { match self.type_id() { - NodeTypeId::Element(..) => { - self.downcast::<Element>().unwrap().TagName() - } - NodeTypeId::CharacterData(CharacterDataTypeId::Text) => DOMString::from("#text"), + NodeTypeId::Attr => self.downcast::<Attr>().unwrap().qualified_name(), + NodeTypeId::Element(..) => self.downcast::<Element>().unwrap().TagName(), + NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => { + DOMString::from("#text") + }, + NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::CDATASection)) => { + DOMString::from("#cdata-section") + }, NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => { self.downcast::<ProcessingInstruction>().unwrap().Target() - } - NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => DOMString::from("#comment"), - NodeTypeId::DocumentType => { - self.downcast::<DocumentType>().unwrap().name().clone() }, - NodeTypeId::DocumentFragment => DOMString::from("#document-fragment"), - NodeTypeId::Document(_) => DOMString::from("#document") + NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => DOMString::from("#comment"), + NodeTypeId::DocumentType => self.downcast::<DocumentType>().unwrap().name().clone(), + NodeTypeId::DocumentFragment(_) => DOMString::from("#document-fragment"), + NodeTypeId::Document(_) => DOMString::from("#document"), } } @@ -1893,30 +2476,47 @@ impl NodeMethods for Node { USVString(String::from(self.owner_doc().base_url().as_str())) } + // https://dom.spec.whatwg.org/#dom-node-isconnected + fn IsConnected(&self) -> bool { + return self.is_connected(); + } + // https://dom.spec.whatwg.org/#dom-node-ownerdocument - fn GetOwnerDocument(&self) -> Option<Root<Document>> { + fn GetOwnerDocument(&self) -> Option<DomRoot<Document>> { match self.type_id() { - NodeTypeId::CharacterData(..) | - NodeTypeId::Element(..) | - NodeTypeId::DocumentType | - NodeTypeId::DocumentFragment => Some(self.owner_doc()), - NodeTypeId::Document(_) => None + NodeTypeId::Document(_) => None, + _ => Some(self.owner_doc()), } } // https://dom.spec.whatwg.org/#dom-node-getrootnode - fn GetRootNode(&self) -> Root<Node> { - self.inclusive_ancestors().last().unwrap() + fn GetRootNode(&self, options: &GetRootNodeOptions) -> DomRoot<Node> { + if let Some(shadow_root) = self.containing_shadow_root() { + return if options.composed { + // shadow-including root. + shadow_root.Host().upcast::<Node>().GetRootNode(options) + } else { + DomRoot::from_ref(shadow_root.upcast::<Node>()) + }; + } + + if self.is_in_doc() { + DomRoot::from_ref(self.owner_doc().upcast::<Node>()) + } else { + self.inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap() + } } // https://dom.spec.whatwg.org/#dom-node-parentnode - fn GetParentNode(&self) -> Option<Root<Node>> { + fn GetParentNode(&self) -> Option<DomRoot<Node>> { self.parent_node.get() } // https://dom.spec.whatwg.org/#dom-node-parentelement - fn GetParentElement(&self) -> Option<Root<Element>> { - self.GetParentNode().and_then(Root::downcast) + fn GetParentElement(&self) -> Option<DomRoot<Element>> { + self.GetParentNode().and_then(DomRoot::downcast) } // https://dom.spec.whatwg.org/#dom-node-haschildnodes @@ -1925,7 +2525,7 @@ impl NodeMethods for Node { } // https://dom.spec.whatwg.org/#dom-node-childnodes - fn ChildNodes(&self) -> Root<NodeList> { + fn ChildNodes(&self) -> DomRoot<NodeList> { self.child_list.or_init(|| { let doc = self.owner_doc(); let window = doc.window(); @@ -1934,53 +2534,65 @@ impl NodeMethods for Node { } // https://dom.spec.whatwg.org/#dom-node-firstchild - fn GetFirstChild(&self) -> Option<Root<Node>> { + fn GetFirstChild(&self) -> Option<DomRoot<Node>> { self.first_child.get() } // https://dom.spec.whatwg.org/#dom-node-lastchild - fn GetLastChild(&self) -> Option<Root<Node>> { + fn GetLastChild(&self) -> Option<DomRoot<Node>> { self.last_child.get() } // https://dom.spec.whatwg.org/#dom-node-previoussibling - fn GetPreviousSibling(&self) -> Option<Root<Node>> { + fn GetPreviousSibling(&self) -> Option<DomRoot<Node>> { self.prev_sibling.get() } // https://dom.spec.whatwg.org/#dom-node-nextsibling - fn GetNextSibling(&self) -> Option<Root<Node>> { + fn GetNextSibling(&self) -> Option<DomRoot<Node>> { self.next_sibling.get() } // https://dom.spec.whatwg.org/#dom-node-nodevalue fn GetNodeValue(&self) -> Option<DOMString> { - self.downcast::<CharacterData>().map(CharacterData::Data) + match self.type_id() { + NodeTypeId::Attr => Some(self.downcast::<Attr>().unwrap().Value()), + NodeTypeId::CharacterData(_) => { + self.downcast::<CharacterData>().map(CharacterData::Data) + }, + _ => None, + } } // https://dom.spec.whatwg.org/#dom-node-nodevalue fn SetNodeValue(&self, val: Option<DOMString>) { - if let Some(character_data) = self.downcast::<CharacterData>() { - character_data.SetData(val.unwrap_or_default()); + match self.type_id() { + NodeTypeId::Attr => { + let attr = self.downcast::<Attr>().unwrap(); + attr.SetValue(val.unwrap_or_default()); + }, + NodeTypeId::CharacterData(_) => { + let character_data = self.downcast::<CharacterData>().unwrap(); + character_data.SetData(val.unwrap_or_default()); + }, + _ => {}, } } // https://dom.spec.whatwg.org/#dom-node-textcontent fn GetTextContent(&self) -> Option<DOMString> { match self.type_id() { - NodeTypeId::DocumentFragment | - NodeTypeId::Element(..) => { - let content = Node::collect_text_contents(self.traverse_preorder()); + NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { + let content = + Node::collect_text_contents(self.traverse_preorder(ShadowIncluding::No)); Some(content) - } + }, + NodeTypeId::Attr => Some(self.downcast::<Attr>().unwrap().Value()), NodeTypeId::CharacterData(..) => { let characterdata = self.downcast::<CharacterData>().unwrap(); Some(characterdata.Data()) - } - NodeTypeId::DocumentType | - NodeTypeId::Document(_) => { - None - } + }, + NodeTypeId::DocumentType | NodeTypeId::Document(_) => None, } } @@ -1988,45 +2600,47 @@ impl NodeMethods for Node { fn SetTextContent(&self, value: Option<DOMString>) { let value = value.unwrap_or_default(); match self.type_id() { - NodeTypeId::DocumentFragment | - NodeTypeId::Element(..) => { + NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { // Step 1-2. let node = if value.is_empty() { None } else { - Some(Root::upcast(self.owner_doc().CreateTextNode(value))) + Some(DomRoot::upcast(self.owner_doc().CreateTextNode(value))) }; // Step 3. - Node::replace_all(node.r(), self); - } + Node::replace_all(node.as_deref(), self); + }, + NodeTypeId::Attr => { + let attr = self.downcast::<Attr>().unwrap(); + attr.SetValue(value); + }, NodeTypeId::CharacterData(..) => { let characterdata = self.downcast::<CharacterData>().unwrap(); characterdata.SetData(value); - } - NodeTypeId::DocumentType | - NodeTypeId::Document(_) => {} + }, + NodeTypeId::DocumentType | NodeTypeId::Document(_) => {}, } } // https://dom.spec.whatwg.org/#dom-node-insertbefore - fn InsertBefore(&self, node: &Node, child: Option<&Node>) -> Fallible<Root<Node>> { + fn InsertBefore(&self, node: &Node, child: Option<&Node>) -> Fallible<DomRoot<Node>> { Node::pre_insert(node, self, child) } // https://dom.spec.whatwg.org/#dom-node-appendchild - fn AppendChild(&self, node: &Node) -> Fallible<Root<Node>> { + fn AppendChild(&self, node: &Node) -> Fallible<DomRoot<Node>> { Node::pre_insert(node, self, None) } // https://dom.spec.whatwg.org/#concept-node-replace - fn ReplaceChild(&self, node: &Node, child: &Node) -> Fallible<Root<Node>> { + fn ReplaceChild(&self, node: &Node, child: &Node) -> Fallible<DomRoot<Node>> { // Step 1. match self.type_id() { - NodeTypeId::Document(_) | - NodeTypeId::DocumentFragment | - NodeTypeId::Element(..) => (), - _ => return Err(Error::HierarchyRequest) + NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { + () + }, + _ => return Err(Error::HierarchyRequest), } // Step 2. @@ -2041,22 +2655,23 @@ impl NodeMethods for Node { // Step 4-5. match node.type_id() { - NodeTypeId::CharacterData(CharacterDataTypeId::Text) if self.is::<Document>() => - return Err(Error::HierarchyRequest), - NodeTypeId::DocumentType if !self.is::<Document>() => return Err(Error::HierarchyRequest), + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) if self.is::<Document>() => { + return Err(Error::HierarchyRequest); + }, + NodeTypeId::DocumentType if !self.is::<Document>() => { + return Err(Error::HierarchyRequest); + }, NodeTypeId::Document(_) => return Err(Error::HierarchyRequest), - _ => () + _ => (), } // Step 6. if self.is::<Document>() { match node.type_id() { // Step 6.1 - NodeTypeId::DocumentFragment => { + NodeTypeId::DocumentFragment(_) => { // Step 6.1.1(b) - if node.children() - .any(|c| c.is::<Text>()) - { + if node.children().any(|c| c.is::<Text>()) { return Err(Error::HierarchyRequest); } match node.child_elements().count() { @@ -2066,54 +2681,49 @@ impl NodeMethods for Node { if self.child_elements().any(|c| c.upcast::<Node>() != child) { return Err(Error::HierarchyRequest); } - if child.following_siblings() - .any(|child| child.is_doctype()) { + if child.following_siblings().any(|child| child.is_doctype()) { return Err(Error::HierarchyRequest); } }, // Step 6.1.1(a) - _ => return Err(Error::HierarchyRequest) + _ => return Err(Error::HierarchyRequest), } }, // Step 6.2 NodeTypeId::Element(..) => { - if self.child_elements() - .any(|c| c.upcast::<Node>() != child) { + if self.child_elements().any(|c| c.upcast::<Node>() != child) { return Err(Error::HierarchyRequest); } - if child.following_siblings() - .any(|child| child.is_doctype()) - { + if child.following_siblings().any(|child| child.is_doctype()) { return Err(Error::HierarchyRequest); } }, // Step 6.3 NodeTypeId::DocumentType => { - if self.children() - .any(|c| c.is_doctype() && - &*c != child) - { + if self.children().any(|c| c.is_doctype() && &*c != child) { return Err(Error::HierarchyRequest); } - if self.children() - .take_while(|c| &**c != child) - .any(|c| c.is::<Element>()) + if self + .children() + .take_while(|c| &**c != child) + .any(|c| c.is::<Element>()) { return Err(Error::HierarchyRequest); } }, NodeTypeId::CharacterData(..) => (), NodeTypeId::Document(_) => unreachable!(), + NodeTypeId::Attr => unreachable!(), } } // Step 7-8. let child_next_sibling = child.GetNextSibling(); let node_next_sibling = node.GetNextSibling(); - let reference_child = if child_next_sibling.r() == Some(node) { - node_next_sibling.r() + let reference_child = if child_next_sibling.as_deref() == Some(node) { + node_next_sibling.as_deref() } else { - child_next_sibling.r() + child_next_sibling.as_deref() }; // Step 9. @@ -2133,29 +2743,42 @@ impl NodeMethods for Node { // Step 12. rooted_vec!(let mut nodes); - let nodes = if node.type_id() == NodeTypeId::DocumentFragment { - nodes.extend(node.children().map(|node| JS::from_ref(&*node))); + let nodes = if node.type_id() == + NodeTypeId::DocumentFragment(DocumentFragmentTypeId::DocumentFragment) || + node.type_id() == NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) + { + nodes.extend(node.children().map(|node| Dom::from_ref(&*node))); nodes.r() } else { - ref_slice(&node) + from_ref(&node) }; // Step 13. Node::insert(node, self, reference_child, SuppressObserver::Suppressed); // Step 14. - vtable_for(&self).children_changed( - &ChildrenMutation::replace(previous_sibling.r(), - &removed_child, nodes, - reference_child)); + vtable_for(&self).children_changed(&ChildrenMutation::replace( + previous_sibling.as_deref(), + &removed_child, + nodes, + reference_child, + )); + let removed = removed_child.map(|r| [r]); + let mutation = Mutation::ChildList { + added: Some(nodes), + removed: removed.as_ref().map(|r| &r[..]), + prev: previous_sibling.as_deref(), + next: reference_child, + }; + + MutationObserver::queue_a_mutation_record(&self, mutation); // Step 15. - Ok(Root::from_ref(child)) + Ok(DomRoot::from_ref(child)) } // https://dom.spec.whatwg.org/#dom-node-removechild - fn RemoveChild(&self, node: &Node) - -> Fallible<Root<Node>> { + fn RemoveChild(&self, node: &Node) -> Fallible<DomRoot<Node>> { Node::pre_remove(node, self) } @@ -2170,10 +2793,16 @@ impl NodeMethods for Node { Node::remove(&node, self, SuppressObserver::Unsuppressed); continue; } - while children.peek().map_or(false, |&(_, ref sibling)| sibling.is::<Text>()) { + while children + .peek() + .map_or(false, |&(_, ref sibling)| sibling.is::<Text>()) + { let (index, sibling) = children.next().unwrap(); - sibling.ranges.drain_to_preceding_text_sibling(&sibling, &node, length); - self.ranges.move_to_text_child_at(self, index as u32, &node, length as u32); + sibling + .ranges + .drain_to_preceding_text_sibling(&sibling, &node, length); + self.ranges + .move_to_text_child_at(self, index as u32, &node, length as u32); let sibling_cdata = sibling.downcast::<CharacterData>().unwrap(); length += sibling_cdata.Length(); cdata.append_data(&sibling_cdata.data()); @@ -2186,12 +2815,19 @@ impl NodeMethods for Node { } // https://dom.spec.whatwg.org/#dom-node-clonenode - fn CloneNode(&self, deep: bool) -> Root<Node> { - Node::clone(self, None, if deep { - CloneChildrenFlag::CloneChildren - } else { - CloneChildrenFlag::DoNotCloneChildren - }) + fn CloneNode(&self, deep: bool) -> Fallible<DomRoot<Node>> { + if deep && self.is::<ShadowRoot>() { + return Err(Error::NotSupported); + } + Ok(Node::clone( + self, + None, + if deep { + CloneChildrenFlag::CloneChildren + } else { + CloneChildrenFlag::DoNotCloneChildren + }, + )) } // https://dom.spec.whatwg.org/#dom-node-isequalnode @@ -2200,28 +2836,36 @@ impl NodeMethods for Node { let doctype = node.downcast::<DocumentType>().unwrap(); let other_doctype = other.downcast::<DocumentType>().unwrap(); (*doctype.name() == *other_doctype.name()) && - (*doctype.public_id() == *other_doctype.public_id()) && - (*doctype.system_id() == *other_doctype.system_id()) + (*doctype.public_id() == *other_doctype.public_id()) && + (*doctype.system_id() == *other_doctype.system_id()) } fn is_equal_element(node: &Node, other: &Node) -> bool { let element = node.downcast::<Element>().unwrap(); let other_element = other.downcast::<Element>().unwrap(); (*element.namespace() == *other_element.namespace()) && - (element.prefix() == other_element.prefix()) && - (*element.local_name() == *other_element.local_name()) && - (element.attrs().len() == other_element.attrs().len()) + (*element.prefix() == *other_element.prefix()) && + (*element.local_name() == *other_element.local_name()) && + (element.attrs().len() == other_element.attrs().len()) } fn is_equal_processinginstruction(node: &Node, other: &Node) -> bool { let pi = node.downcast::<ProcessingInstruction>().unwrap(); let other_pi = other.downcast::<ProcessingInstruction>().unwrap(); (*pi.target() == *other_pi.target()) && - (*pi.upcast::<CharacterData>().data() == *other_pi.upcast::<CharacterData>().data()) + (*pi.upcast::<CharacterData>().data() == + *other_pi.upcast::<CharacterData>().data()) } fn is_equal_characterdata(node: &Node, other: &Node) -> bool { let characterdata = node.downcast::<CharacterData>().unwrap(); let other_characterdata = other.downcast::<CharacterData>().unwrap(); *characterdata.data() == *other_characterdata.data() } + fn is_equal_attr(node: &Node, other: &Node) -> bool { + let attr = node.downcast::<Attr>().unwrap(); + let other_attr = other.downcast::<Attr>().unwrap(); + (*attr.namespace() == *other_attr.namespace()) && + (attr.local_name() == other_attr.local_name()) && + (**attr.value() == **other_attr.value()) + } fn is_equal_element_attrs(node: &Node, other: &Node) -> bool { let element = node.downcast::<Element>().unwrap(); let other_element = other.downcast::<Element>().unwrap(); @@ -2229,11 +2873,12 @@ impl NodeMethods for Node { element.attrs().iter().all(|attr| { other_element.attrs().iter().any(|other_attr| { (*attr.namespace() == *other_attr.namespace()) && - (attr.local_name() == other_attr.local_name()) && - (**attr.value() == **other_attr.value()) + (attr.local_name() == other_attr.local_name()) && + (**attr.value() == **other_attr.value()) }) }) } + fn is_equal_node(this: &Node, node: &Node) -> bool { // Step 2. if this.NodeType() != node.NodeType() { @@ -2242,19 +2887,24 @@ impl NodeMethods for Node { match node.type_id() { // Step 3. - NodeTypeId::DocumentType - if !is_equal_doctype(this, node) => return false, - NodeTypeId::Element(..) - if !is_equal_element(this, node) => return false, + NodeTypeId::DocumentType if !is_equal_doctype(this, node) => return false, + NodeTypeId::Element(..) if !is_equal_element(this, node) => return false, NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) - if !is_equal_processinginstruction(this, node) => return false, - NodeTypeId::CharacterData(CharacterDataTypeId::Text) | + if !is_equal_processinginstruction(this, node) => + { + return false; + } + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) | NodeTypeId::CharacterData(CharacterDataTypeId::Comment) - if !is_equal_characterdata(this, node) => return false, + if !is_equal_characterdata(this, node) => + { + return false; + } // Step 4. - NodeTypeId::Element(..) - if !is_equal_element_attrs(this, node) => return false, - _ => () + NodeTypeId::Element(..) if !is_equal_element_attrs(this, node) => return false, + NodeTypeId::Attr if !is_equal_attr(this, node) => return false, + + _ => (), } // Step 5. @@ -2263,15 +2913,15 @@ impl NodeMethods for Node { } // Step 6. - this.children().zip(node.children()).all(|(child, other_child)| { - is_equal_node(&child, &other_child) - }) + this.children() + .zip(node.children()) + .all(|(child, other_child)| is_equal_node(&child, &other_child)) } match maybe_node { // Step 1. None => false, // Step 2-6. - Some(node) => is_equal_node(self, node) + Some(node) => is_equal_node(self, node), } } @@ -2285,55 +2935,158 @@ impl NodeMethods for Node { // https://dom.spec.whatwg.org/#dom-node-comparedocumentposition fn CompareDocumentPosition(&self, other: &Node) -> u16 { + // step 1. if self == other { - // step 2. - 0 - } else { - let mut lastself = Root::from_ref(self); - let mut lastother = Root::from_ref(other); - for ancestor in self.ancestors() { - if &*ancestor == other { - // step 4. - return NodeConstants::DOCUMENT_POSITION_CONTAINS + - NodeConstants::DOCUMENT_POSITION_PRECEDING; - } - lastself = ancestor; + return 0; + } + + // step 2 + let mut node1 = Some(other); + let mut node2 = Some(self); + + // step 3 + let mut attr1: Option<&Attr> = None; + let mut attr2: Option<&Attr> = None; + + // step 4: spec says to operate on node1 here, + // node1 is definitely Some(other) going into this step + // The compiler doesn't know the lifetime of attr1.GetOwnerElement + // is guaranteed by the lifetime of attr1, so we hold it explicitly + let attr1owner; + if let Some(ref a) = other.downcast::<Attr>() { + attr1 = Some(a); + attr1owner = a.GetOwnerElement(); + node1 = match attr1owner { + Some(ref e) => Some(&e.upcast()), + None => None, } - for ancestor in other.ancestors() { - if &*ancestor == self { - // step 5. - return NodeConstants::DOCUMENT_POSITION_CONTAINED_BY + - NodeConstants::DOCUMENT_POSITION_FOLLOWING; - } - lastother = ancestor; + } + + // step 5.1: spec says to operate on node2 here, + // node2 is definitely just Some(self) going into this step + let attr2owner; + if let Some(ref a) = self.downcast::<Attr>() { + attr2 = Some(a); + attr2owner = a.GetOwnerElement(); + node2 = match attr2owner { + Some(ref e) => Some(&*e.upcast()), + None => None, } + } - if lastself != lastother { - let abstract_uint: uintptr_t = as_uintptr(&self); - let other_uint: uintptr_t = as_uintptr(&*other); + // Step 5.2 + // This substep seems lacking in test coverage. + // We hit this when comparing two attributes that have the + // same owner element. + if let Some(node2) = node2 { + if Some(node2) == node1 { + match (attr1, attr2) { + (Some(a1), Some(a2)) => { + let attrs = node2.downcast::<Element>().unwrap().attrs(); + // go through the attrs in order to see if self + // or other is first; spec is clear that we + // want value-equality, not reference-equality + for attr in attrs.iter() { + if (*attr.namespace() == *a1.namespace()) && + (attr.local_name() == a1.local_name()) && + (**attr.value() == **a1.value()) + { + return NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + + NodeConstants::DOCUMENT_POSITION_PRECEDING; + } + if (*attr.namespace() == *a2.namespace()) && + (attr.local_name() == a2.local_name()) && + (**attr.value() == **a2.value()) + { + return NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + + NodeConstants::DOCUMENT_POSITION_FOLLOWING; + } + } + // both attrs have node2 as their owner element, so + // we can't have left the loop without seeing them + unreachable!(); + }, + (_, _) => {}, + } + } + } - let random = if abstract_uint < other_uint { - NodeConstants::DOCUMENT_POSITION_FOLLOWING - } else { - NodeConstants::DOCUMENT_POSITION_PRECEDING - }; - // step 3. - return random + + // Step 6 + match (node1, node2) { + (None, _) => { + // node1 is null + return NodeConstants::DOCUMENT_POSITION_FOLLOWING + NodeConstants::DOCUMENT_POSITION_DISCONNECTED + NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; - } - - for child in lastself.traverse_preorder() { - if &*child == other { - // step 6. - return NodeConstants::DOCUMENT_POSITION_PRECEDING; + }, + (_, None) => { + // node2 is null + return NodeConstants::DOCUMENT_POSITION_PRECEDING + + NodeConstants::DOCUMENT_POSITION_DISCONNECTED + + NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; + }, + (Some(node1), Some(node2)) => { + // still step 6, testing if node1 and 2 share a root + let mut self_and_ancestors = node2 + .inclusive_ancestors(ShadowIncluding::No) + .collect::<SmallVec<[_; 20]>>(); + let mut other_and_ancestors = node1 + .inclusive_ancestors(ShadowIncluding::No) + .collect::<SmallVec<[_; 20]>>(); + + if self_and_ancestors.last() != other_and_ancestors.last() { + let random = as_uintptr(self_and_ancestors.last().unwrap()) < + as_uintptr(other_and_ancestors.last().unwrap()); + let random = if random { + NodeConstants::DOCUMENT_POSITION_FOLLOWING + } else { + NodeConstants::DOCUMENT_POSITION_PRECEDING + }; + + // Disconnected. + return random + + NodeConstants::DOCUMENT_POSITION_DISCONNECTED + + NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; } - if &*child == self { - // step 7. - return NodeConstants::DOCUMENT_POSITION_FOLLOWING; + // steps 7-10 + let mut parent = self_and_ancestors.pop().unwrap(); + other_and_ancestors.pop().unwrap(); + + let mut current_position = + cmp::min(self_and_ancestors.len(), other_and_ancestors.len()); + + while current_position > 0 { + current_position -= 1; + let child_1 = self_and_ancestors.pop().unwrap(); + let child_2 = other_and_ancestors.pop().unwrap(); + + if child_1 != child_2 { + let is_before = parent.children().position(|c| c == child_1).unwrap() < + parent.children().position(|c| c == child_2).unwrap(); + // If I am before, `other` is following, and the other way + // around. + return if is_before { + NodeConstants::DOCUMENT_POSITION_FOLLOWING + } else { + NodeConstants::DOCUMENT_POSITION_PRECEDING + }; + } + + parent = child_1; } - } - unreachable!() + + // We hit the end of one of the parent chains, so one node needs to be + // contained in the other. + // + // If we're the container, return that `other` is contained by us. + return if self_and_ancestors.len() < other_and_ancestors.len() { + NodeConstants::DOCUMENT_POSITION_FOLLOWING + + NodeConstants::DOCUMENT_POSITION_CONTAINED_BY + } else { + NodeConstants::DOCUMENT_POSITION_PRECEDING + + NodeConstants::DOCUMENT_POSITION_CONTAINS + }; + }, } } @@ -2341,7 +3094,7 @@ impl NodeMethods for Node { fn Contains(&self, maybe_other: Option<&Node>) -> bool { match maybe_other { None => false, - Some(other) => self.is_inclusive_ancestor_of(other) + Some(other) => self.is_inclusive_ancestor_of(other), } } @@ -2356,20 +3109,21 @@ impl NodeMethods for Node { // Step 2. match self.type_id() { - NodeTypeId::Element(..) => { - self.downcast::<Element>().unwrap().lookup_prefix(namespace) - }, - NodeTypeId::Document(_) => { - self.downcast::<Document>().unwrap().GetDocumentElement().and_then(|element| { - element.lookup_prefix(namespace) - }) - }, - NodeTypeId::DocumentType | NodeTypeId::DocumentFragment => None, - _ => { - self.GetParentElement().and_then(|element| { - element.lookup_prefix(namespace) - }) - } + NodeTypeId::Element(..) => self.downcast::<Element>().unwrap().lookup_prefix(namespace), + NodeTypeId::Document(_) => self + .downcast::<Document>() + .unwrap() + .GetDocumentElement() + .and_then(|element| element.lookup_prefix(namespace)), + NodeTypeId::DocumentType | NodeTypeId::DocumentFragment(_) => None, + NodeTypeId::Attr => self + .downcast::<Attr>() + .unwrap() + .GetOwnerElement() + .and_then(|element| element.lookup_prefix(namespace)), + _ => self + .GetParentElement() + .and_then(|element| element.lookup_prefix(namespace)), } } @@ -2378,7 +3132,7 @@ impl NodeMethods for Node { // Step 1. let prefix = match prefix { Some(ref p) if p.is_empty() => None, - pre => pre + pre => pre, }; // Step 2. @@ -2394,18 +3148,35 @@ impl NodeMethods for Node { } } -pub fn document_from_node<T: DerivedFrom<Node> + DomObject>(derived: &T) -> Root<Document> { +pub fn document_from_node<T: DerivedFrom<Node> + DomObject>(derived: &T) -> DomRoot<Document> { derived.upcast().owner_doc() } -pub fn window_from_node<T: DerivedFrom<Node> + DomObject>(derived: &T) -> Root<Window> { +pub fn containing_shadow_root<T: DerivedFrom<Node> + DomObject>( + derived: &T, +) -> Option<DomRoot<ShadowRoot>> { + derived.upcast().containing_shadow_root() +} + +#[allow(unrooted_must_root)] +pub fn stylesheets_owner_from_node<T: DerivedFrom<Node> + DomObject>( + derived: &T, +) -> StyleSheetListOwner { + if let Some(shadow_root) = containing_shadow_root(derived) { + StyleSheetListOwner::ShadowRoot(Dom::from_ref(&*shadow_root)) + } else { + StyleSheetListOwner::Document(Dom::from_ref(&*document_from_node(derived))) + } +} + +pub fn window_from_node<T: DerivedFrom<Node> + DomObject>(derived: &T) -> DomRoot<Window> { let document = document_from_node(derived); - Root::from_ref(document.window()) + DomRoot::from_ref(document.window()) } impl VirtualMethods for Node { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<EventTarget>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<EventTarget>() as &dyn VirtualMethods) } fn children_changed(&self, mutation: &ChildrenMutation) { @@ -2415,6 +3186,7 @@ impl VirtualMethods for Node { if let Some(list) = self.child_list.get() { list.as_children_list().children_changed(mutation); } + self.owner_doc().content_and_heritage_changed(self); } // This handles the ranges mentioned in steps 2-3 when removing a node. @@ -2426,7 +3198,7 @@ impl VirtualMethods for Node { } /// A summary of the changes that happened to a node. -#[derive(Copy, Clone, PartialEq, HeapSizeOf)] +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub enum NodeDamage { /// The node's `style` attribute changed. NodeStyleDamaged, @@ -2435,46 +3207,73 @@ pub enum NodeDamage { } pub enum ChildrenMutation<'a> { - Append { prev: &'a Node, added: &'a [&'a Node] }, - Insert { prev: &'a Node, added: &'a [&'a Node], next: &'a Node }, - Prepend { added: &'a [&'a Node], next: &'a Node }, + Append { + prev: &'a Node, + added: &'a [&'a Node], + }, + Insert { + prev: &'a Node, + added: &'a [&'a Node], + next: &'a Node, + }, + Prepend { + added: &'a [&'a Node], + next: &'a Node, + }, Replace { prev: Option<&'a Node>, removed: &'a Node, added: &'a [&'a Node], next: Option<&'a Node>, }, - ReplaceAll { removed: &'a [&'a Node], added: &'a [&'a Node] }, + ReplaceAll { + removed: &'a [&'a Node], + added: &'a [&'a Node], + }, + /// Mutation for when a Text node's data is modified. + /// This doesn't change the structure of the list, which is what the other + /// variants' fields are stored for at the moment, so this can just have no + /// fields. + ChangeText, } impl<'a> ChildrenMutation<'a> { - fn insert(prev: Option<&'a Node>, added: &'a [&'a Node], next: Option<&'a Node>) - -> ChildrenMutation<'a> { + fn insert( + prev: Option<&'a Node>, + added: &'a [&'a Node], + next: Option<&'a Node>, + ) -> ChildrenMutation<'a> { match (prev, next) { - (None, None) => { - ChildrenMutation::ReplaceAll { removed: &[], added: added } + (None, None) => ChildrenMutation::ReplaceAll { + removed: &[], + added: added, }, - (Some(prev), None) => { - ChildrenMutation::Append { prev: prev, added: added } + (Some(prev), None) => ChildrenMutation::Append { + prev: prev, + added: added, }, - (None, Some(next)) => { - ChildrenMutation::Prepend { added: added, next: next } + (None, Some(next)) => ChildrenMutation::Prepend { + added: added, + next: next, }, - (Some(prev), Some(next)) => { - ChildrenMutation::Insert { prev: prev, added: added, next: next } + (Some(prev), Some(next)) => ChildrenMutation::Insert { + prev: prev, + added: added, + next: next, }, } } - fn replace(prev: Option<&'a Node>, - removed: &'a Option<&'a Node>, - added: &'a [&'a Node], - next: Option<&'a Node>) - -> ChildrenMutation<'a> { + fn replace( + prev: Option<&'a Node>, + removed: &'a Option<&'a Node>, + added: &'a [&'a Node], + next: Option<&'a Node>, + ) -> ChildrenMutation<'a> { if let Some(ref removed) = *removed { if let (None, None) = (prev, next) { ChildrenMutation::ReplaceAll { - removed: ref_slice(removed), + removed: from_ref(removed), added: added, } } else { @@ -2490,12 +3289,17 @@ impl<'a> ChildrenMutation<'a> { } } - fn replace_all(removed: &'a [&'a Node], added: &'a [&'a Node]) - -> ChildrenMutation<'a> { - ChildrenMutation::ReplaceAll { removed: removed, added: added } + fn replace_all(removed: &'a [&'a Node], added: &'a [&'a Node]) -> ChildrenMutation<'a> { + ChildrenMutation::ReplaceAll { + removed: removed, + added: added, + } } /// Get the child that follows the added or removed children. + /// Currently only used when this mutation might force us to + /// restyle later children (see HAS_SLOW_SELECTOR_LATER_SIBLINGS and + /// Element's implementation of VirtualMethods::children_changed). pub fn next_child(&self) -> Option<&Node> { match *self { ChildrenMutation::Append { .. } => None, @@ -2503,6 +3307,7 @@ impl<'a> ChildrenMutation<'a> { ChildrenMutation::Prepend { next, .. } => Some(next), ChildrenMutation::Replace { next, .. } => next, ChildrenMutation::ReplaceAll { .. } => None, + ChildrenMutation::ChangeText => None, } } @@ -2512,38 +3317,75 @@ impl<'a> ChildrenMutation<'a> { /// NOTE: This does not check whether the inserted/removed nodes were elements, so in some /// cases it will return a false positive. This doesn't matter for correctness, because at /// worst the returned element will be restyled unnecessarily. - pub fn modified_edge_element(&self) -> Option<Root<Node>> { + pub fn modified_edge_element(&self) -> Option<DomRoot<Node>> { match *self { // Add/remove at start of container: Return the first following element. ChildrenMutation::Prepend { next, .. } | - ChildrenMutation::Replace { prev: None, next: Some(next), .. } => { - next.inclusively_following_siblings().filter(|node| node.is::<Element>()).next() - } + ChildrenMutation::Replace { + prev: None, + next: Some(next), + .. + } => next + .inclusively_following_siblings() + .filter(|node| node.is::<Element>()) + .next(), // Add/remove at end of container: Return the last preceding element. ChildrenMutation::Append { prev, .. } | - ChildrenMutation::Replace { prev: Some(prev), next: None, .. } => { - prev.inclusively_preceding_siblings().filter(|node| node.is::<Element>()).next() - } + ChildrenMutation::Replace { + prev: Some(prev), + next: None, + .. + } => prev + .inclusively_preceding_siblings() + .filter(|node| node.is::<Element>()) + .next(), // Insert or replace in the middle: ChildrenMutation::Insert { prev, next, .. } | - ChildrenMutation::Replace { prev: Some(prev), next: Some(next), .. } => { - if prev.inclusively_preceding_siblings().all(|node| !node.is::<Element>()) { + ChildrenMutation::Replace { + prev: Some(prev), + next: Some(next), + .. + } => { + if prev + .inclusively_preceding_siblings() + .all(|node| !node.is::<Element>()) + { // Before the first element: Return the first following element. - next.inclusively_following_siblings().filter(|node| node.is::<Element>()).next() - } else if next.inclusively_following_siblings().all(|node| !node.is::<Element>()) { + next.inclusively_following_siblings() + .filter(|node| node.is::<Element>()) + .next() + } else if next + .inclusively_following_siblings() + .all(|node| !node.is::<Element>()) + { // After the last element: Return the last preceding element. - prev.inclusively_preceding_siblings().filter(|node| node.is::<Element>()).next() + prev.inclusively_preceding_siblings() + .filter(|node| node.is::<Element>()) + .next() } else { None } - } + }, - ChildrenMutation::Replace { prev: None, next: None, .. } => unreachable!(), + ChildrenMutation::Replace { + prev: None, + next: None, + .. + } => unreachable!(), ChildrenMutation::ReplaceAll { .. } => None, + ChildrenMutation::ChangeText => None, } } } +/// The context of the binding to tree of a node. +pub struct BindContext { + /// Whether the tree is connected. + pub tree_connected: bool, + /// Whether the tree is in the document. + pub tree_in_doc: bool, +} + /// The context of the unbinding from a tree of a node when one of its /// inclusive ancestors is removed. pub struct UnbindContext<'a> { @@ -2553,19 +3395,28 @@ pub struct UnbindContext<'a> { pub parent: &'a Node, /// The previous sibling of the inclusive ancestor that was removed. prev_sibling: Option<&'a Node>, - /// Whether the tree is in a document. + /// The next sibling of the inclusive ancestor that was removed. + pub next_sibling: Option<&'a Node>, + /// Whether the tree is connected. + pub tree_connected: bool, + /// Whether the tree is in doc. pub tree_in_doc: bool, } impl<'a> UnbindContext<'a> { /// Create a new `UnbindContext` value. - fn new(parent: &'a Node, - prev_sibling: Option<&'a Node>, - cached_index: Option<u32>) -> Self { + pub fn new( + parent: &'a Node, + prev_sibling: Option<&'a Node>, + next_sibling: Option<&'a Node>, + cached_index: Option<u32>, + ) -> Self { UnbindContext { index: Cell::new(cached_index), parent: parent, prev_sibling: prev_sibling, + next_sibling: next_sibling, + tree_connected: parent.is_connected(), tree_in_doc: parent.is_in_doc(), } } @@ -2583,17 +3434,17 @@ impl<'a> UnbindContext<'a> { } /// A node's unique ID, for devtools. -struct UniqueId { +pub struct UniqueId { cell: UnsafeCell<Option<Box<Uuid>>>, } unsafe_no_jsmanaged_fields!(UniqueId); -impl HeapSizeOf for UniqueId { +impl MallocSizeOf for UniqueId { #[allow(unsafe_code)] - fn heap_size_of_children(&self) -> usize { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { if let &Some(ref uuid) = unsafe { &*self.cell.get() } { - unsafe { heap_size_of(&** uuid as *const Uuid as *const _) } + unsafe { ops.malloc_size_of(&**uuid) } } else { 0 } @@ -2603,7 +3454,9 @@ impl HeapSizeOf for UniqueId { impl UniqueId { /// Create a new `UniqueId` value. The underlying `Uuid` is lazily created. fn new() -> UniqueId { - UniqueId { cell: UnsafeCell::new(None) } + UniqueId { + cell: UnsafeCell::new(None), + } } /// The Uuid of that unique ID. @@ -2612,7 +3465,7 @@ impl UniqueId { unsafe { let ptr = self.cell.get(); if (*ptr).is_none() { - *ptr = Some(box Uuid::new_v4()); + *ptr = Some(Box::new(Uuid::new_v4())); } &(&*ptr).as_ref().unwrap() } @@ -2623,10 +3476,8 @@ impl Into<LayoutNodeType> for NodeTypeId { #[inline(always)] fn into(self) -> LayoutNodeType { match self { - NodeTypeId::Element(e) => - LayoutNodeType::Element(e.into()), - NodeTypeId::CharacterData(CharacterDataTypeId::Text) => - LayoutNodeType::Text, + NodeTypeId::Element(e) => LayoutNodeType::Element(e.into()), + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => LayoutNodeType::Text, x => unreachable!("Layout should not traverse nodes of type {:?}", x), } } @@ -2636,30 +3487,57 @@ impl Into<LayoutElementType> for ElementTypeId { #[inline(always)] fn into(self) -> LayoutElementType { match self { - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement) => - LayoutElementType::HTMLCanvasElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement) => - LayoutElementType::HTMLIFrameElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement) => - LayoutElementType::HTMLImageElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement) => - LayoutElementType::HTMLInputElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement) => - LayoutElementType::HTMLObjectElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableCellElement(_)) => - LayoutElementType::HTMLTableCellElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableColElement) => - LayoutElementType::HTMLTableColElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableElement) => - LayoutElementType::HTMLTableElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableRowElement) => - LayoutElementType::HTMLTableRowElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableSectionElement) => - LayoutElementType::HTMLTableSectionElement, - ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement) => - LayoutElementType::HTMLTextAreaElement, - ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement(SVGGraphicsElementTypeId::SVGSVGElement)) => - LayoutElementType::SVGSVGElement, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement) => { + LayoutElementType::HTMLBodyElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBRElement) => { + LayoutElementType::HTMLBRElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement) => { + LayoutElementType::HTMLCanvasElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLHtmlElement) => { + LayoutElementType::HTMLHtmlElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement) => { + LayoutElementType::HTMLIFrameElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement) => { + LayoutElementType::HTMLImageElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLMediaElement(_)) => { + LayoutElementType::HTMLMediaElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement) => { + LayoutElementType::HTMLInputElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement) => { + LayoutElementType::HTMLObjectElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLParagraphElement) => { + LayoutElementType::HTMLParagraphElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableCellElement) => { + LayoutElementType::HTMLTableCellElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableColElement) => { + LayoutElementType::HTMLTableColElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableElement) => { + LayoutElementType::HTMLTableElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableRowElement) => { + LayoutElementType::HTMLTableRowElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableSectionElement) => { + LayoutElementType::HTMLTableSectionElement + }, + ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement) => { + LayoutElementType::HTMLTextAreaElement + }, + ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( + SVGGraphicsElementTypeId::SVGSVGElement, + )) => LayoutElementType::SVGSVGElement, _ => LayoutElementType::Element, } } @@ -2671,8 +3549,9 @@ pub trait VecPreOrderInsertionHelper<T> { fn insert_pre_order(&mut self, elem: &T, tree_root: &Node); } -impl<T> VecPreOrderInsertionHelper<T> for Vec<JS<T>> - where T: DerivedFrom<Node> + DomObject +impl<T> VecPreOrderInsertionHelper<T> for Vec<Dom<T>> +where + T: DerivedFrom<Node> + DomObject, { /// This algorithm relies on the following assumptions: /// * any elements inserted in this vector share the same tree root @@ -2685,21 +3564,21 @@ impl<T> VecPreOrderInsertionHelper<T> for Vec<JS<T>> /// the traversal. fn insert_pre_order(&mut self, elem: &T, tree_root: &Node) { if self.is_empty() { - self.push(JS::from_ref(elem)); + self.push(Dom::from_ref(elem)); return; } let elem_node = elem.upcast::<Node>(); let mut head: usize = 0; - for node in tree_root.traverse_preorder() { - let head_node = Root::upcast::<Node>(Root::from_ref(&*self[head])); + for node in tree_root.traverse_preorder(ShadowIncluding::No) { + let head_node = DomRoot::upcast::<Node>(DomRoot::from_ref(&*self[head])); if head_node == node { head += 1; } - if elem_node == node.r() || head == self.len() { + if elem_node == &*node || head == self.len() { break; } } - self.insert(head, JS::from_ref(elem)); + self.insert(head, Dom::from_ref(elem)); } } diff --git a/components/script/dom/nodeiterator.rs b/components/script/dom/nodeiterator.rs index 91b590a3647..a141785a85d 100644 --- a/components/script/dom/nodeiterator.rs +++ b/components/script/dom/nodeiterator.rs @@ -1,18 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::callback::ExceptionHandling::Rethrow; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter; -use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilterConstants; -use dom::bindings::codegen::Bindings::NodeIteratorBinding; -use dom::bindings::codegen::Bindings::NodeIteratorBinding::NodeIteratorMethods; -use dom::bindings::error::Fallible; -use dom::bindings::js::{JS, MutJS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::document::Document; -use dom::node::Node; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::callback::ExceptionHandling::Rethrow; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter; +use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilterConstants; +use crate::dom::bindings::codegen::Bindings::NodeIteratorBinding::NodeIteratorMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutDom}; +use crate::dom::document::Document; +use crate::dom::node::Node; use dom_struct::dom_struct; use std::cell::Cell; use std::rc::Rc; @@ -20,45 +19,50 @@ use std::rc::Rc; #[dom_struct] pub struct NodeIterator { reflector_: Reflector, - root_node: JS<Node>, - #[ignore_heap_size_of = "Defined in rust-mozjs"] - reference_node: MutJS<Node>, + root_node: Dom<Node>, + #[ignore_malloc_size_of = "Defined in rust-mozjs"] + reference_node: MutDom<Node>, pointer_before_reference_node: Cell<bool>, what_to_show: u32, - #[ignore_heap_size_of = "Can't measure due to #6870"] + #[ignore_malloc_size_of = "Can't measure due to #6870"] filter: Filter, + active: Cell<bool>, } impl NodeIterator { - fn new_inherited(root_node: &Node, - what_to_show: u32, - filter: Filter) -> NodeIterator { + fn new_inherited(root_node: &Node, what_to_show: u32, filter: Filter) -> NodeIterator { NodeIterator { reflector_: Reflector::new(), - root_node: JS::from_ref(root_node), - reference_node: MutJS::new(root_node), + root_node: Dom::from_ref(root_node), + reference_node: MutDom::new(root_node), pointer_before_reference_node: Cell::new(true), what_to_show: what_to_show, - filter: filter + filter: filter, + active: Cell::new(false), } } - pub fn new_with_filter(document: &Document, - root_node: &Node, - what_to_show: u32, - filter: Filter) -> Root<NodeIterator> { - reflect_dom_object(box NodeIterator::new_inherited(root_node, what_to_show, filter), - document.window(), - NodeIteratorBinding::Wrap) + pub fn new_with_filter( + document: &Document, + root_node: &Node, + what_to_show: u32, + filter: Filter, + ) -> DomRoot<NodeIterator> { + reflect_dom_object( + Box::new(NodeIterator::new_inherited(root_node, what_to_show, filter)), + document.window(), + ) } - pub fn new(document: &Document, - root_node: &Node, - what_to_show: u32, - node_filter: Option<Rc<NodeFilter>>) -> Root<NodeIterator> { + pub fn new( + document: &Document, + root_node: &Node, + what_to_show: u32, + node_filter: Option<Rc<NodeFilter>>, + ) -> DomRoot<NodeIterator> { let filter = match node_filter { None => Filter::None, - Some(jsfilter) => Filter::Callback(jsfilter) + Some(jsfilter) => Filter::Callback(jsfilter), }; NodeIterator::new_with_filter(document, root_node, what_to_show, filter) } @@ -66,8 +70,8 @@ impl NodeIterator { impl NodeIteratorMethods for NodeIterator { // https://dom.spec.whatwg.org/#dom-nodeiterator-root - fn Root(&self) -> Root<Node> { - Root::from_ref(&*self.root_node) + fn Root(&self) -> DomRoot<Node> { + DomRoot::from_ref(&*self.root_node) } // https://dom.spec.whatwg.org/#dom-nodeiterator-whattoshow @@ -84,7 +88,7 @@ impl NodeIteratorMethods for NodeIterator { } // https://dom.spec.whatwg.org/#dom-nodeiterator-referencenode - fn ReferenceNode(&self) -> Root<Node> { + fn ReferenceNode(&self) -> DomRoot<Node> { self.reference_node.get() } @@ -94,7 +98,7 @@ impl NodeIteratorMethods for NodeIterator { } // https://dom.spec.whatwg.org/#dom-nodeiterator-nextnode - fn NextNode(&self) -> Fallible<Option<Root<Node>>> { + fn NextNode(&self) -> Fallible<Option<DomRoot<Node>>> { // https://dom.spec.whatwg.org/#concept-NodeIterator-traverse // Step 1. let node = self.reference_node.get(); @@ -107,7 +111,7 @@ impl NodeIteratorMethods for NodeIterator { before_node = false; // Step 3-2. - let result = try!(self.accept_node(&node)); + let result = self.accept_node(&node)?; // Step 3-3. if result == NodeFilterConstants::FILTER_ACCEPT { @@ -122,7 +126,7 @@ impl NodeIteratorMethods for NodeIterator { // Step 3-1. for following_node in node.following_nodes(&self.root_node) { // Step 3-2. - let result = try!(self.accept_node(&following_node)); + let result = self.accept_node(&following_node)?; // Step 3-3. if result == NodeFilterConstants::FILTER_ACCEPT { @@ -138,7 +142,7 @@ impl NodeIteratorMethods for NodeIterator { } // https://dom.spec.whatwg.org/#dom-nodeiterator-previousnode - fn PreviousNode(&self) -> Fallible<Option<Root<Node>>> { + fn PreviousNode(&self) -> Fallible<Option<DomRoot<Node>>> { // https://dom.spec.whatwg.org/#concept-NodeIterator-traverse // Step 1. let node = self.reference_node.get(); @@ -151,7 +155,7 @@ impl NodeIteratorMethods for NodeIterator { before_node = true; // Step 3-2. - let result = try!(self.accept_node(&node)); + let result = self.accept_node(&node)?; // Step 3-3. if result == NodeFilterConstants::FILTER_ACCEPT { @@ -166,7 +170,7 @@ impl NodeIteratorMethods for NodeIterator { // Step 3-1. for preceding_node in node.preceding_nodes(&self.root_node) { // Step 3-2. - let result = try!(self.accept_node(&preceding_node)); + let result = self.accept_node(&preceding_node)?; // Step 3-3. if result == NodeFilterConstants::FILTER_ACCEPT { @@ -187,27 +191,39 @@ impl NodeIteratorMethods for NodeIterator { } } - impl NodeIterator { // https://dom.spec.whatwg.org/#concept-node-filter fn accept_node(&self, node: &Node) -> Fallible<u16> { // Step 1. - let n = node.NodeType() - 1; + if self.active.get() { + return Err(Error::InvalidState); + } // Step 2. + let n = node.NodeType() - 1; + // Step 3. if (self.what_to_show & (1 << n)) == 0 { - return Ok(NodeFilterConstants::FILTER_SKIP) + return Ok(NodeFilterConstants::FILTER_SKIP); } - // Step 3-5. + match self.filter { + // Step 4. Filter::None => Ok(NodeFilterConstants::FILTER_ACCEPT), - Filter::Callback(ref callback) => callback.AcceptNode_(self, node, Rethrow) + Filter::Callback(ref callback) => { + // Step 5. + self.active.set(true); + // Step 6. + let result = callback.AcceptNode_(self, node, Rethrow); + // Step 7. + self.active.set(false); + // Step 8. + result + }, } } } - #[derive(JSTraceable)] pub enum Filter { None, - Callback(Rc<NodeFilter>) + Callback(Rc<NodeFilter>), } diff --git a/components/script/dom/nodelist.rs b/components/script/dom/nodelist.rs index 5c0983ccaa3..d7ec1808323 100644 --- a/components/script/dom/nodelist.rs +++ b/components/script/dom/nodelist.rs @@ -1,22 +1,29 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::Bindings::NodeListBinding; -use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; -use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::node::{ChildrenMutation, Node}; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlformelement::HTMLFormElement; +use crate::dom::node::{ChildrenMutation, Node}; +use crate::dom::window::Window; use dom_struct::dom_struct; +use servo_atoms::Atom; use std::cell::Cell; -#[derive(JSTraceable, HeapSizeOf)] -#[must_root] +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] pub enum NodeListType { - Simple(Vec<JS<Node>>), + Simple(Vec<Dom<Node>>), Children(ChildrenList), + Labels(LabelsList), + Radio(RadioList), + ElementsByName(ElementsByNameList), } // https://dom.spec.whatwg.org/#interface-nodelist @@ -36,22 +43,47 @@ impl NodeList { } #[allow(unrooted_must_root)] - pub fn new(window: &Window, list_type: NodeListType) -> Root<NodeList> { - reflect_dom_object(box NodeList::new_inherited(list_type), - window, - NodeListBinding::Wrap) + pub fn new(window: &Window, list_type: NodeListType) -> DomRoot<NodeList> { + reflect_dom_object(Box::new(NodeList::new_inherited(list_type)), window) + } + + pub fn new_simple_list<T>(window: &Window, iter: T) -> DomRoot<NodeList> + where + T: Iterator<Item = DomRoot<Node>>, + { + NodeList::new( + window, + NodeListType::Simple(iter.map(|r| Dom::from_ref(&*r)).collect()), + ) } - pub fn new_simple_list<T>(window: &Window, iter: T) -> Root<NodeList> - where T: Iterator<Item=Root<Node>> { - NodeList::new(window, NodeListType::Simple(iter.map(|r| JS::from_ref(&*r)).collect())) + pub fn new_simple_list_slice(window: &Window, slice: &[&Node]) -> DomRoot<NodeList> { + NodeList::new( + window, + NodeListType::Simple(slice.iter().map(|r| Dom::from_ref(*r)).collect()), + ) } - pub fn new_child_list(window: &Window, node: &Node) -> Root<NodeList> { + pub fn new_child_list(window: &Window, node: &Node) -> DomRoot<NodeList> { NodeList::new(window, NodeListType::Children(ChildrenList::new(node))) } - pub fn empty(window: &Window) -> Root<NodeList> { + pub fn new_labels_list(window: &Window, element: &HTMLElement) -> DomRoot<NodeList> { + NodeList::new(window, NodeListType::Labels(LabelsList::new(element))) + } + + pub fn new_elements_by_name_list( + window: &Window, + document: &Document, + name: DOMString, + ) -> DomRoot<NodeList> { + NodeList::new( + window, + NodeListType::ElementsByName(ElementsByNameList::new(document, name)), + ) + } + + pub fn empty(window: &Window) -> DomRoot<NodeList> { NodeList::new(window, NodeListType::Simple(vec![])) } } @@ -62,57 +94,62 @@ impl NodeListMethods for NodeList { match self.list_type { NodeListType::Simple(ref elems) => elems.len() as u32, NodeListType::Children(ref list) => list.len(), + NodeListType::Labels(ref list) => list.len(), + NodeListType::Radio(ref list) => list.len(), + NodeListType::ElementsByName(ref list) => list.len(), } } // https://dom.spec.whatwg.org/#dom-nodelist-item - fn Item(&self, index: u32) -> Option<Root<Node>> { + fn Item(&self, index: u32) -> Option<DomRoot<Node>> { match self.list_type { - NodeListType::Simple(ref elems) => { - elems.get(index as usize).map(|node| Root::from_ref(&**node)) - }, + NodeListType::Simple(ref elems) => elems + .get(index as usize) + .map(|node| DomRoot::from_ref(&**node)), NodeListType::Children(ref list) => list.item(index), + NodeListType::Labels(ref list) => list.item(index), + NodeListType::Radio(ref list) => list.item(index), + NodeListType::ElementsByName(ref list) => list.item(index), } } // https://dom.spec.whatwg.org/#dom-nodelist-item - fn IndexedGetter(&self, index: u32) -> Option<Root<Node>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Node>> { self.Item(index) } } - impl NodeList { pub fn as_children_list(&self) -> &ChildrenList { if let NodeListType::Children(ref list) = self.list_type { list } else { - panic!("called as_children_list() on a simple node list") + panic!("called as_children_list() on a non-children node list") } } - pub fn as_simple_list(&self) -> &Vec<JS<Node>> { - if let NodeListType::Simple(ref list) = self.list_type { + pub fn as_radio_list(&self) -> &RadioList { + if let NodeListType::Radio(ref list) = self.list_type { list } else { - panic!("called as_simple_list() on a children node list") + panic!("called as_radio_list() on a non-radio node list") } } - pub fn iter(&self) -> NodeListIterator { - NodeListIterator { - nodes: self, - offset: 0, - } + pub fn iter<'a>(&'a self) -> impl Iterator<Item = DomRoot<Node>> + 'a { + let len = self.Length(); + // There is room for optimization here in non-simple cases, + // as calling Item repeatedly on a live list can involve redundant work. + (0..len).flat_map(move |i| self.Item(i)) } } -#[derive(JSTraceable, HeapSizeOf)] -#[must_root] +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] pub struct ChildrenList { - node: JS<Node>, - #[ignore_heap_size_of = "Defined in rust-mozjs"] - last_visited: MutNullableJS<Node>, + node: Dom<Node>, + #[ignore_malloc_size_of = "Defined in rust-mozjs"] + last_visited: MutNullableDom<Node>, last_index: Cell<u32>, } @@ -120,8 +157,8 @@ impl ChildrenList { pub fn new(node: &Node) -> ChildrenList { let last_visited = node.GetFirstChild(); ChildrenList { - node: JS::from_ref(node), - last_visited: MutNullableJS::new(last_visited.r()), + node: Dom::from_ref(node), + last_visited: MutNullableDom::new(last_visited.as_deref()), last_index: Cell::new(0u32), } } @@ -130,7 +167,7 @@ impl ChildrenList { self.node.children_count() } - pub fn item(&self, index: u32) -> Option<Root<Node>> { + pub fn item(&self, index: u32) -> Option<DomRoot<Node>> { // This always start traversing the children from the closest element // among parent's first and last children and the last visited one. let len = self.len() as u32; @@ -151,7 +188,11 @@ impl ChildrenList { self.last_visited.get().unwrap().GetNextSibling().unwrap() } else if last_index > 0 && index == last_index - 1u32 { // Item is last visited's previous sibling. - self.last_visited.get().unwrap().GetPreviousSibling().unwrap() + self.last_visited + .get() + .unwrap() + .GetPreviousSibling() + .unwrap() } else if index > last_index { if index == len - 1u32 { // Item is parent's last child, not worth updating last visited. @@ -159,28 +200,39 @@ impl ChildrenList { } if index <= last_index + (len - last_index) / 2u32 { // Item is closer to the last visited child and follows it. - self.last_visited.get().unwrap() - .inclusively_following_siblings() - .nth((index - last_index) as usize).unwrap() + self.last_visited + .get() + .unwrap() + .inclusively_following_siblings() + .nth((index - last_index) as usize) + .unwrap() } else { // Item is closer to parent's last child and obviously // precedes it. - self.node.GetLastChild().unwrap() + self.node + .GetLastChild() + .unwrap() .inclusively_preceding_siblings() - .nth((len - index - 1u32) as usize).unwrap() + .nth((len - index - 1u32) as usize) + .unwrap() } } else if index >= last_index / 2u32 { // Item is closer to the last visited child and precedes it. - self.last_visited.get().unwrap() - .inclusively_preceding_siblings() - .nth((last_index - index) as usize).unwrap() + self.last_visited + .get() + .unwrap() + .inclusively_preceding_siblings() + .nth((last_index - index) as usize) + .unwrap() } else { // Item is closer to parent's first child and obviously follows it. debug_assert!(index < last_index / 2u32); - self.node.GetFirstChild().unwrap() - .inclusively_following_siblings() - .nth(index as usize) - .unwrap() + self.node + .GetFirstChild() + .unwrap() + .inclusively_following_siblings() + .nth(index as usize) + .unwrap() }; self.last_visited.set(Some(&last_visited)); self.last_index.set(index); @@ -209,11 +261,13 @@ impl ChildrenList { } } - fn replace(list: &ChildrenList, - prev: Option<&Node>, - removed: &Node, - added: &[&Node], - next: Option<&Node>) { + fn replace( + list: &ChildrenList, + prev: Option<&Node>, + removed: &Node, + added: &[&Node], + next: Option<&Node>, + ) { let index = list.last_index.get(); if removed == &*list.last_visited.get().unwrap() { let visited = match (prev, added, next) { @@ -223,9 +277,9 @@ impl ChildrenList { // by ChildrenMutation::replace(). unreachable!() }, - (_, &[node, ..], _) => node, - (_, &[], Some(next)) => next, - (Some(prev), &[], None) => { + (_, added, _) if !added.is_empty() => added[0], + (_, _, Some(next)) => next, + (Some(prev), _, None) => { list.last_index.set(index - 1u32); prev }, @@ -257,7 +311,12 @@ impl ChildrenList { ChildrenMutation::Prepend { added, next } => { prepend(self, added, next); }, - ChildrenMutation::Replace { prev, removed, added, next } => { + ChildrenMutation::Replace { + prev, + removed, + added, + next, + } => { replace(self, prev, removed, added, next); }, ChildrenMutation::ReplaceAll { added, .. } => { @@ -277,26 +336,105 @@ impl ChildrenList { self.last_index.set(middle as u32); } }, + ChildrenMutation::ChangeText => {}, } } fn reset(&self) { - self.last_visited.set(self.node.GetFirstChild().r()); + self.last_visited.set(self.node.GetFirstChild().as_deref()); self.last_index.set(0u32); } } -pub struct NodeListIterator<'a> { - nodes: &'a NodeList, - offset: u32, +// Labels lists: There might be room for performance optimization +// analogous to the ChildrenMutation case of a children list, +// in which we can keep information from an older access live +// if we know nothing has happened that would change it. +// However, label relationships can happen from further away +// in the DOM than parent-child relationships, so it's not as simple, +// and it's possible that tracking label moves would end up no faster +// than recalculating labels. +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct LabelsList { + element: Dom<HTMLElement>, } -impl<'a> Iterator for NodeListIterator<'a> { - type Item = Root<Node>; +impl LabelsList { + pub fn new(element: &HTMLElement) -> LabelsList { + LabelsList { + element: Dom::from_ref(element), + } + } + + pub fn len(&self) -> u32 { + self.element.labels_count() + } + + pub fn item(&self, index: u32) -> Option<DomRoot<Node>> { + self.element.label_at(index) + } +} + +// Radio node lists: There is room for performance improvement here; +// a form is already aware of changes to its set of controls, +// so a radio list can cache and cache-invalidate its contents +// just by hooking into what the form already knows without a +// separate mutation observer. FIXME #25482 +#[derive(Clone, Copy, JSTraceable, MallocSizeOf)] +pub enum RadioListMode { + ControlsExceptImageInputs, + Images, +} + +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct RadioList { + form: Dom<HTMLFormElement>, + mode: RadioListMode, + name: Atom, +} + +impl RadioList { + pub fn new(form: &HTMLFormElement, mode: RadioListMode, name: Atom) -> RadioList { + RadioList { + form: Dom::from_ref(form), + mode: mode, + name: name, + } + } + + pub fn len(&self) -> u32 { + self.form.count_for_radio_list(self.mode, &self.name) + } + + pub fn item(&self, index: u32) -> Option<DomRoot<Node>> { + self.form.nth_for_radio_list(index, self.mode, &self.name) + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct ElementsByNameList { + document: Dom<Document>, + name: DOMString, +} + +impl ElementsByNameList { + pub fn new(document: &Document, name: DOMString) -> ElementsByNameList { + ElementsByNameList { + document: Dom::from_ref(document), + name: name, + } + } + + pub fn len(&self) -> u32 { + self.document.elements_by_name_count(&self.name) + } - fn next(&mut self) -> Option<Root<Node>> { - let result = self.nodes.Item(self.offset); - self.offset = self.offset + 1; - result + pub fn item(&self, index: u32) -> Option<DomRoot<Node>> { + self.document + .nth_element_by_name(index, &self.name) + .and_then(|n| Some(DomRoot::from_ref(&*n))) } } diff --git a/components/script/dom/offlineaudiocompletionevent.rs b/components/script/dom/offlineaudiocompletionevent.rs new file mode 100644 index 00000000000..5162f4e9671 --- /dev/null +++ b/components/script/dom/offlineaudiocompletionevent.rs @@ -0,0 +1,77 @@ +/* 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 crate::dom::audiobuffer::AudioBuffer; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::OfflineAudioCompletionEventBinding::OfflineAudioCompletionEventInit; +use crate::dom::bindings::codegen::Bindings::OfflineAudioCompletionEventBinding::OfflineAudioCompletionEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct OfflineAudioCompletionEvent { + event: Event, + rendered_buffer: Dom<AudioBuffer>, +} + +impl OfflineAudioCompletionEvent { + pub fn new_inherited(rendered_buffer: &AudioBuffer) -> OfflineAudioCompletionEvent { + OfflineAudioCompletionEvent { + event: Event::new_inherited(), + rendered_buffer: Dom::from_ref(rendered_buffer), + } + } + + pub fn new( + window: &Window, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + rendered_buffer: &AudioBuffer, + ) -> DomRoot<OfflineAudioCompletionEvent> { + let event = Box::new(OfflineAudioCompletionEvent::new_inherited(rendered_buffer)); + let ev = reflect_dom_object(event, window); + { + let event = ev.upcast::<Event>(); + event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); + } + ev + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &OfflineAudioCompletionEventInit, + ) -> Fallible<DomRoot<OfflineAudioCompletionEvent>> { + let bubbles = EventBubbles::from(init.parent.bubbles); + let cancelable = EventCancelable::from(init.parent.cancelable); + Ok(OfflineAudioCompletionEvent::new( + window, + Atom::from(type_), + bubbles, + cancelable, + &init.renderedBuffer, + )) + } +} + +impl OfflineAudioCompletionEventMethods for OfflineAudioCompletionEvent { + // https://webaudio.github.io/web-audio-api/#dom-offlineaudiocompletionevent-renderedbuffer + fn RenderedBuffer(&self) -> DomRoot<AudioBuffer> { + DomRoot::from_ref(&*self.rendered_buffer) + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/offlineaudiocontext.rs b/components/script/dom/offlineaudiocontext.rs new file mode 100644 index 00000000000..bbdf9e1a79f --- /dev/null +++ b/components/script/dom/offlineaudiocontext.rs @@ -0,0 +1,205 @@ +/* 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 crate::dom::audiobuffer::{AudioBuffer, MAX_SAMPLE_RATE, MIN_SAMPLE_RATE}; +use crate::dom::audionode::MAX_CHANNEL_COUNT; +use crate::dom::baseaudiocontext::{BaseAudioContext, BaseAudioContextOptions}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContextBinding::BaseAudioContextMethods; +use crate::dom::bindings::codegen::Bindings::OfflineAudioContextBinding::OfflineAudioContextMethods; +use crate::dom::bindings::codegen::Bindings::OfflineAudioContextBinding::OfflineAudioContextOptions; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::offlineaudiocompletionevent::OfflineAudioCompletionEvent; +use crate::dom::promise::Promise; +use crate::dom::window::Window; +use crate::realms::InRealm; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use msg::constellation_msg::PipelineId; +use servo_media::audio::context::OfflineAudioContextOptions as ServoMediaOfflineAudioContextOptions; +use std::cell::Cell; +use std::rc::Rc; +use std::sync::mpsc; +use std::sync::{Arc, Mutex}; +use std::thread::Builder; + +#[dom_struct] +pub struct OfflineAudioContext { + context: BaseAudioContext, + channel_count: u32, + length: u32, + rendering_started: Cell<bool>, + #[ignore_malloc_size_of = "promises are hard"] + pending_rendering_promise: DomRefCell<Option<Rc<Promise>>>, +} + +#[allow(non_snake_case)] +impl OfflineAudioContext { + #[allow(unrooted_must_root)] + fn new_inherited( + channel_count: u32, + length: u32, + sample_rate: f32, + pipeline_id: PipelineId, + ) -> OfflineAudioContext { + let options = ServoMediaOfflineAudioContextOptions { + channels: channel_count as u8, + length: length as usize, + sample_rate, + }; + let context = BaseAudioContext::new_inherited( + BaseAudioContextOptions::OfflineAudioContext(options), + pipeline_id, + ); + OfflineAudioContext { + context, + channel_count, + length, + rendering_started: Cell::new(false), + pending_rendering_promise: Default::default(), + } + } + + #[allow(unrooted_must_root)] + fn new( + window: &Window, + channel_count: u32, + length: u32, + sample_rate: f32, + ) -> Fallible<DomRoot<OfflineAudioContext>> { + if channel_count > MAX_CHANNEL_COUNT || + channel_count <= 0 || + length <= 0 || + sample_rate < MIN_SAMPLE_RATE || + sample_rate > MAX_SAMPLE_RATE + { + return Err(Error::NotSupported); + } + let pipeline_id = window.pipeline_id(); + let context = + OfflineAudioContext::new_inherited(channel_count, length, sample_rate, pipeline_id); + Ok(reflect_dom_object(Box::new(context), window)) + } + + pub fn Constructor( + window: &Window, + options: &OfflineAudioContextOptions, + ) -> Fallible<DomRoot<OfflineAudioContext>> { + OfflineAudioContext::new( + window, + options.numberOfChannels, + options.length, + *options.sampleRate, + ) + } + + pub fn Constructor_( + window: &Window, + number_of_channels: u32, + length: u32, + sample_rate: Finite<f32>, + ) -> Fallible<DomRoot<OfflineAudioContext>> { + OfflineAudioContext::new(window, number_of_channels, length, *sample_rate) + } +} + +impl OfflineAudioContextMethods for OfflineAudioContext { + // https://webaudio.github.io/web-audio-api/#dom-offlineaudiocontext-oncomplete + event_handler!(complete, GetOncomplete, SetOncomplete); + + // https://webaudio.github.io/web-audio-api/#dom-offlineaudiocontext-length + fn Length(&self) -> u32 { + self.length + } + + // https://webaudio.github.io/web-audio-api/#dom-offlineaudiocontext-startrendering + fn StartRendering(&self, comp: InRealm) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(&self.global(), comp); + if self.rendering_started.get() { + promise.reject_error(Error::InvalidState); + return promise; + } + self.rendering_started.set(true); + + *self.pending_rendering_promise.borrow_mut() = Some(promise.clone()); + + let processed_audio = Arc::new(Mutex::new(Vec::new())); + let processed_audio_ = processed_audio.clone(); + let (sender, receiver) = mpsc::channel(); + let sender = Mutex::new(sender); + self.context + .audio_context_impl() + .lock() + .unwrap() + .set_eos_callback(Box::new(move |buffer| { + processed_audio_ + .lock() + .unwrap() + .extend_from_slice((*buffer).as_ref()); + let _ = sender.lock().unwrap().send(()); + })); + + let this = Trusted::new(self); + let global = self.global(); + let window = global.as_window(); + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + Builder::new() + .name("OfflineAudioContextResolver".to_owned()) + .spawn(move || { + let _ = receiver.recv(); + let _ = task_source.queue_with_canceller( + task!(resolve: move || { + let this = this.root(); + let processed_audio = processed_audio.lock().unwrap(); + let mut processed_audio: Vec<_> = processed_audio + .chunks(this.length as usize) + .map(|channel| channel.to_vec()) + .collect(); + // it can end up being empty if the task failed + if processed_audio.len() != this.length as usize { + processed_audio.resize(this.length as usize, Vec::new()) + } + let buffer = AudioBuffer::new( + &this.global().as_window(), + this.channel_count, + this.length, + *this.context.SampleRate(), + Some(processed_audio.as_slice())); + (*this.pending_rendering_promise.borrow_mut()).take().unwrap().resolve_native(&buffer); + let global = &this.global(); + let window = global.as_window(); + let event = OfflineAudioCompletionEvent::new(&window, + atom!("complete"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + &buffer); + event.upcast::<Event>().fire(this.upcast()); + }), + &canceller, + ); + }) + .unwrap(); + + if self + .context + .audio_context_impl() + .lock() + .unwrap() + .resume() + .is_err() + { + promise.reject_error(Error::Type("Could not start offline rendering".to_owned())); + } + + promise + } +} diff --git a/components/script/dom/offscreencanvas.rs b/components/script/dom/offscreencanvas.rs new file mode 100644 index 00000000000..b381c245cfa --- /dev/null +++ b/components/script/dom/offscreencanvas.rs @@ -0,0 +1,203 @@ +/* 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 crate::dom::bindings::cell::{ref_filter_map, DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::{ + OffscreenCanvasMethods, OffscreenRenderingContext, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlcanvaselement::HTMLCanvasElement; +use crate::dom::offscreencanvasrenderingcontext2d::OffscreenCanvasRenderingContext2D; +use crate::script_runtime::JSContext; +use canvas_traits::canvas::{CanvasMsg, FromScriptMsg}; +use dom_struct::dom_struct; +use euclid::default::Size2D; +use ipc_channel::ipc::IpcSharedMemory; +use js::rust::HandleValue; +use profile_traits::ipc; +use std::cell::Cell; + +#[unrooted_must_root_lint::must_root] +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub enum OffscreenCanvasContext { + OffscreenContext2d(Dom<OffscreenCanvasRenderingContext2D>), + //WebGL(Dom<WebGLRenderingContext>), + //WebGL2(Dom<WebGL2RenderingContext>), +} + +#[dom_struct] +pub struct OffscreenCanvas { + eventtarget: EventTarget, + width: Cell<u64>, + height: Cell<u64>, + context: DomRefCell<Option<OffscreenCanvasContext>>, + placeholder: Option<Dom<HTMLCanvasElement>>, +} + +impl OffscreenCanvas { + pub fn new_inherited( + width: u64, + height: u64, + placeholder: Option<&HTMLCanvasElement>, + ) -> OffscreenCanvas { + OffscreenCanvas { + eventtarget: EventTarget::new_inherited(), + width: Cell::new(width), + height: Cell::new(height), + context: DomRefCell::new(None), + placeholder: placeholder.map(Dom::from_ref), + } + } + + pub fn new( + global: &GlobalScope, + width: u64, + height: u64, + placeholder: Option<&HTMLCanvasElement>, + ) -> DomRoot<OffscreenCanvas> { + reflect_dom_object( + Box::new(OffscreenCanvas::new_inherited(width, height, placeholder)), + global, + ) + } + + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + width: u64, + height: u64, + ) -> Fallible<DomRoot<OffscreenCanvas>> { + let offscreencanvas = OffscreenCanvas::new(global, width, height, None); + Ok(offscreencanvas) + } + + pub fn get_size(&self) -> Size2D<u64> { + Size2D::new(self.Width(), self.Height()) + } + + pub fn origin_is_clean(&self) -> bool { + match *self.context.borrow() { + Some(OffscreenCanvasContext::OffscreenContext2d(ref context)) => { + context.origin_is_clean() + }, + _ => true, + } + } + + pub fn context(&self) -> Option<Ref<OffscreenCanvasContext>> { + ref_filter_map(self.context.borrow(), |ctx| ctx.as_ref()) + } + + pub fn fetch_all_data(&self) -> Option<(Option<IpcSharedMemory>, Size2D<u32>)> { + let size = self.get_size(); + + if size.width == 0 || size.height == 0 { + return None; + } + + let data = match self.context.borrow().as_ref() { + Some(&OffscreenCanvasContext::OffscreenContext2d(ref context)) => { + let (sender, receiver) = + ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); + let msg = CanvasMsg::FromScript( + FromScriptMsg::SendPixels(sender), + context.get_canvas_id(), + ); + context.get_ipc_renderer().send(msg).unwrap(); + + Some(receiver.recv().unwrap()) + }, + None => None, + }; + + Some((data, size.to_u32())) + } + + #[allow(unsafe_code)] + fn get_or_init_2d_context(&self) -> Option<DomRoot<OffscreenCanvasRenderingContext2D>> { + if let Some(ctx) = self.context() { + return match *ctx { + OffscreenCanvasContext::OffscreenContext2d(ref ctx) => Some(DomRoot::from_ref(ctx)), + }; + } + let context = OffscreenCanvasRenderingContext2D::new( + &self.global(), + self, + self.placeholder.as_ref().map(|c| &**c), + ); + *self.context.borrow_mut() = Some(OffscreenCanvasContext::OffscreenContext2d( + Dom::from_ref(&*context), + )); + Some(context) + } + + pub fn is_valid(&self) -> bool { + self.Width() != 0 && self.Height() != 0 + } +} + +impl OffscreenCanvasMethods for OffscreenCanvas { + // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-getcontext + fn GetContext( + &self, + _cx: JSContext, + id: DOMString, + _options: HandleValue, + ) -> Option<OffscreenRenderingContext> { + match &*id { + "2d" => self + .get_or_init_2d_context() + .map(OffscreenRenderingContext::OffscreenCanvasRenderingContext2D), + /*"webgl" | "experimental-webgl" => self + .get_or_init_webgl_context(cx, options) + .map(OffscreenRenderingContext::WebGLRenderingContext), + "webgl2" | "experimental-webgl2" => self + .get_or_init_webgl2_context(cx, options) + .map(OffscreenRenderingContext::WebGL2RenderingContext),*/ + _ => None, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width + fn Width(&self) -> u64 { + return self.width.get(); + } + + // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width + fn SetWidth(&self, value: u64) { + self.width.set(value); + + if let Some(canvas_context) = self.context() { + match &*canvas_context { + OffscreenCanvasContext::OffscreenContext2d(rendering_context) => { + rendering_context.set_canvas_bitmap_dimensions(self.get_size()); + }, + } + } + } + + // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height + fn Height(&self) -> u64 { + return self.height.get(); + } + + // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height + fn SetHeight(&self, value: u64) { + self.height.set(value); + + if let Some(canvas_context) = self.context() { + match &*canvas_context { + OffscreenCanvasContext::OffscreenContext2d(rendering_context) => { + rendering_context.set_canvas_bitmap_dimensions(self.get_size()); + }, + } + } + } +} diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs new file mode 100644 index 00000000000..8c3deb001f4 --- /dev/null +++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs @@ -0,0 +1,561 @@ +/* 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 crate::canvas_state::CanvasState; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasDirection; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextAlign; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextBaseline; +use crate::dom::bindings::codegen::Bindings::OffscreenCanvasRenderingContext2DBinding::OffscreenCanvasRenderingContext2DMethods; +use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::canvasgradient::CanvasGradient; +use crate::dom::canvaspattern::CanvasPattern; +use crate::dom::dommatrix::DOMMatrix; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlcanvaselement::HTMLCanvasElement; +use crate::dom::imagedata::ImageData; +use crate::dom::offscreencanvas::OffscreenCanvas; +use crate::dom::textmetrics::TextMetrics; +use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg}; +use dom_struct::dom_struct; +use euclid::default::Size2D; +use ipc_channel::ipc::IpcSender; + +#[dom_struct] +pub struct OffscreenCanvasRenderingContext2D { + reflector_: Reflector, + canvas: Dom<OffscreenCanvas>, + canvas_state: CanvasState, + htmlcanvas: Option<Dom<HTMLCanvasElement>>, +} + +impl OffscreenCanvasRenderingContext2D { + fn new_inherited( + global: &GlobalScope, + canvas: &OffscreenCanvas, + htmlcanvas: Option<&HTMLCanvasElement>, + ) -> OffscreenCanvasRenderingContext2D { + OffscreenCanvasRenderingContext2D { + reflector_: Reflector::new(), + canvas: Dom::from_ref(canvas), + htmlcanvas: htmlcanvas.map(Dom::from_ref), + canvas_state: CanvasState::new(global, canvas.get_size()), + } + } + + pub fn new( + global: &GlobalScope, + canvas: &OffscreenCanvas, + htmlcanvas: Option<&HTMLCanvasElement>, + ) -> DomRoot<OffscreenCanvasRenderingContext2D> { + let boxed = Box::new(OffscreenCanvasRenderingContext2D::new_inherited( + global, canvas, htmlcanvas, + )); + reflect_dom_object(boxed, global) + } + + pub fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) { + self.canvas_state.set_bitmap_dimensions(size); + } + + pub fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { + self.canvas_state.send_canvas_2d_msg(msg) + } + + pub fn origin_is_clean(&self) -> bool { + self.canvas_state.origin_is_clean() + } + + pub fn get_canvas_id(&self) -> CanvasId { + self.canvas_state.get_canvas_id() + } + + pub fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg> { + self.canvas_state.get_ipc_renderer().clone() + } +} + +impl OffscreenCanvasRenderingContext2DMethods for OffscreenCanvasRenderingContext2D { + // https://html.spec.whatwg.org/multipage/offscreencontext2d-canvas + fn Canvas(&self) -> DomRoot<OffscreenCanvas> { + DomRoot::from_ref(&self.canvas) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fillrect + fn FillRect(&self, x: f64, y: f64, width: f64, height: f64) { + self.canvas_state.fill_rect(x, y, width, height); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clearrect + fn ClearRect(&self, x: f64, y: f64, width: f64, height: f64) { + self.canvas_state.clear_rect(x, y, width, height); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokerect + fn StrokeRect(&self, x: f64, y: f64, width: f64, height: f64) { + self.canvas_state.stroke_rect(x, y, width, height); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx + fn ShadowOffsetX(&self) -> f64 { + self.canvas_state.shadow_offset_x() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx + fn SetShadowOffsetX(&self, value: f64) { + self.canvas_state.set_shadow_offset_x(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety + fn ShadowOffsetY(&self) -> f64 { + self.canvas_state.shadow_offset_y() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety + fn SetShadowOffsetY(&self, value: f64) { + self.canvas_state.set_shadow_offset_y(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur + fn ShadowBlur(&self) -> f64 { + self.canvas_state.shadow_blur() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur + fn SetShadowBlur(&self, value: f64) { + self.canvas_state.set_shadow_blur(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor + fn ShadowColor(&self) -> DOMString { + self.canvas_state.shadow_color() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor + fn SetShadowColor(&self, value: DOMString) { + self.canvas_state.set_shadow_color(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + fn StrokeStyle(&self) -> StringOrCanvasGradientOrCanvasPattern { + self.canvas_state.stroke_style() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + fn SetStrokeStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) { + self.canvas_state + .set_stroke_style(self.htmlcanvas.as_ref().map(|c| &**c), value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + fn FillStyle(&self) -> StringOrCanvasGradientOrCanvasPattern { + self.canvas_state.fill_style() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + fn SetFillStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) { + self.canvas_state + .set_fill_style(self.htmlcanvas.as_ref().map(|c| &**c), value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createlineargradient + fn CreateLinearGradient( + &self, + x0: Finite<f64>, + y0: Finite<f64>, + x1: Finite<f64>, + y1: Finite<f64>, + ) -> DomRoot<CanvasGradient> { + self.canvas_state + .create_linear_gradient(&self.global(), x0, y0, x1, y1) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createradialgradient + fn CreateRadialGradient( + &self, + x0: Finite<f64>, + y0: Finite<f64>, + r0: Finite<f64>, + x1: Finite<f64>, + y1: Finite<f64>, + r1: Finite<f64>, + ) -> Fallible<DomRoot<CanvasGradient>> { + self.canvas_state + .create_radial_gradient(&self.global(), x0, y0, r0, x1, y1, r1) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern + fn CreatePattern( + &self, + image: CanvasImageSource, + repetition: DOMString, + ) -> Fallible<Option<DomRoot<CanvasPattern>>> { + self.canvas_state + .create_pattern(&self.global(), image, repetition) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-save + fn Save(&self) { + self.canvas_state.save() + } + + #[allow(unrooted_must_root)] + // https://html.spec.whatwg.org/multipage/#dom-context-2d-restore + fn Restore(&self) { + self.canvas_state.restore() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha + fn GlobalAlpha(&self) -> f64 { + self.canvas_state.global_alpha() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha + fn SetGlobalAlpha(&self, alpha: f64) { + self.canvas_state.set_global_alpha(alpha) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation + fn GlobalCompositeOperation(&self) -> DOMString { + self.canvas_state.global_composite_operation() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation + fn SetGlobalCompositeOperation(&self, op_str: DOMString) { + self.canvas_state.set_global_composite_operation(op_str) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled + fn ImageSmoothingEnabled(&self) -> bool { + self.canvas_state.image_smoothing_enabled() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled + fn SetImageSmoothingEnabled(&self, value: bool) { + self.canvas_state.set_image_smoothing_enabled(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext + fn FillText(&self, text: DOMString, x: f64, y: f64, max_width: Option<f64>) { + self.canvas_state.fill_text( + self.htmlcanvas.as_ref().map(|c| &**c), + text, + x, + y, + max_width, + ) + } + + // https://html.spec.whatwg.org/multipage/#textmetrics + fn MeasureText(&self, text: DOMString) -> DomRoot<TextMetrics> { + self.canvas_state.measure_text(&self.global(), text) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-font + fn Font(&self) -> DOMString { + self.canvas_state.font() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-font + fn SetFont(&self, value: DOMString) { + self.canvas_state + .set_font(self.htmlcanvas.as_ref().map(|c| &**c), value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + fn TextAlign(&self) -> CanvasTextAlign { + self.canvas_state.text_align() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + fn SetTextAlign(&self, value: CanvasTextAlign) { + self.canvas_state.set_text_align(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textbaseline + fn TextBaseline(&self) -> CanvasTextBaseline { + self.canvas_state.text_baseline() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textbaseline + fn SetTextBaseline(&self, value: CanvasTextBaseline) { + self.canvas_state.set_text_baseline(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + fn Direction(&self) -> CanvasDirection { + self.canvas_state.direction() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + fn SetDirection(&self, value: CanvasDirection) { + self.canvas_state.set_direction(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth + fn LineWidth(&self) -> f64 { + self.canvas_state.line_width() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth + fn SetLineWidth(&self, width: f64) { + self.canvas_state.set_line_width(width) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap + fn LineCap(&self) -> CanvasLineCap { + self.canvas_state.line_cap() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap + fn SetLineCap(&self, cap: CanvasLineCap) { + self.canvas_state.set_line_cap(cap) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin + fn LineJoin(&self) -> CanvasLineJoin { + self.canvas_state.line_join() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin + fn SetLineJoin(&self, join: CanvasLineJoin) { + self.canvas_state.set_line_join(join) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit + fn MiterLimit(&self) -> f64 { + self.canvas_state.miter_limit() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit + fn SetMiterLimit(&self, limit: f64) { + self.canvas_state.set_miter_limit(limit) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata + fn CreateImageData(&self, sw: i32, sh: i32) -> Fallible<DomRoot<ImageData>> { + self.canvas_state.create_image_data(&self.global(), sw, sh) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata + fn CreateImageData_(&self, imagedata: &ImageData) -> Fallible<DomRoot<ImageData>> { + self.canvas_state + .create_image_data_(&self.global(), imagedata) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-getimagedata + fn GetImageData(&self, sx: i32, sy: i32, sw: i32, sh: i32) -> Fallible<DomRoot<ImageData>> { + self.canvas_state + .get_image_data(self.canvas.get_size(), &self.global(), sx, sy, sw, sh) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata + fn PutImageData(&self, imagedata: &ImageData, dx: i32, dy: i32) { + self.canvas_state + .put_image_data(self.canvas.get_size(), imagedata, dx, dy) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata + #[allow(unsafe_code)] + fn PutImageData_( + &self, + imagedata: &ImageData, + dx: i32, + dy: i32, + dirty_x: i32, + dirty_y: i32, + dirty_width: i32, + dirty_height: i32, + ) { + self.canvas_state.put_image_data_( + self.canvas.get_size(), + imagedata, + dx, + dy, + dirty_x, + dirty_y, + dirty_width, + dirty_height, + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + fn DrawImage(&self, image: CanvasImageSource, dx: f64, dy: f64) -> ErrorResult { + self.canvas_state + .draw_image(self.htmlcanvas.as_ref().map(|c| &**c), image, dx, dy) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + fn DrawImage_( + &self, + image: CanvasImageSource, + dx: f64, + dy: f64, + dw: f64, + dh: f64, + ) -> ErrorResult { + self.canvas_state.draw_image_( + self.htmlcanvas.as_ref().map(|c| &**c), + image, + dx, + dy, + dw, + dh, + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + fn DrawImage__( + &self, + image: CanvasImageSource, + sx: f64, + sy: f64, + sw: f64, + sh: f64, + dx: f64, + dy: f64, + dw: f64, + dh: f64, + ) -> ErrorResult { + self.canvas_state.draw_image__( + self.htmlcanvas.as_ref().map(|c| &**c), + image, + sx, + sy, + sw, + sh, + dx, + dy, + dw, + dh, + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-beginpath + fn BeginPath(&self) { + self.canvas_state.begin_path() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill + fn Fill(&self, fill_rule: CanvasFillRule) { + self.canvas_state.fill(fill_rule) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke + fn Stroke(&self) { + self.canvas_state.stroke() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip + fn Clip(&self, fill_rule: CanvasFillRule) { + self.canvas_state.clip(fill_rule) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath + fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool { + self.canvas_state + .is_point_in_path(&self.global(), x, y, fill_rule) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-scale + fn Scale(&self, x: f64, y: f64) { + self.canvas_state.scale(x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-rotate + fn Rotate(&self, angle: f64) { + self.canvas_state.rotate(angle) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-translate + fn Translate(&self, x: f64, y: f64) { + self.canvas_state.translate(x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-transform + fn Transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) { + self.canvas_state.transform(a, b, c, d, e, f) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-gettransform + fn GetTransform(&self) -> DomRoot<DOMMatrix> { + self.canvas_state.get_transform(&self.global()) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-settransform + fn SetTransform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) { + self.canvas_state.set_transform(a, b, c, d, e, f) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-resettransform + fn ResetTransform(&self) { + self.canvas_state.reset_transform() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath + fn ClosePath(&self) { + self.canvas_state.close_path() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto + fn MoveTo(&self, x: f64, y: f64) { + self.canvas_state.move_to(x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto + fn LineTo(&self, x: f64, y: f64) { + self.canvas_state.line_to(x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-rect + fn Rect(&self, x: f64, y: f64, width: f64, height: f64) { + self.canvas_state.rect(x, y, width, height) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto + fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) { + self.canvas_state.quadratic_curve_to(cpx, cpy, x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto + fn BezierCurveTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) { + self.canvas_state + .bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-arc + fn Arc(&self, x: f64, y: f64, r: f64, start: f64, end: f64, ccw: bool) -> ErrorResult { + self.canvas_state.arc(x, y, r, start, end, ccw) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto + fn ArcTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, r: f64) -> ErrorResult { + self.canvas_state.arc_to(cp1x, cp1y, cp2x, cp2y, r) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse + fn Ellipse( + &self, + x: f64, + y: f64, + rx: f64, + ry: f64, + rotation: f64, + start: f64, + end: f64, + ccw: bool, + ) -> ErrorResult { + self.canvas_state + .ellipse(x, y, rx, ry, rotation, start, end, ccw) + } +} diff --git a/components/script/dom/oscillatornode.rs b/components/script/dom/oscillatornode.rs new file mode 100644 index 00000000000..948603ca6e2 --- /dev/null +++ b/components/script/dom/oscillatornode.rs @@ -0,0 +1,156 @@ +/* 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 crate::dom::audioparam::AudioParam; +use crate::dom::audioscheduledsourcenode::AudioScheduledSourceNode; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate; +use crate::dom::bindings::codegen::Bindings::OscillatorNodeBinding::OscillatorNodeMethods; +use crate::dom::bindings::codegen::Bindings::OscillatorNodeBinding::{ + OscillatorOptions, OscillatorType, +}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage}; +use servo_media::audio::oscillator_node::OscillatorNodeMessage; +use servo_media::audio::oscillator_node::OscillatorNodeOptions as ServoMediaOscillatorOptions; +use servo_media::audio::oscillator_node::OscillatorType as ServoMediaOscillatorType; +use servo_media::audio::param::ParamType; +use std::cell::Cell; +use std::f32; + +#[dom_struct] +pub struct OscillatorNode { + source_node: AudioScheduledSourceNode, + detune: Dom<AudioParam>, + frequency: Dom<AudioParam>, + oscillator_type: Cell<OscillatorType>, +} + +impl OscillatorNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + window: &Window, + context: &BaseAudioContext, + options: &OscillatorOptions, + ) -> Fallible<OscillatorNode> { + let node_options = + options + .parent + .unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers); + let source_node = AudioScheduledSourceNode::new_inherited( + AudioNodeInit::OscillatorNode(options.into()), + context, + node_options, + 0, /* inputs */ + 1, /* outputs */ + )?; + let node_id = source_node.node().node_id(); + let frequency = AudioParam::new( + window, + context, + node_id, + ParamType::Frequency, + AutomationRate::A_rate, + 440., + f32::MIN, + f32::MAX, + ); + let detune = AudioParam::new( + window, + context, + node_id, + ParamType::Detune, + AutomationRate::A_rate, + 0., + -440. / 2., + 440. / 2., + ); + Ok(OscillatorNode { + source_node, + oscillator_type: Cell::new(options.type_), + frequency: Dom::from_ref(&frequency), + detune: Dom::from_ref(&detune), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &OscillatorOptions, + ) -> Fallible<DomRoot<OscillatorNode>> { + let node = OscillatorNode::new_inherited(window, context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &OscillatorOptions, + ) -> Fallible<DomRoot<OscillatorNode>> { + OscillatorNode::new(window, context, options) + } +} + +impl OscillatorNodeMethods for OscillatorNode { + // https://webaudio.github.io/web-audio-api/#dom-oscillatornode-frequency + fn Frequency(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.frequency) + } + + // https://webaudio.github.io/web-audio-api/#dom-oscillatornode-detune + fn Detune(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.detune) + } + + // https://webaudio.github.io/web-audio-api/#dom-oscillatornode-type + fn Type(&self) -> OscillatorType { + self.oscillator_type.get() + } + + // https://webaudio.github.io/web-audio-api/#dom-oscillatornode-type + fn SetType(&self, type_: OscillatorType) -> ErrorResult { + if type_ == OscillatorType::Custom { + return Err(Error::InvalidState); + } + self.oscillator_type.set(type_); + self.source_node + .node() + .message(AudioNodeMessage::OscillatorNode( + OscillatorNodeMessage::SetOscillatorType(type_.into()), + )); + return Ok(()); + } +} + +impl<'a> From<&'a OscillatorOptions> for ServoMediaOscillatorOptions { + fn from(options: &'a OscillatorOptions) -> Self { + Self { + oscillator_type: options.type_.into(), + freq: *options.frequency, + detune: *options.detune, + periodic_wave_options: None, // XXX + } + } +} + +impl From<OscillatorType> for ServoMediaOscillatorType { + fn from(oscillator_type: OscillatorType) -> Self { + match oscillator_type { + OscillatorType::Sine => ServoMediaOscillatorType::Sine, + OscillatorType::Square => ServoMediaOscillatorType::Square, + OscillatorType::Sawtooth => ServoMediaOscillatorType::Sawtooth, + OscillatorType::Triangle => ServoMediaOscillatorType::Triangle, + OscillatorType::Custom => ServoMediaOscillatorType::Custom, + } + } +} diff --git a/components/script/dom/pagetransitionevent.rs b/components/script/dom/pagetransitionevent.rs index 3757bece84a..5d11fb62540 100644 --- a/components/script/dom/pagetransitionevent.rs +++ b/components/script/dom/pagetransitionevent.rs @@ -1,17 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::PageTransitionEventBinding; -use dom::bindings::codegen::Bindings::PageTransitionEventBinding::PageTransitionEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::PageTransitionEventBinding; +use crate::dom::bindings::codegen::Bindings::PageTransitionEventBinding::PageTransitionEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; use std::cell::Cell; @@ -31,18 +31,17 @@ impl PageTransitionEvent { } } - pub fn new_uninitialized(window: &Window) -> Root<PageTransitionEvent> { - reflect_dom_object(box PageTransitionEvent::new_inherited(), - window, - PageTransitionEventBinding::Wrap) + pub fn new_uninitialized(window: &Window) -> DomRoot<PageTransitionEvent> { + reflect_dom_object(Box::new(PageTransitionEvent::new_inherited()), window) } - pub fn new(window: &Window, - type_: Atom, - bubbles: bool, - cancelable: bool, - persisted: bool) - -> Root<PageTransitionEvent> { + pub fn new( + window: &Window, + type_: Atom, + bubbles: bool, + cancelable: bool, + persisted: bool, + ) -> DomRoot<PageTransitionEvent> { let ev = PageTransitionEvent::new_uninitialized(window); ev.persisted.set(persisted); { @@ -52,15 +51,19 @@ impl PageTransitionEvent { ev } - pub fn Constructor(window: &Window, - type_: DOMString, - init: &PageTransitionEventBinding::PageTransitionEventInit) - -> Fallible<Root<PageTransitionEvent>> { - Ok(PageTransitionEvent::new(window, - Atom::from(type_), - init.parent.bubbles, - init.parent.cancelable, - init.persisted)) + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &PageTransitionEventBinding::PageTransitionEventInit, + ) -> Fallible<DomRoot<PageTransitionEvent>> { + Ok(PageTransitionEvent::new( + window, + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + init.persisted, + )) } } diff --git a/components/script/dom/paintrenderingcontext2d.rs b/components/script/dom/paintrenderingcontext2d.rs new file mode 100644 index 00000000000..af9f5cd01b3 --- /dev/null +++ b/components/script/dom/paintrenderingcontext2d.rs @@ -0,0 +1,429 @@ +/* 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 crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods; +use crate::dom::bindings::codegen::Bindings::PaintRenderingContext2DBinding::PaintRenderingContext2DMethods; +use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::canvasgradient::CanvasGradient; +use crate::dom::canvaspattern::CanvasPattern; +use crate::dom::canvasrenderingcontext2d::CanvasRenderingContext2D; +use crate::dom::dommatrix::DOMMatrix; +use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; +use canvas_traits::canvas::CanvasImageData; +use canvas_traits::canvas::CanvasMsg; +use canvas_traits::canvas::FromLayoutMsg; +use dom_struct::dom_struct; +use euclid::{Scale, Size2D}; +use ipc_channel::ipc::IpcSender; +use servo_url::ServoUrl; +use std::cell::Cell; +use style_traits::CSSPixel; +use style_traits::DevicePixel; + +#[dom_struct] +pub struct PaintRenderingContext2D { + context: CanvasRenderingContext2D, + device_pixel_ratio: Cell<Scale<f32, CSSPixel, DevicePixel>>, +} + +impl PaintRenderingContext2D { + fn new_inherited(global: &PaintWorkletGlobalScope) -> PaintRenderingContext2D { + let size = Size2D::zero(); + PaintRenderingContext2D { + context: CanvasRenderingContext2D::new_inherited(global.upcast(), None, size), + device_pixel_ratio: Cell::new(Scale::new(1.0)), + } + } + + pub fn new(global: &PaintWorkletGlobalScope) -> DomRoot<PaintRenderingContext2D> { + reflect_dom_object( + Box::new(PaintRenderingContext2D::new_inherited(global)), + global, + ) + } + + pub fn send_data(&self, sender: IpcSender<CanvasImageData>) { + let msg = CanvasMsg::FromLayout( + FromLayoutMsg::SendData(sender), + self.context.get_canvas_id(), + ); + let _ = self.context.get_ipc_renderer().send(msg); + } + + pub fn take_missing_image_urls(&self) -> Vec<ServoUrl> { + self.context.take_missing_image_urls() + } + + pub fn set_bitmap_dimensions( + &self, + size: Size2D<f32, CSSPixel>, + device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>, + ) { + let size = size * device_pixel_ratio; + self.device_pixel_ratio.set(device_pixel_ratio); + self.context + .set_canvas_bitmap_dimensions(size.to_untyped().to_u64()); + self.scale_by_device_pixel_ratio(); + } + + fn scale_by_device_pixel_ratio(&self) { + let device_pixel_ratio = self.device_pixel_ratio.get().get() as f64; + if device_pixel_ratio != 1.0 { + self.Scale(device_pixel_ratio, device_pixel_ratio); + } + } +} + +impl PaintRenderingContext2DMethods for PaintRenderingContext2D { + // https://html.spec.whatwg.org/multipage/#dom-context-2d-save + fn Save(&self) { + self.context.Save() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-restore + fn Restore(&self) { + self.context.Restore() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-scale + fn Scale(&self, x: f64, y: f64) { + self.context.Scale(x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-rotate + fn Rotate(&self, angle: f64) { + self.context.Rotate(angle) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-translate + fn Translate(&self, x: f64, y: f64) { + self.context.Translate(x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-transform + fn Transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) { + self.context.Transform(a, b, c, d, e, f) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-gettransform + fn GetTransform(&self) -> DomRoot<DOMMatrix> { + self.context.GetTransform() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-settransform + fn SetTransform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) { + self.context.SetTransform(a, b, c, d, e, f); + self.scale_by_device_pixel_ratio(); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-resettransform + fn ResetTransform(&self) { + self.context.ResetTransform(); + self.scale_by_device_pixel_ratio(); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha + fn GlobalAlpha(&self) -> f64 { + self.context.GlobalAlpha() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha + fn SetGlobalAlpha(&self, alpha: f64) { + self.context.SetGlobalAlpha(alpha) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation + fn GlobalCompositeOperation(&self) -> DOMString { + self.context.GlobalCompositeOperation() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation + fn SetGlobalCompositeOperation(&self, op_str: DOMString) { + self.context.SetGlobalCompositeOperation(op_str) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fillrect + fn FillRect(&self, x: f64, y: f64, width: f64, height: f64) { + self.context.FillRect(x, y, width, height) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clearrect + fn ClearRect(&self, x: f64, y: f64, width: f64, height: f64) { + self.context.ClearRect(x, y, width, height) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokerect + fn StrokeRect(&self, x: f64, y: f64, width: f64, height: f64) { + self.context.StrokeRect(x, y, width, height) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-beginpath + fn BeginPath(&self) { + self.context.BeginPath() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath + fn ClosePath(&self) { + self.context.ClosePath() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill + fn Fill(&self, fill_rule: CanvasFillRule) { + self.context.Fill(fill_rule) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke + fn Stroke(&self) { + self.context.Stroke() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip + fn Clip(&self, fill_rule: CanvasFillRule) { + self.context.Clip(fill_rule) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath + fn IsPointInPath(&self, x: f64, y: f64, fill_rule: CanvasFillRule) -> bool { + self.context.IsPointInPath(x, y, fill_rule) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + fn DrawImage(&self, image: CanvasImageSource, dx: f64, dy: f64) -> ErrorResult { + self.context.DrawImage(image, dx, dy) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + fn DrawImage_( + &self, + image: CanvasImageSource, + dx: f64, + dy: f64, + dw: f64, + dh: f64, + ) -> ErrorResult { + self.context.DrawImage_(image, dx, dy, dw, dh) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + fn DrawImage__( + &self, + image: CanvasImageSource, + sx: f64, + sy: f64, + sw: f64, + sh: f64, + dx: f64, + dy: f64, + dw: f64, + dh: f64, + ) -> ErrorResult { + self.context + .DrawImage__(image, sx, sy, sw, sh, dx, dy, dw, dh) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto + fn MoveTo(&self, x: f64, y: f64) { + self.context.MoveTo(x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto + fn LineTo(&self, x: f64, y: f64) { + self.context.LineTo(x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-rect + fn Rect(&self, x: f64, y: f64, width: f64, height: f64) { + self.context.Rect(x, y, width, height) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto + fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) { + self.context.QuadraticCurveTo(cpx, cpy, x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto + fn BezierCurveTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) { + self.context.BezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-arc + fn Arc(&self, x: f64, y: f64, r: f64, start: f64, end: f64, ccw: bool) -> ErrorResult { + self.context.Arc(x, y, r, start, end, ccw) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto + fn ArcTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, r: f64) -> ErrorResult { + self.context.ArcTo(cp1x, cp1y, cp2x, cp2y, r) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse + fn Ellipse( + &self, + x: f64, + y: f64, + rx: f64, + ry: f64, + rotation: f64, + start: f64, + end: f64, + ccw: bool, + ) -> ErrorResult { + self.context + .Ellipse(x, y, rx, ry, rotation, start, end, ccw) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled + fn ImageSmoothingEnabled(&self) -> bool { + self.context.ImageSmoothingEnabled() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled + fn SetImageSmoothingEnabled(&self, value: bool) { + self.context.SetImageSmoothingEnabled(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + fn StrokeStyle(&self) -> StringOrCanvasGradientOrCanvasPattern { + self.context.StrokeStyle() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + fn SetStrokeStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) { + self.context.SetStrokeStyle(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + fn FillStyle(&self) -> StringOrCanvasGradientOrCanvasPattern { + self.context.FillStyle() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + fn SetFillStyle(&self, value: StringOrCanvasGradientOrCanvasPattern) { + self.context.SetFillStyle(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createlineargradient + fn CreateLinearGradient( + &self, + x0: Finite<f64>, + y0: Finite<f64>, + x1: Finite<f64>, + y1: Finite<f64>, + ) -> DomRoot<CanvasGradient> { + self.context.CreateLinearGradient(x0, y0, x1, y1) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createradialgradient + fn CreateRadialGradient( + &self, + x0: Finite<f64>, + y0: Finite<f64>, + r0: Finite<f64>, + x1: Finite<f64>, + y1: Finite<f64>, + r1: Finite<f64>, + ) -> Fallible<DomRoot<CanvasGradient>> { + self.context.CreateRadialGradient(x0, y0, r0, x1, y1, r1) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern + fn CreatePattern( + &self, + image: CanvasImageSource, + repetition: DOMString, + ) -> Fallible<Option<DomRoot<CanvasPattern>>> { + self.context.CreatePattern(image, repetition) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth + fn LineWidth(&self) -> f64 { + self.context.LineWidth() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth + fn SetLineWidth(&self, width: f64) { + self.context.SetLineWidth(width) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap + fn LineCap(&self) -> CanvasLineCap { + self.context.LineCap() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap + fn SetLineCap(&self, cap: CanvasLineCap) { + self.context.SetLineCap(cap) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin + fn LineJoin(&self) -> CanvasLineJoin { + self.context.LineJoin() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin + fn SetLineJoin(&self, join: CanvasLineJoin) { + self.context.SetLineJoin(join) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit + fn MiterLimit(&self) -> f64 { + self.context.MiterLimit() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit + fn SetMiterLimit(&self, limit: f64) { + self.context.SetMiterLimit(limit) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx + fn ShadowOffsetX(&self) -> f64 { + self.context.ShadowOffsetX() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx + fn SetShadowOffsetX(&self, value: f64) { + self.context.SetShadowOffsetX(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety + fn ShadowOffsetY(&self) -> f64 { + self.context.ShadowOffsetY() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety + fn SetShadowOffsetY(&self, value: f64) { + self.context.SetShadowOffsetY(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur + fn ShadowBlur(&self) -> f64 { + self.context.ShadowBlur() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur + fn SetShadowBlur(&self, value: f64) { + self.context.SetShadowBlur(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor + fn ShadowColor(&self) -> DOMString { + self.context.ShadowColor() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor + fn SetShadowColor(&self, value: DOMString) { + self.context.SetShadowColor(value) + } +} diff --git a/components/script/dom/paintsize.rs b/components/script/dom/paintsize.rs new file mode 100644 index 00000000000..5f6d0260096 --- /dev/null +++ b/components/script/dom/paintsize.rs @@ -0,0 +1,49 @@ +/* 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 crate::dom::bindings::codegen::Bindings::PaintSizeBinding::PaintSizeMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::DomRoot; +use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; +use dom_struct::dom_struct; +use euclid::Size2D; +use style_traits::CSSPixel; + +#[dom_struct] +pub struct PaintSize { + reflector: Reflector, + width: Finite<f64>, + height: Finite<f64>, +} + +impl PaintSize { + fn new_inherited(size: Size2D<f32, CSSPixel>) -> PaintSize { + PaintSize { + reflector: Reflector::new(), + width: Finite::wrap(size.width as f64), + height: Finite::wrap(size.height as f64), + } + } + + pub fn new( + global: &PaintWorkletGlobalScope, + size: Size2D<f32, CSSPixel>, + ) -> DomRoot<PaintSize> { + reflect_dom_object(Box::new(PaintSize::new_inherited(size)), global) + } +} + +impl PaintSizeMethods for PaintSize { + /// <https://drafts.css-houdini.org/css-paint-api/#paintsize> + fn Width(&self) -> Finite<f64> { + self.width + } + + /// <https://drafts.css-houdini.org/css-paint-api/#paintsize> + fn Height(&self) -> Finite<f64> { + self.height + } +} diff --git a/components/script/dom/paintworkletglobalscope.rs b/components/script/dom/paintworkletglobalscope.rs new file mode 100644 index 00000000000..888a6b16574 --- /dev/null +++ b/components/script/dom/paintworkletglobalscope.rs @@ -0,0 +1,618 @@ +/* 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 crate::dom::bindings::callback::CallbackContainer; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding; +use crate::dom::bindings::codegen::Bindings::PaintWorkletGlobalScopeBinding::PaintWorkletGlobalScopeMethods; +use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; +use crate::dom::bindings::conversions::get_property; +use crate::dom::bindings::conversions::get_property_jsval; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssstylevalue::CSSStyleValue; +use crate::dom::paintrenderingcontext2d::PaintRenderingContext2D; +use crate::dom::paintsize::PaintSize; +use crate::dom::stylepropertymapreadonly::StylePropertyMapReadOnly; +use crate::dom::worklet::WorkletExecutor; +use crate::dom::workletglobalscope::WorkletGlobalScope; +use crate::dom::workletglobalscope::WorkletGlobalScopeInit; +use crate::dom::workletglobalscope::WorkletTask; +use crate::script_runtime::JSContext; +use crossbeam_channel::{unbounded, Sender}; +use dom_struct::dom_struct; +use euclid::Scale; +use euclid::Size2D; +use js::jsapi::HandleValueArray; +use js::jsapi::Heap; +use js::jsapi::IsCallable; +use js::jsapi::IsConstructor; +use js::jsapi::JSAutoRealm; +use js::jsapi::JSObject; +use js::jsapi::JS_ClearPendingException; +use js::jsapi::JS_IsExceptionPending; +use js::jsapi::NewArrayObject; +use js::jsval::JSVal; +use js::jsval::ObjectValue; +use js::jsval::UndefinedValue; +use js::rust::wrappers::Call; +use js::rust::wrappers::Construct1; +use js::rust::HandleValue; +use js::rust::Runtime; +use msg::constellation_msg::PipelineId; +use net_traits::image_cache::ImageCache; +use pixels::PixelFormat; +use profile_traits::ipc; +use script_traits::Painter; +use script_traits::{DrawAPaintImageResult, PaintWorkletError}; +use servo_atoms::Atom; +use servo_config::pref; +use servo_url::ServoUrl; +use std::cell::Cell; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::ptr::null_mut; +use std::rc::Rc; +use std::sync::Arc; +use std::sync::Mutex; +use std::thread; +use std::time::Duration; +use style_traits::CSSPixel; +use style_traits::DevicePixel; +use style_traits::SpeculativePainter; + +/// <https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope> +#[dom_struct] +pub struct PaintWorkletGlobalScope { + /// The worklet global for this object + worklet_global: WorkletGlobalScope, + /// The image cache + #[ignore_malloc_size_of = "Arc"] + image_cache: Arc<dyn ImageCache>, + /// <https://drafts.css-houdini.org/css-paint-api/#paint-definitions> + paint_definitions: DomRefCell<HashMap<Atom, Box<PaintDefinition>>>, + /// <https://drafts.css-houdini.org/css-paint-api/#paint-class-instances> + #[ignore_malloc_size_of = "mozjs"] + paint_class_instances: DomRefCell<HashMap<Atom, Box<Heap<JSVal>>>>, + /// The most recent name the worklet was called with + cached_name: DomRefCell<Atom>, + /// The most recent size the worklet was drawn at + cached_size: Cell<Size2D<f32, CSSPixel>>, + /// The most recent device pixel ratio the worklet was drawn at + cached_device_pixel_ratio: Cell<Scale<f32, CSSPixel, DevicePixel>>, + /// The most recent properties the worklet was drawn at + cached_properties: DomRefCell<Vec<(Atom, String)>>, + /// The most recent arguments the worklet was drawn at + cached_arguments: DomRefCell<Vec<String>>, + /// The most recent result + cached_result: DomRefCell<DrawAPaintImageResult>, +} + +impl PaintWorkletGlobalScope { + #[allow(unsafe_code)] + pub fn new( + runtime: &Runtime, + pipeline_id: PipelineId, + base_url: ServoUrl, + executor: WorkletExecutor, + init: &WorkletGlobalScopeInit, + ) -> DomRoot<PaintWorkletGlobalScope> { + debug!( + "Creating paint worklet global scope for pipeline {}.", + pipeline_id + ); + let global = Box::new(PaintWorkletGlobalScope { + worklet_global: WorkletGlobalScope::new_inherited( + pipeline_id, + base_url, + executor, + init, + ), + image_cache: init.image_cache.clone(), + paint_definitions: Default::default(), + paint_class_instances: Default::default(), + cached_name: DomRefCell::new(Atom::from("")), + cached_size: Cell::new(Size2D::zero()), + cached_device_pixel_ratio: Cell::new(Scale::new(1.0)), + cached_properties: Default::default(), + cached_arguments: Default::default(), + cached_result: DomRefCell::new(DrawAPaintImageResult { + width: 0, + height: 0, + format: PixelFormat::BGRA8, + image_key: None, + missing_image_urls: Vec::new(), + }), + }); + unsafe { PaintWorkletGlobalScopeBinding::Wrap(JSContext::from_ptr(runtime.cx()), global) } + } + + pub fn image_cache(&self) -> Arc<dyn ImageCache> { + self.image_cache.clone() + } + + pub fn perform_a_worklet_task(&self, task: PaintWorkletTask) { + match task { + PaintWorkletTask::DrawAPaintImage( + name, + size, + device_pixel_ratio, + properties, + arguments, + sender, + ) => { + let cache_hit = (&*self.cached_name.borrow() == &name) && + (self.cached_size.get() == size) && + (self.cached_device_pixel_ratio.get() == device_pixel_ratio) && + (&*self.cached_properties.borrow() == &properties) && + (&*self.cached_arguments.borrow() == &arguments); + let result = if cache_hit { + debug!("Cache hit on paint worklet {}!", name); + self.cached_result.borrow().clone() + } else { + debug!("Cache miss on paint worklet {}!", name); + let map = StylePropertyMapReadOnly::from_iter( + self.upcast(), + properties.iter().cloned(), + ); + let result = self.draw_a_paint_image( + &name, + size, + device_pixel_ratio, + &*map, + &*arguments, + ); + if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) { + *self.cached_name.borrow_mut() = name; + self.cached_size.set(size); + self.cached_device_pixel_ratio.set(device_pixel_ratio); + *self.cached_properties.borrow_mut() = properties; + *self.cached_arguments.borrow_mut() = arguments; + *self.cached_result.borrow_mut() = result.clone(); + } + result + }; + let _ = sender.send(result); + }, + PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments) => { + let should_speculate = (&*self.cached_name.borrow() != &name) || + (&*self.cached_properties.borrow() != &properties) || + (&*self.cached_arguments.borrow() != &arguments); + if should_speculate { + let size = self.cached_size.get(); + let device_pixel_ratio = self.cached_device_pixel_ratio.get(); + let map = StylePropertyMapReadOnly::from_iter( + self.upcast(), + properties.iter().cloned(), + ); + let result = self.draw_a_paint_image( + &name, + size, + device_pixel_ratio, + &*map, + &*arguments, + ); + if (result.image_key.is_some()) && (result.missing_image_urls.is_empty()) { + *self.cached_name.borrow_mut() = name; + *self.cached_properties.borrow_mut() = properties; + *self.cached_arguments.borrow_mut() = arguments; + *self.cached_result.borrow_mut() = result; + } + } + }, + } + } + + /// <https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image> + fn draw_a_paint_image( + &self, + name: &Atom, + size_in_px: Size2D<f32, CSSPixel>, + device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>, + properties: &StylePropertyMapReadOnly, + arguments: &[String], + ) -> DrawAPaintImageResult { + let size_in_dpx = size_in_px * device_pixel_ratio; + let size_in_dpx = Size2D::new( + size_in_dpx.width.abs() as u32, + size_in_dpx.height.abs() as u32, + ); + + // TODO: Steps 1-5. + + // TODO: document paint definitions. + self.invoke_a_paint_callback( + name, + size_in_px, + size_in_dpx, + device_pixel_ratio, + properties, + arguments, + ) + } + + /// <https://drafts.css-houdini.org/css-paint-api/#invoke-a-paint-callback> + #[allow(unsafe_code)] + fn invoke_a_paint_callback( + &self, + name: &Atom, + size_in_px: Size2D<f32, CSSPixel>, + size_in_dpx: Size2D<u32, DevicePixel>, + device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>, + properties: &StylePropertyMapReadOnly, + arguments: &[String], + ) -> DrawAPaintImageResult { + debug!( + "Invoking a paint callback {}({},{}) at {}.", + name, size_in_px.width, size_in_px.height, device_pixel_ratio + ); + + let cx = self.worklet_global.get_cx(); + let _ac = JSAutoRealm::new(*cx, self.worklet_global.reflector().get_jsobject().get()); + + // TODO: Steps 1-2.1. + // Step 2.2-5.1. + rooted!(in(*cx) let mut class_constructor = UndefinedValue()); + rooted!(in(*cx) let mut paint_function = UndefinedValue()); + let rendering_context = match self.paint_definitions.borrow().get(name) { + None => { + // Step 2.2. + warn!("Drawing un-registered paint definition {}.", name); + return self.invalid_image(size_in_dpx, vec![]); + }, + Some(definition) => { + // Step 5.1 + if !definition.constructor_valid_flag.get() { + debug!("Drawing invalid paint definition {}.", name); + return self.invalid_image(size_in_dpx, vec![]); + } + class_constructor.set(definition.class_constructor.get()); + paint_function.set(definition.paint_function.get()); + DomRoot::from_ref(&*definition.context) + }, + }; + + // Steps 5.2-5.4 + // TODO: the spec requires calling the constructor now, but we might want to + // prepopulate the paint instance in `RegisterPaint`, to avoid calling it in + // the primary worklet thread. + // https://github.com/servo/servo/issues/17377 + rooted!(in(*cx) let mut paint_instance = UndefinedValue()); + match self.paint_class_instances.borrow_mut().entry(name.clone()) { + Entry::Occupied(entry) => paint_instance.set(entry.get().get()), + Entry::Vacant(entry) => { + // Step 5.2-5.3 + let args = HandleValueArray::new(); + rooted!(in(*cx) let mut result = null_mut::<JSObject>()); + unsafe { + Construct1(*cx, class_constructor.handle(), &args, result.handle_mut()); + } + paint_instance.set(ObjectValue(result.get())); + if unsafe { JS_IsExceptionPending(*cx) } { + debug!("Paint constructor threw an exception {}.", name); + unsafe { + JS_ClearPendingException(*cx); + } + self.paint_definitions + .borrow_mut() + .get_mut(name) + .expect("Vanishing paint definition.") + .constructor_valid_flag + .set(false); + return self.invalid_image(size_in_dpx, vec![]); + } + // Step 5.4 + entry + .insert(Box::new(Heap::default())) + .set(paint_instance.get()); + }, + }; + + // TODO: Steps 6-7 + // Step 8 + // TODO: the spec requires creating a new paint rendering context each time, + // this code recycles the same one. + rendering_context.set_bitmap_dimensions(size_in_px, device_pixel_ratio); + + // Step 9 + let paint_size = PaintSize::new(self, size_in_px); + + // TODO: Step 10 + // Steps 11-12 + debug!("Invoking paint function {}.", name); + rooted_vec!(let arguments_values <- arguments.iter().cloned() + .map(|argument| CSSStyleValue::new(self.upcast(), argument))); + let arguments_value_vec: Vec<JSVal> = arguments_values + .iter() + .map(|argument| ObjectValue(argument.reflector().get_jsobject().get())) + .collect(); + let arguments_value_array = + unsafe { HandleValueArray::from_rooted_slice(&*arguments_value_vec) }; + rooted!(in(*cx) let argument_object = unsafe { NewArrayObject(*cx, &arguments_value_array) }); + + let args_slice = [ + ObjectValue(rendering_context.reflector().get_jsobject().get()), + ObjectValue(paint_size.reflector().get_jsobject().get()), + ObjectValue(properties.reflector().get_jsobject().get()), + ObjectValue(argument_object.get()), + ]; + let args = unsafe { HandleValueArray::from_rooted_slice(&args_slice) }; + + rooted!(in(*cx) let mut result = UndefinedValue()); + unsafe { + Call( + *cx, + paint_instance.handle(), + paint_function.handle(), + &args, + result.handle_mut(), + ); + } + let missing_image_urls = rendering_context.take_missing_image_urls(); + + // Step 13. + if unsafe { JS_IsExceptionPending(*cx) } { + debug!("Paint function threw an exception {}.", name); + unsafe { + JS_ClearPendingException(*cx); + } + return self.invalid_image(size_in_dpx, missing_image_urls); + } + + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()) + .expect("IPC channel creation."); + rendering_context.send_data(sender); + let image_key = match receiver.recv() { + Ok(data) => Some(data.image_key), + _ => None, + }; + + DrawAPaintImageResult { + width: size_in_dpx.width, + height: size_in_dpx.height, + format: PixelFormat::BGRA8, + image_key: image_key, + missing_image_urls: missing_image_urls, + } + } + + // https://drafts.csswg.org/css-images-4/#invalid-image + fn invalid_image( + &self, + size: Size2D<u32, DevicePixel>, + missing_image_urls: Vec<ServoUrl>, + ) -> DrawAPaintImageResult { + debug!("Returning an invalid image."); + DrawAPaintImageResult { + width: size.width as u32, + height: size.height as u32, + format: PixelFormat::BGRA8, + image_key: None, + missing_image_urls: missing_image_urls, + } + } + + fn painter(&self, name: Atom) -> Box<dyn Painter> { + // Rather annoyingly we have to use a mutex here to make the painter Sync. + struct WorkletPainter { + name: Atom, + executor: Mutex<WorkletExecutor>, + } + impl SpeculativePainter for WorkletPainter { + fn speculatively_draw_a_paint_image( + &self, + properties: Vec<(Atom, String)>, + arguments: Vec<String>, + ) { + let name = self.name.clone(); + let task = + PaintWorkletTask::SpeculativelyDrawAPaintImage(name, properties, arguments); + self.executor + .lock() + .expect("Locking a painter.") + .schedule_a_worklet_task(WorkletTask::Paint(task)); + } + } + impl Painter for WorkletPainter { + fn draw_a_paint_image( + &self, + size: Size2D<f32, CSSPixel>, + device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>, + properties: Vec<(Atom, String)>, + arguments: Vec<String>, + ) -> Result<DrawAPaintImageResult, PaintWorkletError> { + let name = self.name.clone(); + let (sender, receiver) = unbounded(); + let task = PaintWorkletTask::DrawAPaintImage( + name, + size, + device_pixel_ratio, + properties, + arguments, + sender, + ); + self.executor + .lock() + .expect("Locking a painter.") + .schedule_a_worklet_task(WorkletTask::Paint(task)); + + let timeout = pref!(dom.worklet.timeout_ms) as u64; + + receiver + .recv_timeout(Duration::from_millis(timeout)) + .map_err(|e| PaintWorkletError::from(e)) + } + } + Box::new(WorkletPainter { + name: name, + executor: Mutex::new(self.worklet_global.executor()), + }) + } +} + +/// Tasks which can be peformed by a paint worklet +pub enum PaintWorkletTask { + DrawAPaintImage( + Atom, + Size2D<f32, CSSPixel>, + Scale<f32, CSSPixel, DevicePixel>, + Vec<(Atom, String)>, + Vec<String>, + Sender<DrawAPaintImageResult>, + ), + SpeculativelyDrawAPaintImage(Atom, Vec<(Atom, String)>, Vec<String>), +} + +/// A paint definition +/// <https://drafts.css-houdini.org/css-paint-api/#paint-definition> +/// This type is dangerous, because it contains uboxed `Heap<JSVal>` values, +/// which can't be moved. +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +struct PaintDefinition { + #[ignore_malloc_size_of = "mozjs"] + class_constructor: Heap<JSVal>, + #[ignore_malloc_size_of = "mozjs"] + paint_function: Heap<JSVal>, + constructor_valid_flag: Cell<bool>, + context_alpha_flag: bool, + // TODO: this should be a list of CSS syntaxes. + input_arguments_len: usize, + // TODO: the spec calls for fresh rendering contexts each time a paint image is drawn, + // but to avoid having the primary worklet thread create a new renering context, + // we recycle them. + context: Dom<PaintRenderingContext2D>, +} + +impl PaintDefinition { + fn new( + class_constructor: HandleValue, + paint_function: HandleValue, + alpha: bool, + input_arguments_len: usize, + context: &PaintRenderingContext2D, + ) -> Box<PaintDefinition> { + let result = Box::new(PaintDefinition { + class_constructor: Heap::default(), + paint_function: Heap::default(), + constructor_valid_flag: Cell::new(true), + context_alpha_flag: alpha, + input_arguments_len: input_arguments_len, + context: Dom::from_ref(context), + }); + result.class_constructor.set(class_constructor.get()); + result.paint_function.set(paint_function.get()); + result + } +} + +impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope { + #[allow(unsafe_code)] + #[allow(unrooted_must_root)] + /// <https://drafts.css-houdini.org/css-paint-api/#dom-paintworkletglobalscope-registerpaint> + fn RegisterPaint(&self, name: DOMString, paint_ctor: Rc<VoidFunction>) -> Fallible<()> { + let name = Atom::from(name); + let cx = self.worklet_global.get_cx(); + rooted!(in(*cx) let paint_obj = paint_ctor.callback_holder().get()); + rooted!(in(*cx) let paint_val = ObjectValue(paint_obj.get())); + + debug!("Registering paint image name {}.", name); + + // Step 1. + if name.is_empty() { + return Err(Error::Type(String::from("Empty paint name."))); + } + + // Step 2-3. + if self.paint_definitions.borrow().contains_key(&name) { + return Err(Error::InvalidModification); + } + + // Step 4-6. + let mut property_names: Vec<String> = + unsafe { get_property(*cx, paint_obj.handle(), "inputProperties", ()) }? + .unwrap_or_default(); + let properties = property_names.drain(..).map(Atom::from).collect(); + + // Step 7-9. + let input_arguments: Vec<String> = + unsafe { get_property(*cx, paint_obj.handle(), "inputArguments", ()) }? + .unwrap_or_default(); + + // TODO: Steps 10-11. + + // Steps 12-13. + let alpha: bool = + unsafe { get_property(*cx, paint_obj.handle(), "alpha", ()) }?.unwrap_or(true); + + // Step 14 + if unsafe { !IsConstructor(paint_obj.get()) } { + return Err(Error::Type(String::from("Not a constructor."))); + } + + // Steps 15-16 + rooted!(in(*cx) let mut prototype = UndefinedValue()); + unsafe { + get_property_jsval(*cx, paint_obj.handle(), "prototype", prototype.handle_mut())?; + } + if !prototype.is_object() { + return Err(Error::Type(String::from("Prototype is not an object."))); + } + rooted!(in(*cx) let prototype = prototype.to_object()); + + // Steps 17-18 + rooted!(in(*cx) let mut paint_function = UndefinedValue()); + unsafe { + get_property_jsval( + *cx, + prototype.handle(), + "paint", + paint_function.handle_mut(), + )?; + } + if !paint_function.is_object() || unsafe { !IsCallable(paint_function.to_object()) } { + return Err(Error::Type(String::from("Paint function is not callable."))); + } + + // Step 19. + let context = PaintRenderingContext2D::new(self); + let definition = PaintDefinition::new( + paint_val.handle(), + paint_function.handle(), + alpha, + input_arguments.len(), + &*context, + ); + + // Step 20. + debug!("Registering definition {}.", name); + self.paint_definitions + .borrow_mut() + .insert(name.clone(), definition); + + // TODO: Step 21. + + // Inform layout that there is a registered paint worklet. + // TODO: layout will end up getting this message multiple times. + let painter = self.painter(name.clone()); + self.worklet_global + .register_paint_worklet(name, properties, painter); + + Ok(()) + } + + /// This is a blocking sleep function available in the paint worklet + /// global scope behind the dom.worklet.enabled + + /// dom.worklet.blockingsleep.enabled prefs. It is to be used only for + /// testing, e.g., timeouts, where otherwise one would need busy waiting + /// to make sure a certain timeout is triggered. + /// check-tidy: no specs after this line + fn Sleep(&self, ms: u64) { + thread::sleep(Duration::from_millis(ms)); + } +} diff --git a/components/script/dom/pannernode.rs b/components/script/dom/pannernode.rs new file mode 100644 index 00000000000..98b9226287b --- /dev/null +++ b/components/script/dom/pannernode.rs @@ -0,0 +1,385 @@ +/* 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 crate::dom::audionode::AudioNode; +use crate::dom::audioparam::AudioParam; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::{ + AudioParamMethods, AutomationRate, +}; +use crate::dom::bindings::codegen::Bindings::PannerNodeBinding::{ + DistanceModelType, PanningModelType, +}; +use crate::dom::bindings::codegen::Bindings::PannerNodeBinding::{ + PannerNodeMethods, PannerOptions, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage}; +use servo_media::audio::panner_node::PannerNodeMessage; +use servo_media::audio::panner_node::{DistanceModel, PannerNodeOptions, PanningModel}; +use servo_media::audio::param::{ParamDir, ParamType}; +use std::cell::Cell; +use std::f32; + +#[dom_struct] +pub struct PannerNode { + node: AudioNode, + position_x: Dom<AudioParam>, + position_y: Dom<AudioParam>, + position_z: Dom<AudioParam>, + orientation_x: Dom<AudioParam>, + orientation_y: Dom<AudioParam>, + orientation_z: Dom<AudioParam>, + #[ignore_malloc_size_of = "servo_media"] + panning_model: Cell<PanningModel>, + #[ignore_malloc_size_of = "servo_media"] + distance_model: Cell<DistanceModel>, + ref_distance: Cell<f64>, + max_distance: Cell<f64>, + rolloff_factor: Cell<f64>, + cone_inner_angle: Cell<f64>, + cone_outer_angle: Cell<f64>, + cone_outer_gain: Cell<f64>, +} + +impl PannerNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + window: &Window, + context: &BaseAudioContext, + options: &PannerOptions, + ) -> Fallible<PannerNode> { + let node_options = options.parent.unwrap_or( + 2, + ChannelCountMode::Clamped_max, + ChannelInterpretation::Speakers, + ); + if node_options.mode == ChannelCountMode::Max { + return Err(Error::NotSupported); + } + if node_options.count > 2 || node_options.count == 0 { + return Err(Error::NotSupported); + } + if *options.maxDistance <= 0. { + return Err(Error::Range("maxDistance should be positive".into())); + } + if *options.refDistance < 0. { + return Err(Error::Range("refDistance should be non-negative".into())); + } + if *options.rolloffFactor < 0. { + return Err(Error::Range("rolloffFactor should be non-negative".into())); + } + if *options.coneOuterGain < 0. || *options.coneOuterGain > 1. { + return Err(Error::InvalidState); + } + let options = options.into(); + let node = AudioNode::new_inherited( + AudioNodeInit::PannerNode(options), + context, + node_options, + 1, // inputs + 1, // outputs + )?; + let id = node.node_id(); + let position_x = AudioParam::new( + window, + context, + id, + ParamType::Position(ParamDir::X), + AutomationRate::A_rate, + options.position_x, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let position_y = AudioParam::new( + window, + context, + id, + ParamType::Position(ParamDir::Y), + AutomationRate::A_rate, + options.position_y, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let position_z = AudioParam::new( + window, + context, + id, + ParamType::Position(ParamDir::Z), + AutomationRate::A_rate, + options.position_z, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let orientation_x = AudioParam::new( + window, + context, + id, + ParamType::Orientation(ParamDir::X), + AutomationRate::A_rate, + options.orientation_x, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let orientation_y = AudioParam::new( + window, + context, + id, + ParamType::Orientation(ParamDir::Y), + AutomationRate::A_rate, + options.orientation_y, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + let orientation_z = AudioParam::new( + window, + context, + id, + ParamType::Orientation(ParamDir::Z), + AutomationRate::A_rate, + options.orientation_z, // default value + f32::MIN, // min value + f32::MAX, // max value + ); + Ok(PannerNode { + node, + position_x: Dom::from_ref(&position_x), + position_y: Dom::from_ref(&position_y), + position_z: Dom::from_ref(&position_z), + orientation_x: Dom::from_ref(&orientation_x), + orientation_y: Dom::from_ref(&orientation_y), + orientation_z: Dom::from_ref(&orientation_z), + panning_model: Cell::new(options.panning_model), + distance_model: Cell::new(options.distance_model), + ref_distance: Cell::new(options.ref_distance), + max_distance: Cell::new(options.max_distance), + rolloff_factor: Cell::new(options.rolloff_factor), + cone_inner_angle: Cell::new(options.cone_inner_angle), + cone_outer_angle: Cell::new(options.cone_outer_angle), + cone_outer_gain: Cell::new(options.cone_outer_gain), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &PannerOptions, + ) -> Fallible<DomRoot<PannerNode>> { + let node = PannerNode::new_inherited(window, context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &PannerOptions, + ) -> Fallible<DomRoot<PannerNode>> { + PannerNode::new(window, context, options) + } +} + +impl PannerNodeMethods for PannerNode { + // https://webaudio.github.io/web-audio-api/#dom-pannernode-positionx + fn PositionX(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.position_x) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-positiony + fn PositionY(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.position_y) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-positionz + fn PositionZ(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.position_z) + } + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationx + fn OrientationX(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.orientation_x) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationy + fn OrientationY(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.orientation_y) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationz + fn OrientationZ(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.orientation_z) + } + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-distancemodel + fn DistanceModel(&self) -> DistanceModelType { + match self.distance_model.get() { + DistanceModel::Linear => DistanceModelType::Linear, + DistanceModel::Inverse => DistanceModelType::Inverse, + DistanceModel::Exponential => DistanceModelType::Exponential, + } + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-distancemodel + fn SetDistanceModel(&self, model: DistanceModelType) { + self.distance_model.set(model.into()); + let msg = PannerNodeMessage::SetDistanceModel(self.distance_model.get()); + self.upcast::<AudioNode>() + .message(AudioNodeMessage::PannerNode(msg)); + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-panningmodel + fn PanningModel(&self) -> PanningModelType { + match self.panning_model.get() { + PanningModel::EqualPower => PanningModelType::Equalpower, + PanningModel::HRTF => PanningModelType::HRTF, + } + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-panningmodel + fn SetPanningModel(&self, model: PanningModelType) { + self.panning_model.set(model.into()); + let msg = PannerNodeMessage::SetPanningModel(self.panning_model.get()); + self.upcast::<AudioNode>() + .message(AudioNodeMessage::PannerNode(msg)); + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance + fn RefDistance(&self) -> Finite<f64> { + Finite::wrap(self.ref_distance.get()) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance + fn SetRefDistance(&self, val: Finite<f64>) -> Fallible<()> { + if *val < 0. { + return Err(Error::Range("value should be non-negative".into())); + } + self.ref_distance.set(*val); + let msg = PannerNodeMessage::SetRefDistance(self.ref_distance.get()); + self.upcast::<AudioNode>() + .message(AudioNodeMessage::PannerNode(msg)); + Ok(()) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance + fn MaxDistance(&self) -> Finite<f64> { + Finite::wrap(self.max_distance.get()) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance + fn SetMaxDistance(&self, val: Finite<f64>) -> Fallible<()> { + if *val <= 0. { + return Err(Error::Range("value should be positive".into())); + } + self.max_distance.set(*val); + let msg = PannerNodeMessage::SetMaxDistance(self.max_distance.get()); + self.upcast::<AudioNode>() + .message(AudioNodeMessage::PannerNode(msg)); + Ok(()) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor + fn RolloffFactor(&self) -> Finite<f64> { + Finite::wrap(self.rolloff_factor.get()) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor + fn SetRolloffFactor(&self, val: Finite<f64>) -> Fallible<()> { + if *val < 0. { + return Err(Error::Range("value should be non-negative".into())); + } + self.rolloff_factor.set(*val); + let msg = PannerNodeMessage::SetRolloff(self.rolloff_factor.get()); + self.upcast::<AudioNode>() + .message(AudioNodeMessage::PannerNode(msg)); + Ok(()) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneinnerangle + fn ConeInnerAngle(&self) -> Finite<f64> { + Finite::wrap(self.cone_inner_angle.get()) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneinnerangle + fn SetConeInnerAngle(&self, val: Finite<f64>) { + self.cone_inner_angle.set(*val); + let msg = PannerNodeMessage::SetConeInner(self.cone_inner_angle.get()); + self.upcast::<AudioNode>() + .message(AudioNodeMessage::PannerNode(msg)); + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneouterangle + fn ConeOuterAngle(&self) -> Finite<f64> { + Finite::wrap(self.cone_outer_angle.get()) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneouterangle + fn SetConeOuterAngle(&self, val: Finite<f64>) { + self.cone_outer_angle.set(*val); + let msg = PannerNodeMessage::SetConeOuter(self.cone_outer_angle.get()); + self.upcast::<AudioNode>() + .message(AudioNodeMessage::PannerNode(msg)); + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain + fn ConeOuterGain(&self) -> Finite<f64> { + Finite::wrap(self.cone_outer_gain.get()) + } + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain + fn SetConeOuterGain(&self, val: Finite<f64>) -> Fallible<()> { + if *val < 0. || *val > 1. { + return Err(Error::InvalidState); + } + self.cone_outer_gain.set(*val); + let msg = PannerNodeMessage::SetConeGain(self.cone_outer_gain.get()); + self.upcast::<AudioNode>() + .message(AudioNodeMessage::PannerNode(msg)); + Ok(()) + } + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-setposition + fn SetPosition(&self, x: Finite<f32>, y: Finite<f32>, z: Finite<f32>) { + self.position_x.SetValue(x); + self.position_y.SetValue(y); + self.position_z.SetValue(z); + } + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-setorientation + fn SetOrientation(&self, x: Finite<f32>, y: Finite<f32>, z: Finite<f32>) { + self.orientation_x.SetValue(x); + self.orientation_y.SetValue(y); + self.orientation_z.SetValue(z); + } +} + +impl<'a> From<&'a PannerOptions> for PannerNodeOptions { + fn from(options: &'a PannerOptions) -> Self { + Self { + panning_model: options.panningModel.into(), + distance_model: options.distanceModel.into(), + position_x: *options.positionX, + position_y: *options.positionY, + position_z: *options.positionZ, + orientation_x: *options.orientationX, + orientation_y: *options.orientationY, + orientation_z: *options.orientationZ, + ref_distance: *options.refDistance, + max_distance: *options.maxDistance, + rolloff_factor: *options.rolloffFactor, + cone_inner_angle: *options.coneInnerAngle, + cone_outer_angle: *options.coneOuterAngle, + cone_outer_gain: *options.coneOuterGain, + } + } +} + +impl From<DistanceModelType> for DistanceModel { + fn from(model: DistanceModelType) -> Self { + match model { + DistanceModelType::Linear => DistanceModel::Linear, + DistanceModelType::Inverse => DistanceModel::Inverse, + DistanceModelType::Exponential => DistanceModel::Exponential, + } + } +} + +impl From<PanningModelType> for PanningModel { + fn from(model: PanningModelType) -> Self { + match model { + PanningModelType::Equalpower => PanningModel::EqualPower, + PanningModelType::HRTF => PanningModel::HRTF, + } + } +} diff --git a/components/script/dom/performance.rs b/components/script/dom/performance.rs index 7ce0cd7d1eb..c707160f66a 100644 --- a/components/script/dom/performance.rs +++ b/components/script/dom/performance.rs @@ -1,58 +1,561 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::PerformanceBinding; -use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::performancetiming::PerformanceTiming; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceEntryList as DOMPerformanceEntryList; +use crate::dom::bindings::codegen::Bindings::PerformanceBinding::{ + DOMHighResTimeStamp, PerformanceMethods, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::performanceentry::PerformanceEntry; +use crate::dom::performancemark::PerformanceMark; +use crate::dom::performancemeasure::PerformanceMeasure; +use crate::dom::performancenavigation::PerformanceNavigation; +use crate::dom::performancenavigationtiming::PerformanceNavigationTiming; +use crate::dom::performanceobserver::PerformanceObserver as DOMPerformanceObserver; +use crate::dom::window::Window; use dom_struct::dom_struct; -use time; +use metrics::ToMs; +use std::cell::Cell; +use std::cmp::Ordering; +use std::collections::VecDeque; + +const INVALID_ENTRY_NAMES: &'static [&'static str] = &[ + "navigationStart", + "unloadEventStart", + "unloadEventEnd", + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + "responseEnd", + "domLoading", + "domInteractive", + "domContentLoadedEventStart", + "domContentLoadedEventEnd", + "domComplete", + "loadEventStart", + "loadEventEnd", +]; + +/// Implementation of a list of PerformanceEntry items shared by the +/// Performance and PerformanceObserverEntryList interfaces implementations. +#[derive(JSTraceable, MallocSizeOf)] +pub struct PerformanceEntryList { + /// https://w3c.github.io/performance-timeline/#dfn-performance-entry-buffer + entries: DOMPerformanceEntryList, +} + +impl PerformanceEntryList { + pub fn new(entries: DOMPerformanceEntryList) -> Self { + PerformanceEntryList { entries } + } + + pub fn get_entries_by_name_and_type( + &self, + name: Option<DOMString>, + entry_type: Option<DOMString>, + ) -> Vec<DomRoot<PerformanceEntry>> { + let mut res = self + .entries + .iter() + .filter(|e| { + name.as_ref().map_or(true, |name_| *e.name() == *name_) && + entry_type + .as_ref() + .map_or(true, |type_| *e.entry_type() == *type_) + }) + .map(|e| e.clone()) + .collect::<Vec<DomRoot<PerformanceEntry>>>(); + res.sort_by(|a, b| { + a.start_time() + .partial_cmp(&b.start_time()) + .unwrap_or(Ordering::Equal) + }); + res + } + + pub fn clear_entries_by_name_and_type( + &mut self, + name: Option<DOMString>, + entry_type: Option<DOMString>, + ) { + self.entries.retain(|e| { + name.as_ref().map_or(true, |name_| *e.name() != *name_) && + entry_type + .as_ref() + .map_or(true, |type_| *e.entry_type() != *type_) + }); + } + + fn get_last_entry_start_time_with_name_and_type( + &self, + name: DOMString, + entry_type: DOMString, + ) -> f64 { + match self + .entries + .iter() + .rev() + .find(|e| *e.entry_type() == *entry_type && *e.name() == *name) + { + Some(entry) => entry.start_time(), + None => 0., + } + } +} + +impl IntoIterator for PerformanceEntryList { + type Item = DomRoot<PerformanceEntry>; + type IntoIter = ::std::vec::IntoIter<DomRoot<PerformanceEntry>>; + + fn into_iter(self) -> Self::IntoIter { + self.entries.into_iter() + } +} -pub type DOMHighResTimeStamp = Finite<f64>; +#[derive(JSTraceable, MallocSizeOf)] +struct PerformanceObserver { + observer: DomRoot<DOMPerformanceObserver>, + entry_types: Vec<DOMString>, +} #[dom_struct] pub struct Performance { - reflector_: Reflector, - timing: JS<PerformanceTiming>, + eventtarget: EventTarget, + buffer: DomRefCell<PerformanceEntryList>, + observers: DomRefCell<Vec<PerformanceObserver>>, + pending_notification_observers_task: Cell<bool>, + navigation_start_precise: u64, + /// https://w3c.github.io/performance-timeline/#dfn-maxbuffersize + /// The max-size of the buffer, set to 0 once the pipeline exits. + /// TODO: have one max-size per entry type. + resource_timing_buffer_size_limit: Cell<usize>, + resource_timing_buffer_current_size: Cell<usize>, + resource_timing_buffer_pending_full_event: Cell<bool>, + resource_timing_secondary_entries: DomRefCell<VecDeque<DomRoot<PerformanceEntry>>>, } impl Performance { - fn new_inherited(window: &Window, - navigation_start: u64, - navigation_start_precise: f64) -> Performance { + fn new_inherited(navigation_start_precise: u64) -> Performance { Performance { - reflector_: Reflector::new(), - timing: JS::from_ref(&*PerformanceTiming::new(window, - navigation_start, - navigation_start_precise)), + eventtarget: EventTarget::new_inherited(), + buffer: DomRefCell::new(PerformanceEntryList::new(Vec::new())), + observers: DomRefCell::new(Vec::new()), + pending_notification_observers_task: Cell::new(false), + navigation_start_precise, + resource_timing_buffer_size_limit: Cell::new(250), + resource_timing_buffer_current_size: Cell::new(0), + resource_timing_buffer_pending_full_event: Cell::new(false), + resource_timing_secondary_entries: DomRefCell::new(VecDeque::new()), } } - pub fn new(window: &Window, - navigation_start: u64, - navigation_start_precise: f64) -> Root<Performance> { - reflect_dom_object(box Performance::new_inherited(window, - navigation_start, - navigation_start_precise), - window, - PerformanceBinding::Wrap) + pub fn new(global: &GlobalScope, navigation_start_precise: u64) -> DomRoot<Performance> { + reflect_dom_object( + Box::new(Performance::new_inherited(navigation_start_precise)), + global, + ) + } + + /// Clear all buffered performance entries, and disable the buffer. + /// Called as part of the window's "clear_js_runtime" workflow, + /// performed when exiting a pipeline. + pub fn clear_and_disable_performance_entry_buffer(&self) { + let mut buffer = self.buffer.borrow_mut(); + buffer.entries.clear(); + self.resource_timing_buffer_size_limit.set(0); + } + + /// Add a PerformanceObserver to the list of observers with a set of + /// observed entry types. + + pub fn add_multiple_type_observer( + &self, + observer: &DOMPerformanceObserver, + entry_types: Vec<DOMString>, + ) { + let mut observers = self.observers.borrow_mut(); + match observers.iter().position(|o| *o.observer == *observer) { + // If the observer is already in the list, we only update the observed + // entry types. + Some(p) => observers[p].entry_types = entry_types, + // Otherwise, we create and insert the new PerformanceObserver. + None => observers.push(PerformanceObserver { + observer: DomRoot::from_ref(observer), + entry_types, + }), + }; + } + + pub fn add_single_type_observer( + &self, + observer: &DOMPerformanceObserver, + entry_type: &DOMString, + buffered: bool, + ) { + if buffered { + let buffer = self.buffer.borrow(); + let mut new_entries = + buffer.get_entries_by_name_and_type(None, Some(entry_type.clone())); + if new_entries.len() > 0 { + let mut obs_entries = observer.entries(); + obs_entries.append(&mut new_entries); + observer.set_entries(obs_entries); + } + + if !self.pending_notification_observers_task.get() { + self.pending_notification_observers_task.set(true); + let task_source = self.global().performance_timeline_task_source(); + task_source.queue_notification(&self.global()); + } + } + let mut observers = self.observers.borrow_mut(); + match observers.iter().position(|o| *o.observer == *observer) { + // If the observer is already in the list, we only update + // the observed entry types. + Some(p) => { + // Append the type if not already present, otherwise do nothing + if !observers[p].entry_types.contains(entry_type) { + observers[p].entry_types.push(entry_type.clone()) + } + }, + // Otherwise, we create and insert the new PerformanceObserver. + None => observers.push(PerformanceObserver { + observer: DomRoot::from_ref(observer), + entry_types: vec![entry_type.clone()], + }), + }; + } + + /// Remove a PerformanceObserver from the list of observers. + pub fn remove_observer(&self, observer: &DOMPerformanceObserver) { + let mut observers = self.observers.borrow_mut(); + let index = match observers.iter().position(|o| &(*o.observer) == observer) { + Some(p) => p, + None => return, + }; + + observers.remove(index); + } + + /// Queue a notification for each performance observer interested in + /// this type of performance entry and queue a low priority task to + /// notify the observers if no other notification task is already queued. + /// + /// Algorithm spec: + /// <https://w3c.github.io/performance-timeline/#queue-a-performanceentry> + /// Also this algorithm has been extented according to : + /// <https://w3c.github.io/resource-timing/#sec-extensions-performance-interface> + pub fn queue_entry(&self, entry: &PerformanceEntry) -> Option<usize> { + // https://w3c.github.io/performance-timeline/#dfn-determine-eligibility-for-adding-a-performance-entry + if entry.entry_type() == "resource" && !self.should_queue_resource_entry(entry) { + return None; + } + + // Steps 1-3. + // Add the performance entry to the list of performance entries that have not + // been notified to each performance observer owner, filtering the ones it's + // interested in. + for o in self + .observers + .borrow() + .iter() + .filter(|o| o.entry_types.contains(entry.entry_type())) + { + o.observer.queue_entry(entry); + } + + // Step 4. + //add the new entry to the buffer. + self.buffer + .borrow_mut() + .entries + .push(DomRoot::from_ref(entry)); + + let entry_last_index = self.buffer.borrow_mut().entries.len() - 1; + + // Step 5. + // If there is already a queued notification task, we just bail out. + if self.pending_notification_observers_task.get() { + return None; + } + + // Step 6. + // Queue a new notification task. + self.pending_notification_observers_task.set(true); + let task_source = self.global().performance_timeline_task_source(); + task_source.queue_notification(&self.global()); + + Some(entry_last_index) + } + + /// Observers notifications task. + /// + /// Algorithm spec (step 7): + /// <https://w3c.github.io/performance-timeline/#queue-a-performanceentry> + pub fn notify_observers(&self) { + // Step 7.1. + self.pending_notification_observers_task.set(false); + + // Step 7.2. + // We have to operate over a copy of the performance observers to avoid + // the risk of an observer's callback modifying the list of registered + // observers. This is a shallow copy, so observers can + // disconnect themselves by using the argument of their own callback. + let observers: Vec<DomRoot<DOMPerformanceObserver>> = self + .observers + .borrow() + .iter() + .map(|o| DomRoot::from_ref(&*o.observer)) + .collect(); + + // Step 7.3. + for o in observers.iter() { + o.notify(); + } + } + + fn now(&self) -> f64 { + (time::precise_time_ns() - self.navigation_start_precise).to_ms() + } + + fn can_add_resource_timing_entry(&self) -> bool { + self.resource_timing_buffer_current_size.get() <= + self.resource_timing_buffer_size_limit.get() + } + fn copy_secondary_resource_timing_buffer(&self) { + while self.can_add_resource_timing_entry() { + let entry = self + .resource_timing_secondary_entries + .borrow_mut() + .pop_front(); + if let Some(ref entry) = entry { + self.queue_entry(entry); + } else { + break; + } + } + } + // `fire a buffer full event` paragraph of + // https://w3c.github.io/resource-timing/#sec-extensions-performance-interface + fn fire_buffer_full_event(&self) { + while !self.resource_timing_secondary_entries.borrow().is_empty() { + let no_of_excess_entries_before = self.resource_timing_secondary_entries.borrow().len(); + + if !self.can_add_resource_timing_entry() { + self.upcast::<EventTarget>() + .fire_event(atom!("resourcetimingbufferfull")); + } + self.copy_secondary_resource_timing_buffer(); + let no_of_excess_entries_after = self.resource_timing_secondary_entries.borrow().len(); + if no_of_excess_entries_before <= no_of_excess_entries_after { + self.resource_timing_secondary_entries.borrow_mut().clear(); + break; + } + } + self.resource_timing_buffer_pending_full_event.set(false); + } + /// `add a PerformanceResourceTiming entry` paragraph of + /// https://w3c.github.io/resource-timing/#sec-extensions-performance-interface + fn should_queue_resource_entry(&self, entry: &PerformanceEntry) -> bool { + // Step 1 is done in the args list. + if !self.resource_timing_buffer_pending_full_event.get() { + // Step 2. + if self.can_add_resource_timing_entry() { + // Step 2.a is done in `queue_entry` + // Step 2.b. + self.resource_timing_buffer_current_size + .set(self.resource_timing_buffer_current_size.get() + 1); + // Step 2.c. + return true; + } + // Step 3. + self.resource_timing_buffer_pending_full_event.set(true); + self.fire_buffer_full_event(); + } + // Steps 4 and 5. + self.resource_timing_secondary_entries + .borrow_mut() + .push_back(DomRoot::from_ref(entry)); + false + } + + pub fn update_entry(&self, index: usize, entry: &PerformanceEntry) { + if let Some(e) = self.buffer.borrow_mut().entries.get_mut(index) { + *e = DomRoot::from_ref(entry); + } } } impl PerformanceMethods for Performance { + // FIXME(avada): this should be deprecated in the future, but some sites still use it // https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#performance-timing-attribute - fn Timing(&self) -> Root<PerformanceTiming> { - Root::from_ref(&*self.timing) + fn Timing(&self) -> DomRoot<PerformanceNavigationTiming> { + let entries = self.GetEntriesByType(DOMString::from("navigation")); + if entries.len() > 0 { + return DomRoot::from_ref( + entries[0] + .downcast::<PerformanceNavigationTiming>() + .unwrap(), + ); + } + unreachable!("Are we trying to expose Performance.timing in workers?"); + } + + // https://w3c.github.io/navigation-timing/#dom-performance-navigation + fn Navigation(&self) -> DomRoot<PerformanceNavigation> { + PerformanceNavigation::new(&self.global()) } // https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HighResolutionTime/Overview.html#dom-performance-now fn Now(&self) -> DOMHighResTimeStamp { - let nav_start = self.timing.navigation_start_precise(); - let now = (time::precise_time_ns() as f64 - nav_start) / 1000000 as f64; - Finite::wrap(now) + reduce_timing_resolution(self.now()) + } + + // https://www.w3.org/TR/hr-time-2/#dom-performance-timeorigin + fn TimeOrigin(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.navigation_start_precise as f64) + } + + // https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentries + fn GetEntries(&self) -> Vec<DomRoot<PerformanceEntry>> { + self.buffer + .borrow() + .get_entries_by_name_and_type(None, None) + } + + // https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentriesbytype + fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<DomRoot<PerformanceEntry>> { + self.buffer + .borrow() + .get_entries_by_name_and_type(None, Some(entry_type)) } + + // https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentriesbyname + fn GetEntriesByName( + &self, + name: DOMString, + entry_type: Option<DOMString>, + ) -> Vec<DomRoot<PerformanceEntry>> { + self.buffer + .borrow() + .get_entries_by_name_and_type(Some(name), entry_type) + } + + // https://w3c.github.io/user-timing/#dom-performance-mark + fn Mark(&self, mark_name: DOMString) -> Fallible<()> { + let global = self.global(); + // Step 1. + if global.is::<Window>() && INVALID_ENTRY_NAMES.contains(&mark_name.as_ref()) { + return Err(Error::Syntax); + } + + // Steps 2 to 6. + let entry = PerformanceMark::new(&global, mark_name, self.now(), 0.); + // Steps 7 and 8. + self.queue_entry(&entry.upcast::<PerformanceEntry>()); + + // Step 9. + Ok(()) + } + + // https://w3c.github.io/user-timing/#dom-performance-clearmarks + fn ClearMarks(&self, mark_name: Option<DOMString>) { + self.buffer + .borrow_mut() + .clear_entries_by_name_and_type(mark_name, Some(DOMString::from("mark"))); + } + + // https://w3c.github.io/user-timing/#dom-performance-measure + fn Measure( + &self, + measure_name: DOMString, + start_mark: Option<DOMString>, + end_mark: Option<DOMString>, + ) -> Fallible<()> { + // Steps 1 and 2. + let end_time = match end_mark { + Some(name) => self + .buffer + .borrow() + .get_last_entry_start_time_with_name_and_type(DOMString::from("mark"), name), + None => self.now(), + }; + + // Step 3. + let start_time = match start_mark { + Some(name) => self + .buffer + .borrow() + .get_last_entry_start_time_with_name_and_type(DOMString::from("mark"), name), + None => 0., + }; + + // Steps 4 to 8. + let entry = PerformanceMeasure::new( + &self.global(), + measure_name, + start_time, + end_time - start_time, + ); + + // Step 9 and 10. + self.queue_entry(&entry.upcast::<PerformanceEntry>()); + + // Step 11. + Ok(()) + } + + // https://w3c.github.io/user-timing/#dom-performance-clearmeasures + fn ClearMeasures(&self, measure_name: Option<DOMString>) { + self.buffer + .borrow_mut() + .clear_entries_by_name_and_type(measure_name, Some(DOMString::from("measure"))); + } + // https://w3c.github.io/resource-timing/#dom-performance-clearresourcetimings + fn ClearResourceTimings(&self) { + self.buffer + .borrow_mut() + .clear_entries_by_name_and_type(None, Some(DOMString::from("resource"))); + self.resource_timing_buffer_current_size.set(0); + } + + // https://w3c.github.io/resource-timing/#dom-performance-setresourcetimingbuffersize + fn SetResourceTimingBufferSize(&self, max_size: u32) { + self.resource_timing_buffer_size_limit + .set(max_size as usize); + } + + // https://w3c.github.io/resource-timing/#dom-performance-onresourcetimingbufferfull + event_handler!( + resourcetimingbufferfull, + GetOnresourcetimingbufferfull, + SetOnresourcetimingbufferfull + ); +} + +// https://www.w3.org/TR/hr-time-2/#clock-resolution +pub fn reduce_timing_resolution(exact: f64) -> DOMHighResTimeStamp { + // We need a granularity no finer than 5 microseconds. + // 5 microseconds isn't an exactly representable f64 so WPT tests + // might occasionally corner-case on rounding. + // web-platform-tests/wpt#21526 wants us to use an integer number of + // microseconds; the next divisor of milliseconds up from 5 microseconds + // is 10, which is 1/100th of a millisecond. + Finite::wrap((exact * 100.0).floor() / 100.0) } diff --git a/components/script/dom/performanceentry.rs b/components/script/dom/performanceentry.rs new file mode 100644 index 00000000000..8197deb8976 --- /dev/null +++ b/components/script/dom/performanceentry.rs @@ -0,0 +1,88 @@ +/* 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 crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; +use crate::dom::bindings::codegen::Bindings::PerformanceEntryBinding::PerformanceEntryMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::performance::reduce_timing_resolution; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct PerformanceEntry { + reflector_: Reflector, + name: DOMString, + entry_type: DOMString, + start_time: f64, + duration: f64, +} + +impl PerformanceEntry { + pub fn new_inherited( + name: DOMString, + entry_type: DOMString, + start_time: f64, + duration: f64, + ) -> PerformanceEntry { + PerformanceEntry { + reflector_: Reflector::new(), + name, + entry_type, + start_time, + duration, + } + } + + #[allow(unrooted_must_root)] + pub fn new( + global: &GlobalScope, + name: DOMString, + entry_type: DOMString, + start_time: f64, + duration: f64, + ) -> DomRoot<PerformanceEntry> { + let entry = PerformanceEntry::new_inherited(name, entry_type, start_time, duration); + reflect_dom_object(Box::new(entry), global) + } + + pub fn entry_type(&self) -> &DOMString { + &self.entry_type + } + + pub fn name(&self) -> &DOMString { + &self.name + } + + pub fn start_time(&self) -> f64 { + self.start_time + } + + pub fn duration(&self) -> f64 { + self.duration + } +} + +impl PerformanceEntryMethods for PerformanceEntry { + // https://w3c.github.io/performance-timeline/#dom-performanceentry-name + fn Name(&self) -> DOMString { + DOMString::from(self.name.clone()) + } + + // https://w3c.github.io/performance-timeline/#dom-performanceentry-entrytype + fn EntryType(&self) -> DOMString { + DOMString::from(self.entry_type.clone()) + } + + // https://w3c.github.io/performance-timeline/#dom-performanceentry-starttime + fn StartTime(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.start_time) + } + + // https://w3c.github.io/performance-timeline/#dom-performanceentry-duration + fn Duration(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.duration) + } +} diff --git a/components/script/dom/performancemark.rs b/components/script/dom/performancemark.rs new file mode 100644 index 00000000000..d79f9864ecc --- /dev/null +++ b/components/script/dom/performancemark.rs @@ -0,0 +1,5 @@ +/* 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/. */ + +impl_performance_entry_struct!(PerformanceMarkBinding, PerformanceMark, "mark"); diff --git a/components/script/dom/performancemeasure.rs b/components/script/dom/performancemeasure.rs new file mode 100644 index 00000000000..db98a3f5746 --- /dev/null +++ b/components/script/dom/performancemeasure.rs @@ -0,0 +1,5 @@ +/* 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/. */ + +impl_performance_entry_struct!(PerformanceMeasureBinding, PerformanceMeasure, "measure"); diff --git a/components/script/dom/performancenavigation.rs b/components/script/dom/performancenavigation.rs new file mode 100644 index 00000000000..f52250b815c --- /dev/null +++ b/components/script/dom/performancenavigation.rs @@ -0,0 +1,41 @@ +/* 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 crate::dom::bindings::codegen::Bindings::PerformanceNavigationBinding::{ + PerformanceNavigationConstants, PerformanceNavigationMethods, +}; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct PerformanceNavigation { + reflector_: Reflector, +} + +impl PerformanceNavigation { + fn new_inherited() -> PerformanceNavigation { + PerformanceNavigation { + reflector_: Reflector::new(), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<PerformanceNavigation> { + reflect_dom_object(Box::new(PerformanceNavigation::new_inherited()), global) + } +} + +impl PerformanceNavigationMethods for PerformanceNavigation { + // https://w3c.github.io/navigation-timing/#dom-performancenavigation-type + fn Type(&self) -> u16 { + PerformanceNavigationConstants::TYPE_NAVIGATE + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigation-redirectcount + fn RedirectCount(&self) -> u16 { + self.global().as_window().Document().get_redirect_count() + } +} diff --git a/components/script/dom/performancenavigationtiming.rs b/components/script/dom/performancenavigationtiming.rs new file mode 100644 index 00000000000..5562427f1c9 --- /dev/null +++ b/components/script/dom/performancenavigationtiming.rs @@ -0,0 +1,123 @@ +/* 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 crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; +use crate::dom::bindings::codegen::Bindings::PerformanceNavigationTimingBinding::NavigationType; +use crate::dom::bindings::codegen::Bindings::PerformanceNavigationTimingBinding::PerformanceNavigationTimingMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::document::Document; +use crate::dom::globalscope::GlobalScope; +use crate::dom::performanceresourcetiming::{InitiatorType, PerformanceResourceTiming}; +use dom_struct::dom_struct; + +#[dom_struct] +// https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming +/// Only the current document resource is included in the performance timeline; +/// there is only one PerformanceNavigationTiming object in the performance timeline. +pub struct PerformanceNavigationTiming { + // https://w3c.github.io/navigation-timing/#PerformanceResourceTiming + performanceresourcetiming: PerformanceResourceTiming, + navigation_start: u64, + navigation_start_precise: u64, + document: Dom<Document>, + nav_type: NavigationType, +} + +impl PerformanceNavigationTiming { + fn new_inherited( + nav_start: u64, + nav_start_precise: u64, + document: &Document, + ) -> PerformanceNavigationTiming { + PerformanceNavigationTiming { + performanceresourcetiming: PerformanceResourceTiming::new_inherited( + document.url(), + InitiatorType::Navigation, + None, + nav_start_precise as f64, + ), + navigation_start: nav_start, + navigation_start_precise: nav_start_precise, + document: Dom::from_ref(document), + nav_type: NavigationType::Navigate, + } + } + + pub fn new( + global: &GlobalScope, + nav_start: u64, + nav_start_precise: u64, + document: &Document, + ) -> DomRoot<PerformanceNavigationTiming> { + reflect_dom_object( + Box::new(PerformanceNavigationTiming::new_inherited( + nav_start, + nav_start_precise, + document, + )), + global, + ) + } +} + +// https://w3c.github.io/navigation-timing/ +impl PerformanceNavigationTimingMethods for PerformanceNavigationTiming { + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-unloadeventstart + fn UnloadEventStart(&self) -> DOMHighResTimeStamp { + Finite::wrap(self.document.get_unload_event_start() as f64) + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-unloadeventend + fn UnloadEventEnd(&self) -> DOMHighResTimeStamp { + Finite::wrap(self.document.get_unload_event_end() as f64) + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-dominteractive + fn DomInteractive(&self) -> DOMHighResTimeStamp { + Finite::wrap(self.document.get_dom_interactive() as f64) + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-domcontentloadedeventstart + fn DomContentLoadedEventStart(&self) -> DOMHighResTimeStamp { + Finite::wrap(self.document.get_dom_content_loaded_event_start() as f64) + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-domcontentloadedeventstart + fn DomContentLoadedEventEnd(&self) -> DOMHighResTimeStamp { + Finite::wrap(self.document.get_dom_content_loaded_event_end() as f64) + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-domcomplete + fn DomComplete(&self) -> DOMHighResTimeStamp { + Finite::wrap(self.document.get_dom_complete() as f64) + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-loadeventstart + fn LoadEventStart(&self) -> DOMHighResTimeStamp { + Finite::wrap(self.document.get_load_event_start() as f64) + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-loadeventend + fn LoadEventEnd(&self) -> DOMHighResTimeStamp { + Finite::wrap(self.document.get_load_event_end() as f64) + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-type + fn Type(&self) -> NavigationType { + self.nav_type.clone() + } + + // https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming-redirectcount + fn RedirectCount(&self) -> u16 { + self.document.get_redirect_count() + } + + // check-tidy: no specs after this line + // Servo-only timing for when top-level content (not iframes) is complete + fn TopLevelDomComplete(&self) -> DOMHighResTimeStamp { + Finite::wrap(self.document.get_top_level_dom_complete() as f64) + } +} diff --git a/components/script/dom/performanceobserver.rs b/components/script/dom/performanceobserver.rs new file mode 100644 index 00000000000..8d689a35785 --- /dev/null +++ b/components/script/dom/performanceobserver.rs @@ -0,0 +1,235 @@ +/* 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 crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceEntryList as DOMPerformanceEntryList; +use crate::dom::bindings::codegen::Bindings::PerformanceObserverBinding::PerformanceObserverCallback; +use crate::dom::bindings::codegen::Bindings::PerformanceObserverBinding::PerformanceObserverInit; +use crate::dom::bindings::codegen::Bindings::PerformanceObserverBinding::PerformanceObserverMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::console::Console; +use crate::dom::globalscope::GlobalScope; +use crate::dom::performance::PerformanceEntryList; +use crate::dom::performanceentry::PerformanceEntry; +use crate::dom::performanceobserverentrylist::PerformanceObserverEntryList; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use js::jsval::JSVal; +use std::cell::Cell; +use std::rc::Rc; + +/// List of allowed performance entry types, in alphabetical order. +pub const VALID_ENTRY_TYPES: &'static [&'static str] = &[ + // "frame", //TODO Frame Timing API + "mark", // User Timing API + "measure", // User Timing API + "navigation", // Navigation Timing API + "paint", // Paint Timing API + "resource", // Resource Timing API + // "server", XXX Server Timing API +]; + +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +enum ObserverType { + Undefined, + Single, + Multiple, +} + +#[dom_struct] +pub struct PerformanceObserver { + reflector_: Reflector, + #[ignore_malloc_size_of = "can't measure Rc values"] + callback: Rc<PerformanceObserverCallback>, + entries: DomRefCell<DOMPerformanceEntryList>, + observer_type: Cell<ObserverType>, +} + +impl PerformanceObserver { + fn new_inherited( + callback: Rc<PerformanceObserverCallback>, + entries: DomRefCell<DOMPerformanceEntryList>, + ) -> PerformanceObserver { + PerformanceObserver { + reflector_: Reflector::new(), + callback, + entries, + observer_type: Cell::new(ObserverType::Undefined), + } + } + + #[allow(unrooted_must_root)] + pub fn new( + global: &GlobalScope, + callback: Rc<PerformanceObserverCallback>, + entries: DOMPerformanceEntryList, + ) -> DomRoot<PerformanceObserver> { + let observer = PerformanceObserver::new_inherited(callback, DomRefCell::new(entries)); + reflect_dom_object(Box::new(observer), global) + } + + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + callback: Rc<PerformanceObserverCallback>, + ) -> Fallible<DomRoot<PerformanceObserver>> { + Ok(PerformanceObserver::new(global, callback, Vec::new())) + } + + /// Buffer a new performance entry. + pub fn queue_entry(&self, entry: &PerformanceEntry) { + self.entries.borrow_mut().push(DomRoot::from_ref(entry)); + } + + /// Trigger performance observer callback with the list of performance entries + /// buffered since the last callback call. + pub fn notify(&self) { + if self.entries.borrow().is_empty() { + return; + } + let entry_list = PerformanceEntryList::new(self.entries.borrow_mut().drain(..).collect()); + let observer_entry_list = PerformanceObserverEntryList::new(&self.global(), entry_list); + // using self both as thisArg and as the second formal argument + let _ = self + .callback + .Call_(self, &observer_entry_list, self, ExceptionHandling::Report); + } + + pub fn callback(&self) -> Rc<PerformanceObserverCallback> { + self.callback.clone() + } + + pub fn entries(&self) -> DOMPerformanceEntryList { + self.entries.borrow().clone() + } + + pub fn set_entries(&self, entries: DOMPerformanceEntryList) { + *self.entries.borrow_mut() = entries; + } + + // https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute + #[allow(non_snake_case)] + pub fn SupportedEntryTypes(cx: JSContext, global: &GlobalScope) -> JSVal { + // While this is exposed through a method of PerformanceObserver, + // it is specified as associated with the global scope. + global.supported_performance_entry_types(cx) + } +} + +impl PerformanceObserverMethods for PerformanceObserver { + // https://w3c.github.io/performance-timeline/#dom-performanceobserver-observe() + fn Observe(&self, options: &PerformanceObserverInit) -> Fallible<()> { + // Step 1 is self + + // Step 2 is self.global() + + // Step 3 + if options.entryTypes.is_none() && options.type_.is_none() { + return Err(Error::Syntax); + } + + // Step 4 + if options.entryTypes.is_some() && (options.buffered.is_some() || options.type_.is_some()) { + return Err(Error::Syntax); + } + + // If this point is reached, then one of options.entryTypes or options.type_ + // is_some, but not both. + + // Step 5 + match self.observer_type.get() { + ObserverType::Undefined => { + if options.entryTypes.is_some() { + self.observer_type.set(ObserverType::Multiple); + } else { + self.observer_type.set(ObserverType::Single); + } + }, + ObserverType::Single => { + if options.entryTypes.is_some() { + return Err(Error::InvalidModification); + } + }, + ObserverType::Multiple => { + if options.type_.is_some() { + return Err(Error::InvalidModification); + } + }, + } + + // The entryTypes and type paths diverge here + if let Some(entry_types) = &options.entryTypes { + // Steps 6.1 - 6.2 + let entry_types = entry_types + .iter() + .filter(|e| VALID_ENTRY_TYPES.contains(&e.as_ref())) + .map(|e| e.clone()) + .collect::<Vec<DOMString>>(); + + // Step 6.3 + if entry_types.is_empty() { + Console::Warn( + &*self.global(), + vec![DOMString::from( + "No valid entry type provided to observe().", + )], + ); + return Ok(()); + } + + // Steps 6.4-6.5 + // This never pre-fills buffered entries, and + // any existing types are replaced. + self.global() + .performance() + .add_multiple_type_observer(self, entry_types); + Ok(()) + } else if let Some(entry_type) = &options.type_ { + // Step 7.2 + if !VALID_ENTRY_TYPES.contains(&entry_type.as_ref()) { + Console::Warn( + &*self.global(), + vec![DOMString::from( + "No valid entry type provided to observe().", + )], + ); + return Ok(()); + } + + // Steps 7.3-7.5 + // This may pre-fill buffered entries, and + // existing types are appended to. + self.global().performance().add_single_type_observer( + self, + entry_type, + options.buffered.unwrap_or(false), + ); + Ok(()) + } else { + // Step 7.1 + unreachable!() + } + } + + // https://w3c.github.io/performance-timeline/#dom-performanceobserver-disconnect + fn Disconnect(&self) { + self.global().performance().remove_observer(self); + self.entries.borrow_mut().clear(); + } + + // https://w3c.github.io/performance-timeline/#takerecords-method + fn TakeRecords(&self) -> Vec<DomRoot<PerformanceEntry>> { + let mut entries = self.entries.borrow_mut(); + let taken = entries + .iter() + .map(|entry| DomRoot::from_ref(&**entry)) + .collect(); + entries.clear(); + return taken; + } +} diff --git a/components/script/dom/performanceobserverentrylist.rs b/components/script/dom/performanceobserverentrylist.rs new file mode 100644 index 00000000000..f96e063153e --- /dev/null +++ b/components/script/dom/performanceobserverentrylist.rs @@ -0,0 +1,64 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::PerformanceObserverEntryListBinding::PerformanceObserverEntryListMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::performance::PerformanceEntryList; +use crate::dom::performanceentry::PerformanceEntry; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct PerformanceObserverEntryList { + reflector_: Reflector, + entries: DomRefCell<PerformanceEntryList>, +} + +impl PerformanceObserverEntryList { + fn new_inherited(entries: PerformanceEntryList) -> PerformanceObserverEntryList { + PerformanceObserverEntryList { + reflector_: Reflector::new(), + entries: DomRefCell::new(entries), + } + } + + #[allow(unrooted_must_root)] + pub fn new( + global: &GlobalScope, + entries: PerformanceEntryList, + ) -> DomRoot<PerformanceObserverEntryList> { + let observer_entry_list = PerformanceObserverEntryList::new_inherited(entries); + reflect_dom_object(Box::new(observer_entry_list), global) + } +} + +impl PerformanceObserverEntryListMethods for PerformanceObserverEntryList { + // https://w3c.github.io/performance-timeline/#dom-performanceobserver + fn GetEntries(&self) -> Vec<DomRoot<PerformanceEntry>> { + self.entries + .borrow() + .get_entries_by_name_and_type(None, None) + } + + // https://w3c.github.io/performance-timeline/#dom-performanceobserver + fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<DomRoot<PerformanceEntry>> { + self.entries + .borrow() + .get_entries_by_name_and_type(None, Some(entry_type)) + } + + // https://w3c.github.io/performance-timeline/#dom-performanceobserver + fn GetEntriesByName( + &self, + name: DOMString, + entry_type: Option<DOMString>, + ) -> Vec<DomRoot<PerformanceEntry>> { + self.entries + .borrow() + .get_entries_by_name_and_type(Some(name), entry_type) + } +} diff --git a/components/script/dom/performancepainttiming.rs b/components/script/dom/performancepainttiming.rs new file mode 100644 index 00000000000..2dd3c27f1f1 --- /dev/null +++ b/components/script/dom/performancepainttiming.rs @@ -0,0 +1,50 @@ +/* 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 crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::performanceentry::PerformanceEntry; +use dom_struct::dom_struct; +use metrics::ToMs; +use script_traits::ProgressiveWebMetricType; + +#[dom_struct] +pub struct PerformancePaintTiming { + entry: PerformanceEntry, +} + +impl PerformancePaintTiming { + fn new_inherited( + metric_type: ProgressiveWebMetricType, + start_time: u64, + ) -> PerformancePaintTiming { + let name = match metric_type { + ProgressiveWebMetricType::FirstPaint => DOMString::from("first-paint"), + ProgressiveWebMetricType::FirstContentfulPaint => { + DOMString::from("first-contentful-paint") + }, + _ => DOMString::from(""), + }; + PerformancePaintTiming { + entry: PerformanceEntry::new_inherited( + name, + DOMString::from("paint"), + start_time.to_ms(), + 0., + ), + } + } + + #[allow(unrooted_must_root)] + pub fn new( + global: &GlobalScope, + metric_type: ProgressiveWebMetricType, + start_time: u64, + ) -> DomRoot<PerformancePaintTiming> { + let entry = PerformancePaintTiming::new_inherited(metric_type, start_time); + reflect_dom_object(Box::new(entry), global) + } +} diff --git a/components/script/dom/performanceresourcetiming.rs b/components/script/dom/performanceresourcetiming.rs new file mode 100644 index 00000000000..c98b5140f60 --- /dev/null +++ b/components/script/dom/performanceresourcetiming.rs @@ -0,0 +1,249 @@ +/* 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 crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; +use crate::dom::bindings::codegen::Bindings::PerformanceResourceTimingBinding::PerformanceResourceTimingMethods; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::performance::reduce_timing_resolution; +use crate::dom::performanceentry::PerformanceEntry; +use dom_struct::dom_struct; +use net_traits::ResourceFetchTiming; +use servo_url::ServoUrl; + +// TODO UA may choose to limit how many resources are included as PerformanceResourceTiming objects +// recommended minimum is 150, can be changed by setResourceTimingBufferSize in performance +// https://w3c.github.io/resource-timing/#sec-extensions-performance-interface + +// TODO Cross origin resources MUST BE INCLUDED as PerformanceResourceTiming objects +// https://w3c.github.io/resource-timing/#sec-cross-origin-resources + +// TODO CSS, Beacon +#[derive(Debug, JSTraceable, MallocSizeOf, PartialEq)] +pub enum InitiatorType { + LocalName(String), + Navigation, + XMLHttpRequest, + Fetch, + Other, +} + +#[dom_struct] +pub struct PerformanceResourceTiming { + entry: PerformanceEntry, + initiator_type: InitiatorType, + next_hop: Option<DOMString>, + worker_start: f64, + redirect_start: f64, + redirect_end: f64, + fetch_start: f64, + domain_lookup_start: f64, + domain_lookup_end: f64, + connect_start: f64, + connect_end: f64, + secure_connection_start: f64, + request_start: f64, + response_start: f64, + response_end: f64, + transfer_size: u64, //size in octets + encoded_body_size: u64, //size in octets + decoded_body_size: u64, //size in octets +} + +// TODO(#21269): next_hop +// TODO(#21264): worker_start +// TODO(#21258): fetch_start +// TODO(#21259): domain_lookup_start +// TODO(#21260): domain_lookup_end +// TODO(#21261): connect_start +// TODO(#21262): connect_end +impl PerformanceResourceTiming { + pub fn new_inherited( + url: ServoUrl, + initiator_type: InitiatorType, + next_hop: Option<DOMString>, + fetch_start: f64, + ) -> PerformanceResourceTiming { + let entry_type = if initiator_type == InitiatorType::Navigation { + DOMString::from("navigation") + } else { + DOMString::from("resource") + }; + PerformanceResourceTiming { + entry: PerformanceEntry::new_inherited( + DOMString::from(url.into_string()), + entry_type, + 0., + 0., + ), + initiator_type: initiator_type, + next_hop: next_hop, + worker_start: 0., + redirect_start: 0., + redirect_end: 0., + fetch_start: fetch_start, + domain_lookup_end: 0., + domain_lookup_start: 0., + connect_start: 0., + connect_end: 0., + secure_connection_start: 0., + request_start: 0., + response_start: 0., + response_end: 0., + transfer_size: 0, + encoded_body_size: 0, + decoded_body_size: 0, + } + } + + //TODO fetch start should be in RFT + #[allow(unrooted_must_root)] + fn from_resource_timing( + url: ServoUrl, + initiator_type: InitiatorType, + next_hop: Option<DOMString>, + resource_timing: &ResourceFetchTiming, + ) -> PerformanceResourceTiming { + PerformanceResourceTiming { + entry: PerformanceEntry::new_inherited( + DOMString::from(url.into_string()), + DOMString::from("resource"), + resource_timing.start_time as f64, + resource_timing.response_end as f64 - resource_timing.start_time as f64, + ), + initiator_type: initiator_type, + next_hop: next_hop, + worker_start: 0., + redirect_start: resource_timing.redirect_start as f64, + redirect_end: resource_timing.redirect_end as f64, + fetch_start: resource_timing.fetch_start as f64, + domain_lookup_start: resource_timing.domain_lookup_start as f64, + //TODO (#21260) + domain_lookup_end: 0., + connect_start: resource_timing.connect_start as f64, + connect_end: resource_timing.connect_end as f64, + secure_connection_start: resource_timing.secure_connection_start as f64, + request_start: resource_timing.request_start as f64, + response_start: resource_timing.response_start as f64, + response_end: resource_timing.response_end as f64, + transfer_size: 0, + encoded_body_size: 0, + decoded_body_size: 0, + } + } + + pub fn new( + global: &GlobalScope, + url: ServoUrl, + initiator_type: InitiatorType, + next_hop: Option<DOMString>, + resource_timing: &ResourceFetchTiming, + ) -> DomRoot<PerformanceResourceTiming> { + reflect_dom_object( + Box::new(PerformanceResourceTiming::from_resource_timing( + url, + initiator_type, + next_hop, + resource_timing, + )), + global, + ) + } +} + +// https://w3c.github.io/resource-timing/ +impl PerformanceResourceTimingMethods for PerformanceResourceTiming { + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-initiatortype + fn InitiatorType(&self) -> DOMString { + match self.initiator_type { + InitiatorType::LocalName(ref n) => DOMString::from(n.clone()), + InitiatorType::Navigation => DOMString::from("navigation"), + InitiatorType::XMLHttpRequest => DOMString::from("xmlhttprequest"), + InitiatorType::Fetch => DOMString::from("fetch"), + InitiatorType::Other => DOMString::from("other"), + } + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-nexthopprotocol + // returns the ALPN protocol ID of the network protocol used to fetch the resource + // when a proxy is configured + fn NextHopProtocol(&self) -> DOMString { + match self.next_hop { + Some(ref protocol) => DOMString::from(protocol.clone()), + None => DOMString::from(""), + } + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-domainlookupstart + fn DomainLookupStart(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.domain_lookup_start) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-domainlookupend + fn DomainLookupEnd(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.domain_lookup_end) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-secureconnectionstart + fn SecureConnectionStart(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.secure_connection_start) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-transfersize + fn TransferSize(&self) -> u64 { + self.transfer_size + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-encodedbodysize + fn EncodedBodySize(&self) -> u64 { + self.encoded_body_size + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-decodedbodysize + fn DecodedBodySize(&self) -> u64 { + self.decoded_body_size + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-requeststart + fn RequestStart(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.request_start) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-redirectstart + fn RedirectStart(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.redirect_start) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-redirectend + fn RedirectEnd(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.redirect_end) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-responsestart + fn ResponseStart(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.response_start) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-fetchstart + fn FetchStart(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.fetch_start) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-connectstart + fn ConnectStart(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.connect_start) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-connectend + fn ConnectEnd(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.connect_end) + } + + // https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-responseend + fn ResponseEnd(&self) -> DOMHighResTimeStamp { + reduce_timing_resolution(self.response_end) + } +} diff --git a/components/script/dom/performancetiming.rs b/components/script/dom/performancetiming.rs index 37001f5d15f..9f8a3a8b5f2 100644 --- a/components/script/dom/performancetiming.rs +++ b/components/script/dom/performancetiming.rs @@ -1,48 +1,50 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::PerformanceTimingBinding; -use dom::bindings::codegen::Bindings::PerformanceTimingBinding::PerformanceTimingMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::document::Document; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::PerformanceTimingBinding; +use crate::dom::bindings::codegen::Bindings::PerformanceTimingBinding::PerformanceTimingMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::document::Document; +use crate::dom::window::Window; use dom_struct::dom_struct; #[dom_struct] pub struct PerformanceTiming { reflector_: Reflector, navigation_start: u64, - navigation_start_precise: f64, - document: JS<Document>, + navigation_start_precise: u64, + document: Dom<Document>, } impl PerformanceTiming { - fn new_inherited(nav_start: u64, - nav_start_precise: f64, - document: &Document) - -> PerformanceTiming { + fn new_inherited( + nav_start: u64, + nav_start_precise: u64, + document: &Document, + ) -> PerformanceTiming { PerformanceTiming { reflector_: Reflector::new(), navigation_start: nav_start, navigation_start_precise: nav_start_precise, - document: JS::from_ref(document), + document: Dom::from_ref(document), } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, - navigation_start: u64, - navigation_start_precise: f64) - -> Root<PerformanceTiming> { - let timing = PerformanceTiming::new_inherited(navigation_start, - navigation_start_precise, - &window.Document()); - reflect_dom_object(box timing, - window, - PerformanceTimingBinding::Wrap) + pub fn new( + window: &Window, + navigation_start: u64, + navigation_start_precise: u64, + ) -> DomRoot<PerformanceTiming> { + let timing = PerformanceTiming::new_inherited( + navigation_start, + navigation_start_precise, + &window.Document(), + ); + reflect_dom_object(Box::new(timing), window) } } @@ -86,11 +88,16 @@ impl PerformanceTimingMethods for PerformanceTiming { fn LoadEventEnd(&self) -> u64 { self.document.get_load_event_end() } -} + // check-tidy: no specs after this line + // Servo-only timing for when top-level content (not iframes) is complete + fn TopLevelDomComplete(&self) -> u64 { + self.document.get_top_level_dom_complete() + } +} impl PerformanceTiming { - pub fn navigation_start_precise(&self) -> f64 { + pub fn navigation_start_precise(&self) -> u64 { self.navigation_start_precise } } diff --git a/components/script/dom/permissions.rs b/components/script/dom/permissions.rs index b5cab2ace90..b4e6bf8f5de 100644 --- a/components/script/dom/permissions.rs +++ b/components/script/dom/permissions.rs @@ -1,46 +1,51 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::PermissionStatusBinding::{PermissionDescriptor, PermissionName, PermissionState}; -use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatusMethods; -use dom::bindings::codegen::Bindings::PermissionsBinding::{self, PermissionsMethods}; -use dom::bindings::error::Error; -use dom::bindings::js::Root; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bluetooth::Bluetooth; -use dom::bluetoothpermissionresult::BluetoothPermissionResult; -use dom::globalscope::GlobalScope; -use dom::permissionstatus::PermissionStatus; -use dom::promise::Promise; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionDescriptor; +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatusMethods; +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{ + PermissionName, PermissionState, +}; +use crate::dom::bindings::codegen::Bindings::PermissionsBinding::PermissionsMethods; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bluetooth::Bluetooth; +use crate::dom::bluetoothpermissionresult::BluetoothPermissionResult; +use crate::dom::globalscope::GlobalScope; +use crate::dom::permissionstatus::PermissionStatus; +use crate::dom::promise::Promise; +use crate::realms::{AlreadyInRealm, InRealm}; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; +use embedder_traits::{self, EmbedderMsg, PermissionPrompt, PermissionRequest}; +use ipc_channel::ipc; use js::conversions::ConversionResult; -use js::jsapi::{JSContext, JSObject}; +use js::jsapi::JSObject; use js::jsval::{ObjectValue, UndefinedValue}; -#[cfg(target_os = "linux")] -use servo_config::opts; -use servo_config::prefs::PREFS; +use servo_config::pref; use std::rc::Rc; -#[cfg(target_os = "linux")] -use tinyfiledialogs::{self, MessageBoxIcon, YesNo}; - -#[cfg(target_os = "linux")] -const DIALOG_TITLE: &'static str = "Permission request dialog"; -const NONSECURE_DIALOG_MESSAGE: &'static str = "feature is only safe to use in secure context,\ - but servo can't guarantee\n that the current context is secure. Do you want to proceed and grant permission?"; -const REQUEST_DIALOG_MESSAGE: &'static str = "Do you want to grant permission for"; -const ROOT_DESC_CONVERSION_ERROR: &'static str = "Can't convert to an IDL value of type PermissionDescriptor"; pub trait PermissionAlgorithm { type Descriptor; type Status; - fn create_descriptor(cx: *mut JSContext, - permission_descriptor_obj: *mut JSObject) - -> Result<Self::Descriptor, Error>; - fn permission_query(cx: *mut JSContext, promise: &Rc<Promise>, - descriptor: &Self::Descriptor, status: &Self::Status); - fn permission_request(cx: *mut JSContext, promise: &Rc<Promise>, - descriptor: &Self::Descriptor, status: &Self::Status); + fn create_descriptor( + cx: JSContext, + permission_descriptor_obj: *mut JSObject, + ) -> Result<Self::Descriptor, Error>; + fn permission_query( + cx: JSContext, + promise: &Rc<Promise>, + descriptor: &Self::Descriptor, + status: &Self::Status, + ); + fn permission_request( + cx: JSContext, + promise: &Rc<Promise>, + descriptor: &Self::Descriptor, + status: &Self::Status, + ); fn permission_revoke(descriptor: &Self::Descriptor, status: &Self::Status); } @@ -63,33 +68,35 @@ impl Permissions { } } - pub fn new(global: &GlobalScope) -> Root<Permissions> { - reflect_dom_object(box Permissions::new_inherited(), - global, - PermissionsBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<Permissions> { + reflect_dom_object(Box::new(Permissions::new_inherited()), global) } - #[allow(unrooted_must_root)] // https://w3c.github.io/permissions/#dom-permissions-query // https://w3c.github.io/permissions/#dom-permissions-request // https://w3c.github.io/permissions/#dom-permissions-revoke - fn manipulate(&self, - op: Operation, - cx: *mut JSContext, - permissionDesc: *mut JSObject, - promise: Option<Rc<Promise>>) - -> Rc<Promise> { + #[allow(non_snake_case)] + fn manipulate( + &self, + op: Operation, + cx: JSContext, + permissionDesc: *mut JSObject, + promise: Option<Rc<Promise>>, + ) -> Rc<Promise> { // (Query, Request) Step 3. let p = match promise { Some(promise) => promise, - None => Promise::new(&self.global()), + None => { + let in_realm_proof = AlreadyInRealm::assert(&self.global()); + Promise::new_in_current_realm(&self.global(), InRealm::Already(&in_realm_proof)) + }, }; // (Query, Request, Revoke) Step 1. let root_desc = match Permissions::create_descriptor(cx, permissionDesc) { Ok(descriptor) => descriptor, Err(error) => { - p.reject_error(cx, error); + p.reject_error(error); return p; }, }; @@ -103,7 +110,7 @@ impl Permissions { let bluetooth_desc = match Bluetooth::create_descriptor(cx, permissionDesc) { Ok(descriptor) => descriptor, Err(error) => { - p.reject_error(cx, error); + p.reject_error(error); return p; }, }; @@ -113,18 +120,22 @@ impl Permissions { match &op { // (Request) Step 6 - 8. - &Operation::Request => Bluetooth::permission_request(cx, &p, &bluetooth_desc, &result), + &Operation::Request => { + Bluetooth::permission_request(cx, &p, &bluetooth_desc, &result) + }, // (Query) Step 6 - 7. - &Operation::Query => Bluetooth::permission_query(cx, &p, &bluetooth_desc, &result), + &Operation::Query => { + Bluetooth::permission_query(cx, &p, &bluetooth_desc, &result) + }, &Operation::Revoke => { // (Revoke) Step 3. let globalscope = self.global(); - globalscope.as_window() - .permission_state_invocation_results() - .borrow_mut() - .remove(&root_desc.name.to_string()); + globalscope + .permission_state_invocation_results() + .borrow_mut() + .remove(&root_desc.name.to_string()); // (Revoke) Step 4. Bluetooth::permission_revoke(&bluetooth_desc, &result) @@ -140,23 +151,23 @@ impl Permissions { // (Request) Step 7. The default algorithm always resolve // (Request) Step 8. - p.resolve_native(cx, &status); + p.resolve_native(&status); }, &Operation::Query => { // (Query) Step 6. Permissions::permission_query(cx, &p, &root_desc, &status); // (Query) Step 7. - p.resolve_native(cx, &status); + p.resolve_native(&status); }, &Operation::Revoke => { // (Revoke) Step 3. let globalscope = self.global(); - globalscope.as_window() - .permission_state_invocation_results() - .borrow_mut() - .remove(&root_desc.name.to_string()); + globalscope + .permission_state_invocation_results() + .borrow_mut() + .remove(&root_desc.name.to_string()); // (Revoke) Step 4. Permissions::permission_revoke(&root_desc, &status); @@ -174,25 +185,20 @@ impl Permissions { } } +#[allow(non_snake_case)] impl PermissionsMethods for Permissions { - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] // https://w3c.github.io/permissions/#dom-permissions-query - unsafe fn Query(&self, cx: *mut JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> { + fn Query(&self, cx: JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> { self.manipulate(Operation::Query, cx, permissionDesc, None) } - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] // https://w3c.github.io/permissions/#dom-permissions-request - unsafe fn Request(&self, cx: *mut JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> { + fn Request(&self, cx: JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> { self.manipulate(Operation::Request, cx, permissionDesc, None) } - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] // https://w3c.github.io/permissions/#dom-permissions-revoke - unsafe fn Revoke(&self, cx: *mut JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> { + fn Revoke(&self, cx: JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> { self.manipulate(Operation::Revoke, cx, permissionDesc, None) } } @@ -201,35 +207,39 @@ impl PermissionAlgorithm for Permissions { type Descriptor = PermissionDescriptor; type Status = PermissionStatus; - #[allow(unsafe_code)] - fn create_descriptor(cx: *mut JSContext, - permission_descriptor_obj: *mut JSObject) - -> Result<PermissionDescriptor, Error> { - rooted!(in(cx) let mut property = UndefinedValue()); - property.handle_mut().set(ObjectValue(permission_descriptor_obj)); - unsafe { - match PermissionDescriptor::new(cx, property.handle()) { - Ok(ConversionResult::Success(descriptor)) => Ok(descriptor), - Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())), - Err(_) => Err(Error::Type(String::from(ROOT_DESC_CONVERSION_ERROR))), - } + fn create_descriptor( + cx: JSContext, + permission_descriptor_obj: *mut JSObject, + ) -> Result<PermissionDescriptor, Error> { + rooted!(in(*cx) let mut property = UndefinedValue()); + property + .handle_mut() + .set(ObjectValue(permission_descriptor_obj)); + match PermissionDescriptor::new(cx, property.handle()) { + Ok(ConversionResult::Success(descriptor)) => Ok(descriptor), + Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())), + Err(_) => Err(Error::JSFailed), } } // https://w3c.github.io/permissions/#boolean-permission-query-algorithm - fn permission_query(_cx: *mut JSContext, - _promise: &Rc<Promise>, - _descriptor: &PermissionDescriptor, - status: &PermissionStatus) { + fn permission_query( + _cx: JSContext, + _promise: &Rc<Promise>, + _descriptor: &PermissionDescriptor, + status: &PermissionStatus, + ) { // Step 1. status.set_state(get_descriptor_permission_state(status.get_query(), None)); } // https://w3c.github.io/permissions/#boolean-permission-request-algorithm - fn permission_request(cx: *mut JSContext, - promise: &Rc<Promise>, - descriptor: &PermissionDescriptor, - status: &PermissionStatus) { + fn permission_request( + cx: JSContext, + promise: &Rc<Promise>, + descriptor: &PermissionDescriptor, + status: &PermissionStatus, + ) { // Step 1. Permissions::permission_query(cx, promise, descriptor, status); @@ -237,15 +247,16 @@ impl PermissionAlgorithm for Permissions { // Step 3. PermissionState::Prompt => { let perm_name = status.get_query(); + let prompt = + PermissionPrompt::Request(embedder_traits::PermissionName::from(perm_name)); + // https://w3c.github.io/permissions/#request-permission-to-use (Step 3 - 4) - let state = - prompt_user(&format!("{} {} ?", REQUEST_DIALOG_MESSAGE, perm_name.clone())); - - let globalscope = GlobalScope::current(); - globalscope.as_window() - .permission_state_invocation_results() - .borrow_mut() - .insert(perm_name.to_string(), state); + let globalscope = GlobalScope::current().expect("No current global object"); + let state = prompt_user_from_embedder(prompt, &globalscope); + globalscope + .permission_state_invocation_results() + .borrow_mut() + .insert(perm_name.to_string(), state); }, // Step 2. @@ -260,13 +271,14 @@ impl PermissionAlgorithm for Permissions { } // https://w3c.github.io/permissions/#permission-state -pub fn get_descriptor_permission_state(permission_name: PermissionName, - env_settings_obj: Option<&GlobalScope>) - -> PermissionState { +pub fn get_descriptor_permission_state( + permission_name: PermissionName, + env_settings_obj: Option<&GlobalScope>, +) -> PermissionState { // Step 1. - let settings = match env_settings_obj { - Some(env_settings_obj) => Root::from_ref(env_settings_obj), - None => GlobalScope::current(), + let globalscope = match env_settings_obj { + Some(env_settings_obj) => DomRoot::from_ref(env_settings_obj), + None => GlobalScope::current().expect("No current global object"), }; // Step 2. @@ -274,60 +286,43 @@ pub fn get_descriptor_permission_state(permission_name: PermissionName, // The current solution is a workaround with a message box to warn about this, // if the feature is not allowed in non-secure contexcts, // and let the user decide to grant the permission or not. - let state = match allowed_in_nonsecure_contexts(&permission_name) { - true => PermissionState::Prompt, - false => { - match PREFS.get("dom.permissions.testing.allowed_in_nonsecure_contexts").as_boolean().unwrap_or(false) { - true => PermissionState::Granted, - false => { - settings.as_window() - .permission_state_invocation_results() - .borrow_mut() - .remove(&permission_name.to_string()); - prompt_user(&format!("The {} {}", permission_name, NONSECURE_DIALOG_MESSAGE)) - }, - } - }, + let state = if allowed_in_nonsecure_contexts(&permission_name) { + PermissionState::Prompt + } else { + if pref!(dom.permissions.testing.allowed_in_nonsecure_contexts) { + PermissionState::Granted + } else { + globalscope + .permission_state_invocation_results() + .borrow_mut() + .remove(&permission_name.to_string()); + + prompt_user_from_embedder( + PermissionPrompt::Insecure(embedder_traits::PermissionName::from(permission_name)), + &globalscope, + ) + } }; // Step 3. - if let Some(prev_result) = settings.as_window() - .permission_state_invocation_results() - .borrow() - .get(&permission_name.to_string()) { + if let Some(prev_result) = globalscope + .permission_state_invocation_results() + .borrow() + .get(&permission_name.to_string()) + { return prev_result.clone(); } // Store the invocation result - settings.as_window() - .permission_state_invocation_results() - .borrow_mut() - .insert(permission_name.to_string(), state); + globalscope + .permission_state_invocation_results() + .borrow_mut() + .insert(permission_name.to_string(), state); // Step 4. state } -#[cfg(target_os = "linux")] -fn prompt_user(message: &str) -> PermissionState { - if opts::get().headless { - return PermissionState::Denied; - } - match tinyfiledialogs::message_box_yes_no(DIALOG_TITLE, - message, - MessageBoxIcon::Question, - YesNo::No) { - YesNo::Yes => PermissionState::Granted, - YesNo::No => PermissionState::Denied, - } -} - -#[cfg(not(target_os = "linux"))] -fn prompt_user(_message: &str) -> PermissionState { - // TODO popup only supported on linux - PermissionState::Denied -} - // https://w3c.github.io/permissions/#allowed-in-non-secure-contexts fn allowed_in_nonsecure_contexts(permission_name: &PermissionName) -> bool { match *permission_name { @@ -355,3 +350,40 @@ fn allowed_in_nonsecure_contexts(permission_name: &PermissionName) -> bool { PermissionName::Persistent_storage => false, } } + +fn prompt_user_from_embedder(prompt: PermissionPrompt, gs: &GlobalScope) -> PermissionState { + let (sender, receiver) = ipc::channel().expect("Failed to create IPC channel!"); + gs.send_to_embedder(EmbedderMsg::PromptPermission(prompt, sender)); + + match receiver.recv() { + Ok(PermissionRequest::Granted) => PermissionState::Granted, + Ok(PermissionRequest::Denied) => PermissionState::Denied, + Err(e) => { + warn!( + "Failed to receive permission state from embedder ({:?}).", + e + ); + PermissionState::Denied + }, + } +} + +impl From<PermissionName> for embedder_traits::PermissionName { + fn from(permission_name: PermissionName) -> Self { + match permission_name { + PermissionName::Geolocation => embedder_traits::PermissionName::Geolocation, + PermissionName::Notifications => embedder_traits::PermissionName::Notifications, + PermissionName::Push => embedder_traits::PermissionName::Push, + PermissionName::Midi => embedder_traits::PermissionName::Midi, + PermissionName::Camera => embedder_traits::PermissionName::Camera, + PermissionName::Microphone => embedder_traits::PermissionName::Microphone, + PermissionName::Speaker => embedder_traits::PermissionName::Speaker, + PermissionName::Device_info => embedder_traits::PermissionName::DeviceInfo, + PermissionName::Background_sync => embedder_traits::PermissionName::BackgroundSync, + PermissionName::Bluetooth => embedder_traits::PermissionName::Bluetooth, + PermissionName::Persistent_storage => { + embedder_traits::PermissionName::PersistentStorage + }, + } + } +} diff --git a/components/script/dom/permissionstatus.rs b/components/script/dom/permissionstatus.rs index 27d353bf0ed..8f8b96f23de 100644 --- a/components/script/dom/permissionstatus.rs +++ b/components/script/dom/permissionstatus.rs @@ -1,15 +1,16 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::PermissionStatusBinding::{self, PermissionDescriptor, PermissionName}; -use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionState; -use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatusMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionState; +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionStatusMethods; +use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{ + PermissionDescriptor, PermissionName, +}; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; use std::cell::Cell; use std::fmt::{self, Display, Formatter}; @@ -31,10 +32,11 @@ impl PermissionStatus { } } - pub fn new(global: &GlobalScope, query: &PermissionDescriptor) -> Root<PermissionStatus> { - reflect_dom_object(box PermissionStatus::new_inherited(query.name), - global, - PermissionStatusBinding::Wrap) + pub fn new(global: &GlobalScope, query: &PermissionDescriptor) -> DomRoot<PermissionStatus> { + reflect_dom_object( + Box::new(PermissionStatus::new_inherited(query.name)), + global, + ) } pub fn set_state(&self, state: PermissionState) { @@ -53,7 +55,7 @@ impl PermissionStatusMethods for PermissionStatus { } // https://w3c.github.io/permissions/#dom-permissionstatus-onchange - event_handler!(onchange, GetOnchange, SetOnchange); + event_handler!(change, GetOnchange, SetOnchange); } impl Display for PermissionName { diff --git a/components/script/dom/plugin.rs b/components/script/dom/plugin.rs index 8e3af814ec9..83c8e252556 100644 --- a/components/script/dom/plugin.rs +++ b/components/script/dom/plugin.rs @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::PluginBinding::PluginMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::Reflector; -use dom::bindings::str::DOMString; -use dom::mimetype::MimeType; +use crate::dom::bindings::codegen::Bindings::PluginBinding::PluginMethods; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::mimetype::MimeType; use dom_struct::dom_struct; #[dom_struct] @@ -36,22 +36,22 @@ impl PluginMethods for Plugin { } // https://html.spec.whatwg.org/multipage/#dom-plugin-item - fn Item(&self, _index: u32) -> Option<Root<MimeType>> { + fn Item(&self, _index: u32) -> Option<DomRoot<MimeType>> { unreachable!() } // https://html.spec.whatwg.org/multipage/#dom-plugin-nameditem - fn NamedItem(&self, _name: DOMString) -> Option<Root<MimeType>> { + fn NamedItem(&self, _name: DOMString) -> Option<DomRoot<MimeType>> { unreachable!() } // https://html.spec.whatwg.org/multipage/#dom-plugin-item - fn IndexedGetter(&self, _index: u32) -> Option<Root<MimeType>> { + fn IndexedGetter(&self, _index: u32) -> Option<DomRoot<MimeType>> { unreachable!() } // check-tidy: no specs after this line - fn NamedGetter(&self, _name: DOMString) -> Option<Root<MimeType>> { + fn NamedGetter(&self, _name: DOMString) -> Option<DomRoot<MimeType>> { unreachable!() } diff --git a/components/script/dom/pluginarray.rs b/components/script/dom/pluginarray.rs index b359c2b8368..b063d55fb02 100644 --- a/components/script/dom/pluginarray.rs +++ b/components/script/dom/pluginarray.rs @@ -1,14 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::PluginArrayBinding; -use dom::bindings::codegen::Bindings::PluginArrayBinding::PluginArrayMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::globalscope::GlobalScope; -use dom::plugin::Plugin; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::PluginArrayBinding::PluginArrayMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::plugin::Plugin; use dom_struct::dom_struct; #[dom_struct] @@ -19,21 +18,18 @@ pub struct PluginArray { impl PluginArray { pub fn new_inherited() -> PluginArray { PluginArray { - reflector_: Reflector::new() + reflector_: Reflector::new(), } } - pub fn new(global: &GlobalScope) -> Root<PluginArray> { - reflect_dom_object(box PluginArray::new_inherited(), - global, - PluginArrayBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<PluginArray> { + reflect_dom_object(Box::new(PluginArray::new_inherited()), global) } } impl PluginArrayMethods for PluginArray { // https://html.spec.whatwg.org/multipage/#dom-pluginarray-refresh - fn Refresh(&self, _reload: bool) { - } + fn Refresh(&self, _reload: bool) {} // https://html.spec.whatwg.org/multipage/#dom-pluginarray-length fn Length(&self) -> u32 { @@ -41,22 +37,22 @@ impl PluginArrayMethods for PluginArray { } // https://html.spec.whatwg.org/multipage/#dom-pluginarray-item - fn Item(&self, _index: u32) -> Option<Root<Plugin>> { + fn Item(&self, _index: u32) -> Option<DomRoot<Plugin>> { None } // https://html.spec.whatwg.org/multipage/#dom-pluginarray-nameditem - fn NamedItem(&self, _name: DOMString) -> Option<Root<Plugin>> { + fn NamedItem(&self, _name: DOMString) -> Option<DomRoot<Plugin>> { None } // https://html.spec.whatwg.org/multipage/#dom-pluginarray-item - fn IndexedGetter(&self, _index: u32) -> Option<Root<Plugin>> { + fn IndexedGetter(&self, _index: u32) -> Option<DomRoot<Plugin>> { None } // check-tidy: no specs after this line - fn NamedGetter(&self, _name: DOMString) -> Option<Root<Plugin>> { + fn NamedGetter(&self, _name: DOMString) -> Option<DomRoot<Plugin>> { None } diff --git a/components/script/dom/popstateevent.rs b/components/script/dom/popstateevent.rs index c5e63d4ea50..851064df389 100644 --- a/components/script/dom/popstateevent.rs +++ b/components/script/dom/popstateevent.rs @@ -1,28 +1,31 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::PopStateEventBinding; -use dom::bindings::codegen::Bindings::PopStateEventBinding::PopStateEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::bindings::trace::RootedTraceableBox; -use dom::event::Event; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::PopStateEventBinding; +use crate::dom::bindings::codegen::Bindings::PopStateEventBinding::PopStateEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::window::Window; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{Heap, HandleValue, JSContext}; +use js::jsapi::Heap; use js::jsval::JSVal; +use js::rust::HandleValue; use servo_atoms::Atom; // https://html.spec.whatwg.org/multipage/#the-popstateevent-interface #[dom_struct] pub struct PopStateEvent { event: Event, - #[ignore_heap_size_of = "Defined in rust-mozjs"] + #[ignore_malloc_size_of = "Defined in rust-mozjs"] state: Heap<JSVal>, } @@ -34,18 +37,17 @@ impl PopStateEvent { } } - pub fn new_uninitialized(window: &Window) -> Root<PopStateEvent> { - reflect_dom_object(box PopStateEvent::new_inherited(), - window, - PopStateEventBinding::Wrap) + pub fn new_uninitialized(window: &Window) -> DomRoot<PopStateEvent> { + reflect_dom_object(Box::new(PopStateEvent::new_inherited()), window) } - pub fn new(window: &Window, - type_: Atom, - bubbles: bool, - cancelable: bool, - state: HandleValue) - -> Root<PopStateEvent> { + pub fn new( + window: &Window, + type_: Atom, + bubbles: bool, + cancelable: bool, + state: HandleValue, + ) -> DomRoot<PopStateEvent> { let ev = PopStateEvent::new_uninitialized(window); ev.state.set(state.get()); { @@ -55,22 +57,30 @@ impl PopStateEvent { ev } - pub fn Constructor(window: &Window, - type_: DOMString, - init: RootedTraceableBox<PopStateEventBinding::PopStateEventInit>) - -> Fallible<Root<PopStateEvent>> { - Ok(PopStateEvent::new(window, - Atom::from(type_), - init.parent.bubbles, - init.parent.cancelable, - init.state.handle())) + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: RootedTraceableBox<PopStateEventBinding::PopStateEventInit>, + ) -> Fallible<DomRoot<PopStateEvent>> { + Ok(PopStateEvent::new( + window, + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + init.state.handle(), + )) + } + + pub fn dispatch_jsval(target: &EventTarget, window: &Window, state: HandleValue) { + let event = PopStateEvent::new(window, atom!("popstate"), false, false, state); + event.upcast::<Event>().fire(target); } } impl PopStateEventMethods for PopStateEvent { - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-popstateevent-state - unsafe fn State(&self, _cx: *mut JSContext) -> JSVal { + fn State(&self, _cx: JSContext) -> JSVal { self.state.get() } diff --git a/components/script/dom/processinginstruction.rs b/components/script/dom/processinginstruction.rs index 31e6737a977..9edbf1a0a29 100644 --- a/components/script/dom/processinginstruction.rs +++ b/components/script/dom/processinginstruction.rs @@ -1,14 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::ProcessingInstructionBinding; -use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; -use dom::bindings::js::Root; -use dom::bindings::str::DOMString; -use dom::characterdata::CharacterData; -use dom::document::Document; -use dom::node::Node; +use crate::dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::characterdata::CharacterData; +use crate::dom::document::Document; +use crate::dom::node::Node; use dom_struct::dom_struct; /// An HTML processing instruction node. @@ -19,20 +18,29 @@ pub struct ProcessingInstruction { } impl ProcessingInstruction { - fn new_inherited(target: DOMString, data: DOMString, document: &Document) -> ProcessingInstruction { + fn new_inherited( + target: DOMString, + data: DOMString, + document: &Document, + ) -> ProcessingInstruction { ProcessingInstruction { characterdata: CharacterData::new_inherited(data, document), - target: target + target: target, } } - pub fn new(target: DOMString, data: DOMString, document: &Document) -> Root<ProcessingInstruction> { - Node::reflect_node(box ProcessingInstruction::new_inherited(target, data, document), - document, ProcessingInstructionBinding::Wrap) + pub fn new( + target: DOMString, + data: DOMString, + document: &Document, + ) -> DomRoot<ProcessingInstruction> { + Node::reflect_node( + Box::new(ProcessingInstruction::new_inherited(target, data, document)), + document, + ) } } - impl ProcessingInstruction { pub fn target(&self) -> &DOMString { &self.target diff --git a/components/script/dom/progressevent.rs b/components/script/dom/progressevent.rs index e5ca1af2ae1..32e6917a15d 100644 --- a/components/script/dom/progressevent.rs +++ b/components/script/dom/progressevent.rs @@ -1,17 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::ProgressEventBinding; -use dom::bindings::codegen::Bindings::ProgressEventBinding::ProgressEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::ProgressEventBinding; +use crate::dom::bindings::codegen::Bindings::ProgressEventBinding::ProgressEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; use servo_atoms::Atom; @@ -20,7 +20,7 @@ pub struct ProgressEvent { event: Event, length_computable: bool, loaded: u64, - total: u64 + total: u64, } impl ProgressEvent { @@ -29,34 +29,50 @@ impl ProgressEvent { event: Event::new_inherited(), length_computable: length_computable, loaded: loaded, - total: total + total: total, } } - pub fn new_uninitialized(global: &GlobalScope) -> Root<ProgressEvent> { - reflect_dom_object(box ProgressEvent::new_inherited(false, 0, 0), - global, - ProgressEventBinding::Wrap) - } - pub fn new(global: &GlobalScope, type_: Atom, - can_bubble: EventBubbles, cancelable: EventCancelable, - length_computable: bool, loaded: u64, total: u64) -> Root<ProgressEvent> { - let ev = reflect_dom_object(box ProgressEvent::new_inherited(length_computable, loaded, total), - global, - ProgressEventBinding::Wrap); + pub fn new( + global: &GlobalScope, + type_: Atom, + can_bubble: EventBubbles, + cancelable: EventCancelable, + length_computable: bool, + loaded: u64, + total: u64, + ) -> DomRoot<ProgressEvent> { + let ev = reflect_dom_object( + Box::new(ProgressEvent::new_inherited( + length_computable, + loaded, + total, + )), + global, + ); { let event = ev.upcast::<Event>(); event.init_event(type_, bool::from(can_bubble), bool::from(cancelable)); } ev } - pub fn Constructor(global: &GlobalScope, - type_: DOMString, - init: &ProgressEventBinding::ProgressEventInit) - -> Fallible<Root<ProgressEvent>> { + + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + type_: DOMString, + init: &ProgressEventBinding::ProgressEventInit, + ) -> Fallible<DomRoot<ProgressEvent>> { let bubbles = EventBubbles::from(init.parent.bubbles); let cancelable = EventCancelable::from(init.parent.cancelable); - let ev = ProgressEvent::new(global, Atom::from(type_), bubbles, cancelable, - init.lengthComputable, init.loaded, init.total); + let ev = ProgressEvent::new( + global, + Atom::from(type_), + bubbles, + cancelable, + init.lengthComputable, + init.loaded, + init.total, + ); Ok(ev) } } diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs index 13689023462..18be293c625 100644 --- a/components/script/dom/promise.rs +++ b/components/script/dom/promise.rs @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Native representation of JS Promise values. //! @@ -11,207 +11,230 @@ //! native Promise values that refer to the same JS value yet are distinct native objects //! (ie. address equality for the native objects is meaningless). -use dom::bindings::callback::CallbackContainer; -use dom::bindings::codegen::Bindings::PromiseBinding::AnyCallback; -use dom::bindings::conversions::root_from_object; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::reflector::{DomObject, MutDomObject, Reflector}; -use dom::bindings::utils::AsCCharPtrPtr; -use dom::globalscope::GlobalScope; -use dom::promisenativehandler::PromiseNativeHandler; +use crate::dom::bindings::conversions::root_from_object; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{DomObject, MutDomObject, Reflector}; +use crate::dom::bindings::settings_stack::AutoEntryScript; +use crate::dom::bindings::utils::AsCCharPtrPtr; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promisenativehandler::PromiseNativeHandler; +use crate::realms::{enter_realm, AlreadyInRealm, InRealm}; +use crate::script_runtime::JSContext as SafeJSContext; +use crate::script_thread::ScriptThread; use dom_struct::dom_struct; use js::conversions::ToJSValConvertible; -use js::jsapi::{CallOriginalPromiseResolve, CallOriginalPromiseReject, CallOriginalPromiseThen}; -use js::jsapi::{JSAutoCompartment, CallArgs, JS_GetFunctionObject, JS_NewFunction}; -use js::jsapi::{JSContext, HandleValue, HandleObject, IsPromiseObject, GetFunctionNativeReserved}; -use js::jsapi::{JS_ClearPendingException, JSObject, AddRawValueRoot, RemoveRawValueRoot, PromiseState}; -use js::jsapi::{MutableHandleObject, NewPromiseObject, ResolvePromise, RejectPromise, GetPromiseState}; -use js::jsapi::{SetFunctionNativeReserved, NewFunctionWithReserved, AddPromiseReactions}; -use js::jsapi::Heap; -use js::jsval::{JSVal, UndefinedValue, ObjectValue, Int32Value}; +use js::jsapi::{AddRawValueRoot, CallArgs, GetFunctionNativeReserved}; +use js::jsapi::{Heap, JS_ClearPendingException}; +use js::jsapi::{JSAutoRealm, JSContext, JSObject, JS_GetFunctionObject}; +use js::jsapi::{JS_NewFunction, NewFunctionWithReserved}; +use js::jsapi::{PromiseState, PromiseUserInputEventHandlingState}; +use js::jsapi::{RemoveRawValueRoot, SetFunctionNativeReserved}; +use js::jsval::{Int32Value, JSVal, ObjectValue, UndefinedValue}; +use js::rust::wrappers::{ + AddPromiseReactions, CallOriginalPromiseReject, CallOriginalPromiseResolve, +}; +use js::rust::wrappers::{GetPromiseState, IsPromiseObject, NewPromiseObject, RejectPromise}; +use js::rust::wrappers::{ResolvePromise, SetPromiseUserInputEventHandlingState}; +use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime}; use std::ptr; use std::rc::Rc; #[dom_struct] +#[unrooted_must_root_lint::allow_unrooted_in_rc] pub struct Promise { reflector: Reflector, /// Since Promise values are natively reference counted without the knowledge of /// the SpiderMonkey GC, an explicit root for the reflector is stored while any /// native instance exists. This ensures that the reflector will never be GCed /// while native code could still interact with its native representation. - #[ignore_heap_size_of = "SM handles JS values"] + #[ignore_malloc_size_of = "SM handles JS values"] permanent_js_root: Heap<JSVal>, } /// Private helper to enable adding new methods to Rc<Promise>. trait PromiseHelper { - #[allow(unsafe_code)] - unsafe fn initialize(&self, cx: *mut JSContext); + fn initialize(&self, cx: SafeJSContext); } impl PromiseHelper for Rc<Promise> { #[allow(unsafe_code)] - unsafe fn initialize(&self, cx: *mut JSContext) { + fn initialize(&self, cx: SafeJSContext) { let obj = self.reflector().get_jsobject(); self.permanent_js_root.set(ObjectValue(*obj)); - assert!(AddRawValueRoot(cx, - self.permanent_js_root.get_unsafe(), - b"Promise::root\0".as_c_char_ptr())); + unsafe { + assert!(AddRawValueRoot( + *cx, + self.permanent_js_root.get_unsafe(), + b"Promise::root\0".as_c_char_ptr() + )); + } } } impl Drop for Promise { #[allow(unsafe_code)] fn drop(&mut self) { - let cx = self.global().get_cx(); unsafe { + let object = self.permanent_js_root.get().to_object(); + assert!(!object.is_null()); + let cx = Runtime::get(); + assert!(!cx.is_null()); RemoveRawValueRoot(cx, self.permanent_js_root.get_unsafe()); } } } impl Promise { - #[allow(unsafe_code)] pub fn new(global: &GlobalScope) -> Rc<Promise> { + let realm = enter_realm(&*global); + let comp = InRealm::Entered(&realm); + Promise::new_in_current_realm(global, comp) + } + + pub fn new_in_current_realm(global: &GlobalScope, _comp: InRealm) -> Rc<Promise> { let cx = global.get_cx(); - rooted!(in(cx) let mut obj = ptr::null_mut()); - unsafe { - Promise::create_js_promise(cx, HandleObject::null(), obj.handle_mut()); - Promise::new_with_js_promise(obj.handle(), cx) - } + rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>()); + Promise::create_js_promise(cx, obj.handle_mut()); + Promise::new_with_js_promise(obj.handle(), cx) } - #[allow(unsafe_code, unrooted_must_root)] + #[allow(unsafe_code)] pub fn duplicate(&self) -> Rc<Promise> { let cx = self.global().get_cx(); - unsafe { - Promise::new_with_js_promise(self.reflector().get_jsobject(), cx) - } + Promise::new_with_js_promise(self.reflector().get_jsobject(), cx) } #[allow(unsafe_code, unrooted_must_root)] - unsafe fn new_with_js_promise(obj: HandleObject, cx: *mut JSContext) -> Rc<Promise> { - assert!(IsPromiseObject(obj)); - let promise = Promise { - reflector: Reflector::new(), - permanent_js_root: Heap::default(), - }; - let mut promise = Rc::new(promise); - Rc::get_mut(&mut promise).unwrap().init_reflector(obj.get()); - promise.initialize(cx); - promise + pub fn new_with_js_promise(obj: HandleObject, cx: SafeJSContext) -> Rc<Promise> { + unsafe { + assert!(IsPromiseObject(obj)); + let promise = Promise { + reflector: Reflector::new(), + permanent_js_root: Heap::default(), + }; + let promise = Rc::new(promise); + promise.init_reflector(obj.get()); + promise.initialize(cx); + promise + } } #[allow(unsafe_code)] - unsafe fn create_js_promise(cx: *mut JSContext, proto: HandleObject, obj: MutableHandleObject) { - let do_nothing_func = JS_NewFunction(cx, Some(do_nothing_promise_executor), /* nargs = */ 2, - /* flags = */ 0, ptr::null()); - assert!(!do_nothing_func.is_null()); - rooted!(in(cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func)); - assert!(!do_nothing_obj.is_null()); - obj.set(NewPromiseObject(cx, do_nothing_obj.handle(), proto)); - assert!(!obj.is_null()); + fn create_js_promise(cx: SafeJSContext, mut obj: MutableHandleObject) { + unsafe { + let do_nothing_func = JS_NewFunction( + *cx, + Some(do_nothing_promise_executor), + /* nargs = */ 2, + /* flags = */ 0, + ptr::null(), + ); + assert!(!do_nothing_func.is_null()); + rooted!(in(*cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func)); + assert!(!do_nothing_obj.is_null()); + obj.set(NewPromiseObject(*cx, do_nothing_obj.handle())); + assert!(!obj.is_null()); + let is_user_interacting = if ScriptThread::is_user_interacting() { + PromiseUserInputEventHandlingState::HadUserInteractionAtCreation + } else { + PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation + }; + SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting); + } } #[allow(unrooted_must_root, unsafe_code)] - pub fn Resolve(global: &GlobalScope, - cx: *mut JSContext, - value: HandleValue) -> Fallible<Rc<Promise>> { - let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get()); - rooted!(in(cx) let p = unsafe { CallOriginalPromiseResolve(cx, value) }); + pub fn new_resolved( + global: &GlobalScope, + cx: SafeJSContext, + value: HandleValue, + ) -> Fallible<Rc<Promise>> { + let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get()); + rooted!(in(*cx) let p = unsafe { CallOriginalPromiseResolve(*cx, value) }); assert!(!p.handle().is_null()); - unsafe { - Ok(Promise::new_with_js_promise(p.handle(), cx)) - } + Ok(Promise::new_with_js_promise(p.handle(), cx)) } #[allow(unrooted_must_root, unsafe_code)] - pub fn Reject(global: &GlobalScope, - cx: *mut JSContext, - value: HandleValue) -> Fallible<Rc<Promise>> { - let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get()); - rooted!(in(cx) let p = unsafe { CallOriginalPromiseReject(cx, value) }); + pub fn new_rejected( + global: &GlobalScope, + cx: SafeJSContext, + value: HandleValue, + ) -> Fallible<Rc<Promise>> { + let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get()); + rooted!(in(*cx) let p = unsafe { CallOriginalPromiseReject(*cx, value) }); assert!(!p.handle().is_null()); - unsafe { - Ok(Promise::new_with_js_promise(p.handle(), cx)) - } + Ok(Promise::new_with_js_promise(p.handle(), cx)) } #[allow(unsafe_code)] - pub fn resolve_native<T>(&self, cx: *mut JSContext, val: &T) where T: ToJSValConvertible { - rooted!(in(cx) let mut v = UndefinedValue()); + pub fn resolve_native<T>(&self, val: &T) + where + T: ToJSValConvertible, + { + let cx = self.global().get_cx(); + let _ac = enter_realm(&*self); + rooted!(in(*cx) let mut v = UndefinedValue()); unsafe { - val.to_jsval(cx, v.handle_mut()); + val.to_jsval(*cx, v.handle_mut()); } self.resolve(cx, v.handle()); } #[allow(unrooted_must_root, unsafe_code)] - pub fn resolve(&self, cx: *mut JSContext, value: HandleValue) { + pub fn resolve(&self, cx: SafeJSContext, value: HandleValue) { unsafe { - if !ResolvePromise(cx, self.promise_obj(), value) { - JS_ClearPendingException(cx); + if !ResolvePromise(*cx, self.promise_obj(), value) { + JS_ClearPendingException(*cx); } } } #[allow(unsafe_code)] - pub fn reject_native<T>(&self, cx: *mut JSContext, val: &T) where T: ToJSValConvertible { - rooted!(in(cx) let mut v = UndefinedValue()); + pub fn reject_native<T>(&self, val: &T) + where + T: ToJSValConvertible, + { + let cx = self.global().get_cx(); + let _ac = enter_realm(&*self); + rooted!(in(*cx) let mut v = UndefinedValue()); unsafe { - val.to_jsval(cx, v.handle_mut()); + val.to_jsval(*cx, v.handle_mut()); } self.reject(cx, v.handle()); } #[allow(unsafe_code)] - pub fn reject_error(&self, cx: *mut JSContext, error: Error) { - rooted!(in(cx) let mut v = UndefinedValue()); + pub fn reject_error(&self, error: Error) { + let cx = self.global().get_cx(); + let _ac = enter_realm(&*self); + rooted!(in(*cx) let mut v = UndefinedValue()); unsafe { - error.to_jsval(cx, &self.global(), v.handle_mut()); + error.to_jsval(*cx, &self.global(), v.handle_mut()); } self.reject(cx, v.handle()); } #[allow(unrooted_must_root, unsafe_code)] - pub fn reject(&self, - cx: *mut JSContext, - value: HandleValue) { + pub fn reject(&self, cx: SafeJSContext, value: HandleValue) { unsafe { - if !RejectPromise(cx, self.promise_obj(), value) { - JS_ClearPendingException(cx); + if !RejectPromise(*cx, self.promise_obj(), value) { + JS_ClearPendingException(*cx); } } } - #[allow(unrooted_must_root, unsafe_code)] - pub fn then(&self, - cx: *mut JSContext, - _callee: HandleObject, - cb_resolve: AnyCallback, - cb_reject: AnyCallback, - result: MutableHandleObject) { - let promise = self.promise_obj(); - rooted!(in(cx) let resolve = cb_resolve.callback()); - rooted!(in(cx) let reject = cb_reject.callback()); - unsafe { - rooted!(in(cx) let res = - CallOriginalPromiseThen(cx, promise, resolve.handle(), reject.handle())); - result.set(*res); - } - } - #[allow(unsafe_code)] - pub fn is_settled(&self) -> bool { + pub fn is_fulfilled(&self) -> bool { let state = unsafe { GetPromiseState(self.promise_obj()) }; match state { PromiseState::Rejected | PromiseState::Fulfilled => true, - _ => false + _ => false, } } #[allow(unsafe_code)] - fn promise_obj(&self) -> HandleObject { + pub fn promise_obj(&self) -> HandleObject { let obj = self.reflector().get_jsobject(); unsafe { assert!(IsPromiseObject(obj)); @@ -220,30 +243,37 @@ impl Promise { } #[allow(unsafe_code)] - pub fn append_native_handler(&self, handler: &PromiseNativeHandler) { + pub fn append_native_handler(&self, handler: &PromiseNativeHandler, _comp: InRealm) { + let _ais = AutoEntryScript::new(&*handler.global()); let cx = self.global().get_cx(); - rooted!(in(cx) let resolve_func = - create_native_handler_function(cx, + rooted!(in(*cx) let resolve_func = + create_native_handler_function(*cx, handler.reflector().get_jsobject(), NativeHandlerTask::Resolve)); - rooted!(in(cx) let reject_func = - create_native_handler_function(cx, + rooted!(in(*cx) let reject_func = + create_native_handler_function(*cx, handler.reflector().get_jsobject(), NativeHandlerTask::Reject)); unsafe { - let ok = AddPromiseReactions(cx, - self.promise_obj(), - resolve_func.handle(), - reject_func.handle()); + let ok = AddPromiseReactions( + *cx, + self.promise_obj(), + resolve_func.handle(), + reject_func.handle(), + ); assert!(ok); } } } #[allow(unsafe_code)] -unsafe extern fn do_nothing_promise_executor(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool { +unsafe extern "C" fn do_nothing_promise_executor( + _cx: *mut JSContext, + argc: u32, + vp: *mut JSVal, +) -> bool { let args = CallArgs::from_vp(vp, argc); *args.rval() = UndefinedValue(); true @@ -259,18 +289,33 @@ enum NativeHandlerTask { } #[allow(unsafe_code)] -unsafe extern fn native_handler_callback(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool { +unsafe extern "C" fn native_handler_callback( + cx: *mut JSContext, + argc: u32, + vp: *mut JSVal, +) -> bool { + let cx = SafeJSContext::from_ptr(cx); + let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); + let args = CallArgs::from_vp(vp, argc); - rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER)); + rooted!(in(*cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER)); assert!(v.get().is_object()); - let handler = root_from_object::<PromiseNativeHandler>(v.to_object()) - .ok().expect("unexpected value for native handler in promise native handler callback"); + let handler = root_from_object::<PromiseNativeHandler>(v.to_object(), *cx) + .expect("unexpected value for native handler in promise native handler callback"); - rooted!(in(cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK)); + rooted!(in(*cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK)); match v.to_int32() { - v if v == NativeHandlerTask::Resolve as i32 => handler.resolved_callback(cx, args.get(0)), - v if v == NativeHandlerTask::Reject as i32 => handler.rejected_callback(cx, args.get(0)), + v if v == NativeHandlerTask::Resolve as i32 => handler.resolved_callback( + *cx, + HandleValue::from_raw(args.get(0)), + InRealm::Already(&in_realm_proof), + ), + v if v == NativeHandlerTask::Reject as i32 => handler.rejected_callback( + *cx, + HandleValue::from_raw(args.get(0)), + InRealm::Already(&in_realm_proof), + ), _ => panic!("unexpected native handler task value"), }; @@ -278,21 +323,19 @@ unsafe extern fn native_handler_callback(cx: *mut JSContext, argc: u32, vp: *mut } #[allow(unsafe_code)] -fn create_native_handler_function(cx: *mut JSContext, - holder: HandleObject, - task: NativeHandlerTask) -> *mut JSObject { +fn create_native_handler_function( + cx: *mut JSContext, + holder: HandleObject, + task: NativeHandlerTask, +) -> *mut JSObject { unsafe { let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null()); assert!(!func.is_null()); rooted!(in(cx) let obj = JS_GetFunctionObject(func)); assert!(!obj.is_null()); - SetFunctionNativeReserved(obj.get(), - SLOT_NATIVEHANDLER, - &ObjectValue(*holder)); - SetFunctionNativeReserved(obj.get(), - SLOT_NATIVEHANDLER_TASK, - &Int32Value(task as i32)); + SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder)); + SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32)); obj.get() } } diff --git a/components/script/dom/promisenativehandler.rs b/components/script/dom/promisenativehandler.rs index 59fe1d7425b..a5039d1f0b6 100644 --- a/components/script/dom/promisenativehandler.rs +++ b/components/script/dom/promisenativehandler.rs @@ -1,50 +1,63 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::PromiseNativeHandlerBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::trace::JSTraceable; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::globalscope::GlobalScope; +use crate::realms::InRealm; +use crate::script_runtime::JSContext as SafeJSContext; use dom_struct::dom_struct; -use heapsize::HeapSizeOf; -use js::jsapi::{JSContext, HandleValue}; +use js::jsapi::JSContext; +use js::rust::HandleValue; +use malloc_size_of::MallocSizeOf; -pub trait Callback: JSTraceable + HeapSizeOf { - fn callback(&self, cx: *mut JSContext, v: HandleValue); +pub trait Callback: JSTraceable + MallocSizeOf { + fn callback(&self, cx: SafeJSContext, v: HandleValue, realm: InRealm); } #[dom_struct] pub struct PromiseNativeHandler { reflector: Reflector, - resolve: Option<Box<Callback>>, - reject: Option<Box<Callback>>, + resolve: Option<Box<dyn Callback>>, + reject: Option<Box<dyn Callback>>, } impl PromiseNativeHandler { - pub fn new(global: &GlobalScope, - resolve: Option<Box<Callback>>, - reject: Option<Box<Callback>>) - -> Root<PromiseNativeHandler> { - reflect_dom_object(box PromiseNativeHandler { - reflector: Reflector::new(), - resolve: resolve, - reject: reject, - }, global, PromiseNativeHandlerBinding::Wrap) + pub fn new( + global: &GlobalScope, + resolve: Option<Box<dyn Callback>>, + reject: Option<Box<dyn Callback>>, + ) -> DomRoot<PromiseNativeHandler> { + reflect_dom_object( + Box::new(PromiseNativeHandler { + reflector: Reflector::new(), + resolve: resolve, + reject: reject, + }), + global, + ) } - fn callback(callback: &Option<Box<Callback>>, cx: *mut JSContext, v: HandleValue) { + #[allow(unsafe_code)] + fn callback( + callback: &Option<Box<dyn Callback>>, + cx: *mut JSContext, + v: HandleValue, + realm: InRealm, + ) { + let cx = unsafe { SafeJSContext::from_ptr(cx) }; if let Some(ref callback) = *callback { - callback.callback(cx, v) + callback.callback(cx, v, realm) } } - pub fn resolved_callback(&self, cx: *mut JSContext, v: HandleValue) { - PromiseNativeHandler::callback(&self.resolve, cx, v) + pub fn resolved_callback(&self, cx: *mut JSContext, v: HandleValue, realm: InRealm) { + PromiseNativeHandler::callback(&self.resolve, cx, v, realm) } - pub fn rejected_callback(&self, cx: *mut JSContext, v: HandleValue) { - PromiseNativeHandler::callback(&self.reject, cx, v) + pub fn rejected_callback(&self, cx: *mut JSContext, v: HandleValue, realm: InRealm) { + PromiseNativeHandler::callback(&self.reject, cx, v, realm) } } diff --git a/components/script/dom/promiserejectionevent.rs b/components/script/dom/promiserejectionevent.rs new file mode 100644 index 00000000000..92d67725ca0 --- /dev/null +++ b/components/script/dom/promiserejectionevent.rs @@ -0,0 +1,105 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::PromiseRejectionEventBinding; +use crate::dom::bindings::codegen::Bindings::PromiseRejectionEventBinding::PromiseRejectionEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use js::jsapi::Heap; +use js::jsval::JSVal; +use js::rust::HandleValue; +use servo_atoms::Atom; +use std::rc::Rc; + +#[dom_struct] +pub struct PromiseRejectionEvent { + event: Event, + #[ignore_malloc_size_of = "Rc"] + promise: Rc<Promise>, + #[ignore_malloc_size_of = "Defined in rust-mozjs"] + reason: Heap<JSVal>, +} + +impl PromiseRejectionEvent { + #[allow(unrooted_must_root)] + fn new_inherited(promise: Rc<Promise>) -> Self { + PromiseRejectionEvent { + event: Event::new_inherited(), + promise, + reason: Heap::default(), + } + } + + #[allow(unrooted_must_root)] + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + promise: Rc<Promise>, + reason: HandleValue, + ) -> DomRoot<Self> { + let ev = reflect_dom_object( + Box::new(PromiseRejectionEvent::new_inherited(promise)), + global, + ); + + { + let event = ev.upcast::<Event>(); + event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); + + ev.reason.set(reason.get()); + } + ev + } + + #[allow(unrooted_must_root, non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + type_: DOMString, + init: RootedTraceableBox<PromiseRejectionEventBinding::PromiseRejectionEventInit>, + ) -> Fallible<DomRoot<Self>> { + let reason = init.reason.handle(); + let promise = init.promise.clone(); + let bubbles = EventBubbles::from(init.parent.bubbles); + let cancelable = EventCancelable::from(init.parent.cancelable); + + let event = PromiseRejectionEvent::new( + global, + Atom::from(type_), + bubbles, + cancelable, + promise, + reason, + ); + Ok(event) + } +} + +impl PromiseRejectionEventMethods for PromiseRejectionEvent { + // https://html.spec.whatwg.org/multipage/#dom-promiserejectionevent-promise + fn Promise(&self) -> Rc<Promise> { + self.promise.clone() + } + + // https://html.spec.whatwg.org/multipage/#dom-promiserejectionevent-reason + fn Reason(&self, _cx: JSContext) -> JSVal { + self.reason.get() + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/radionodelist.rs b/components/script/dom/radionodelist.rs index 07f479c6ed4..958652da9f7 100644 --- a/components/script/dom/radionodelist.rs +++ b/components/script/dom/radionodelist.rs @@ -1,20 +1,21 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; -use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; -use dom::bindings::codegen::Bindings::RadioNodeListBinding; -use dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::htmlinputelement::HTMLInputElement; -use dom::node::Node; -use dom::nodelist::{NodeList, NodeListType}; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; +use crate::dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::htmlformelement::HTMLFormElement; +use crate::dom::htmlinputelement::{HTMLInputElement, InputType}; +use crate::dom::node::Node; +use crate::dom::nodelist::{NodeList, NodeListType, RadioList, RadioListMode}; +use crate::dom::window::Window; use dom_struct::dom_struct; +use servo_atoms::Atom; #[dom_struct] pub struct RadioNodeList { @@ -25,25 +26,44 @@ impl RadioNodeList { #[allow(unrooted_must_root)] fn new_inherited(list_type: NodeListType) -> RadioNodeList { RadioNodeList { - node_list: NodeList::new_inherited(list_type) + node_list: NodeList::new_inherited(list_type), } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, list_type: NodeListType) -> Root<RadioNodeList> { - reflect_dom_object(box RadioNodeList::new_inherited(list_type), - window, - RadioNodeListBinding::Wrap) + pub fn new(window: &Window, list_type: NodeListType) -> DomRoot<RadioNodeList> { + reflect_dom_object(Box::new(RadioNodeList::new_inherited(list_type)), window) } - pub fn new_simple_list<T>(window: &Window, iter: T) -> Root<RadioNodeList> - where T: Iterator<Item=Root<Node>> { - RadioNodeList::new(window, NodeListType::Simple(iter.map(|r| JS::from_ref(&*r)).collect())) + pub fn new_controls_except_image_inputs( + window: &Window, + form: &HTMLFormElement, + name: &Atom, + ) -> DomRoot<RadioNodeList> { + RadioNodeList::new( + window, + NodeListType::Radio(RadioList::new( + form, + RadioListMode::ControlsExceptImageInputs, + name.clone(), + )), + ) } - // FIXME: This shouldn't need to be implemented here since NodeList (the parent of - // RadioNodeList) implements Length + pub fn new_images( + window: &Window, + form: &HTMLFormElement, + name: &Atom, + ) -> DomRoot<RadioNodeList> { + RadioNodeList::new( + window, + NodeListType::Radio(RadioList::new(form, RadioListMode::Images, name.clone())), + ) + } + + // https://dom.spec.whatwg.org/#dom-nodelist-length // https://github.com/servo/servo/issues/5875 + #[allow(non_snake_case)] pub fn Length(&self) -> u32 { self.node_list.Length() } @@ -52,45 +72,51 @@ impl RadioNodeList { impl RadioNodeListMethods for RadioNodeList { // https://html.spec.whatwg.org/multipage/#dom-radionodelist-value fn Value(&self) -> DOMString { - self.upcast::<NodeList>().as_simple_list().iter().filter_map(|node| { - // Step 1 - node.downcast::<HTMLInputElement>().and_then(|input| { - match input.type_() { - atom!("radio") if input.Checked() => { + self.upcast::<NodeList>() + .iter() + .filter_map(|node| { + // Step 1 + node.downcast::<HTMLInputElement>().and_then(|input| { + if input.input_type() == InputType::Radio && input.Checked() { // Step 3-4 let value = input.Value(); - Some(if value.is_empty() { DOMString::from("on") } else { value }) + Some(if value.is_empty() { + DOMString::from("on") + } else { + value + }) + } else { + None } - _ => None - } + }) }) - }).next() - // Step 2 - .unwrap_or(DOMString::from("")) + .next() + // Step 2 + .unwrap_or(DOMString::from("")) } // https://html.spec.whatwg.org/multipage/#dom-radionodelist-value fn SetValue(&self, value: DOMString) { - for node in self.upcast::<NodeList>().as_simple_list().iter() { + for node in self.upcast::<NodeList>().iter() { // Step 1 if let Some(input) = node.downcast::<HTMLInputElement>() { - match input.type_() { - atom!("radio") if value == DOMString::from("on") => { + match input.input_type() { + InputType::Radio if value == DOMString::from("on") => { // Step 2 let val = input.Value(); if val.is_empty() || val == value { input.SetChecked(true); return; } - } - atom!("radio") => { + }, + InputType::Radio => { // Step 2 if input.Value() == value { input.SetChecked(true); return; } - } - _ => {} + }, + _ => {}, } } } @@ -101,7 +127,7 @@ impl RadioNodeListMethods for RadioNodeList { // https://github.com/servo/servo/issues/5875 // // https://dom.spec.whatwg.org/#dom-nodelist-item - fn IndexedGetter(&self, index: u32) -> Option<Root<Node>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Node>> { self.node_list.IndexedGetter(index) } } diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs index b5521e93d55..b48e186bc3f 100644 --- a/components/script/dom/range.rs +++ b/components/script/dom/range.rs @@ -1,34 +1,36 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeConstants; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; -use dom::bindings::codegen::Bindings::RangeBinding::{self, RangeConstants}; -use dom::bindings::codegen::Bindings::RangeBinding::RangeMethods; -use dom::bindings::codegen::Bindings::TextBinding::TextMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::{CharacterDataTypeId, NodeTypeId}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutJS, Root, RootedReference}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::bindings::trace::JSTraceable; -use dom::bindings::weakref::{WeakRef, WeakRefVec}; -use dom::characterdata::CharacterData; -use dom::document::Document; -use dom::documentfragment::DocumentFragment; -use dom::element::Element; -use dom::htmlscriptelement::HTMLScriptElement; -use dom::node::{Node, UnbindContext}; -use dom::text::Text; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeConstants; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; +use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeConstants; +use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods; +use crate::dom::bindings::codegen::Bindings::TextBinding::TextMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::inheritance::{CharacterDataTypeId, NodeTypeId}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::bindings::weakref::{WeakRef, WeakRefVec}; +use crate::dom::characterdata::CharacterData; +use crate::dom::document::Document; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::element::Element; +use crate::dom::htmlscriptelement::HTMLScriptElement; +use crate::dom::node::{Node, ShadowIncluding, UnbindContext}; +use crate::dom::selection::Selection; +use crate::dom::text::Text; +use crate::dom::window::Window; use dom_struct::dom_struct; -use heapsize::HeapSizeOf; use js::jsapi::JSTracer; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use std::cell::{Cell, UnsafeCell}; use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; @@ -37,31 +39,54 @@ pub struct Range { reflector_: Reflector, start: BoundaryPoint, end: BoundaryPoint, + // A range that belongs to a Selection needs to know about it + // so selectionchange can fire when the range changes. + // A range shouldn't belong to more than one Selection at a time, + // but from the spec as of Feb 1 2020 I can't rule out a corner case like: + // * Select a range R in document A, from node X to Y + // * Insert everything from X to Y into document B + // * Set B's selection's range to R + // which leaves R technically, and observably, associated with A even though + // it will fail the same-root-node check on many of A's selection's methods. + associated_selections: DomRefCell<Vec<Dom<Selection>>>, } impl Range { - fn new_inherited(start_container: &Node, start_offset: u32, - end_container: &Node, end_offset: u32) -> Range { + fn new_inherited( + start_container: &Node, + start_offset: u32, + end_container: &Node, + end_offset: u32, + ) -> Range { Range { reflector_: Reflector::new(), start: BoundaryPoint::new(start_container, start_offset), end: BoundaryPoint::new(end_container, end_offset), + associated_selections: DomRefCell::new(vec![]), } } - pub fn new_with_doc(document: &Document) -> Root<Range> { + pub fn new_with_doc(document: &Document) -> DomRoot<Range> { let root = document.upcast(); Range::new(document, root, 0, root, 0) } - pub fn new(document: &Document, - start_container: &Node, start_offset: u32, - end_container: &Node, end_offset: u32) - -> Root<Range> { - let range = reflect_dom_object(box Range::new_inherited(start_container, start_offset, - end_container, end_offset), - document.window(), - RangeBinding::Wrap); + pub fn new( + document: &Document, + start_container: &Node, + start_offset: u32, + end_container: &Node, + end_offset: u32, + ) -> DomRoot<Range> { + let range = reflect_dom_object( + Box::new(Range::new_inherited( + start_container, + start_offset, + end_container, + end_offset, + )), + document.window(), + ); start_container.ranges().push(WeakRef::new(&range)); if start_container != end_container { end_container.ranges().push(WeakRef::new(&range)); @@ -70,76 +95,97 @@ impl Range { } // https://dom.spec.whatwg.org/#dom-range - pub fn Constructor(window: &Window) -> Fallible<Root<Range>> { + #[allow(non_snake_case)] + pub fn Constructor(window: &Window) -> Fallible<DomRoot<Range>> { let document = window.Document(); Ok(Range::new_with_doc(&document)) } // https://dom.spec.whatwg.org/#contained fn contains(&self, node: &Node) -> bool { - match (bp_position(node, 0, &self.StartContainer(), self.StartOffset()), - bp_position(node, node.len(), &self.EndContainer(), self.EndOffset())) { + match ( + bp_position(node, 0, &self.StartContainer(), self.StartOffset()), + bp_position(node, node.len(), &self.EndContainer(), self.EndOffset()), + ) { (Some(Ordering::Greater), Some(Ordering::Less)) => true, - _ => false + _ => false, } } // https://dom.spec.whatwg.org/#partially-contained fn partially_contains(&self, node: &Node) -> bool { - self.StartContainer().inclusive_ancestors().any(|n| &*n == node) != - self.EndContainer().inclusive_ancestors().any(|n| &*n == node) + self.StartContainer() + .inclusive_ancestors(ShadowIncluding::No) + .any(|n| &*n == node) != + self.EndContainer() + .inclusive_ancestors(ShadowIncluding::No) + .any(|n| &*n == node) } // https://dom.spec.whatwg.org/#concept-range-clone - fn contained_children(&self) -> Fallible<(Option<Root<Node>>, - Option<Root<Node>>, - Vec<Root<Node>>)> { + fn contained_children( + &self, + ) -> Fallible<( + Option<DomRoot<Node>>, + Option<DomRoot<Node>>, + Vec<DomRoot<Node>>, + )> { let start_node = self.StartContainer(); let end_node = self.EndContainer(); // Steps 5-6. let common_ancestor = self.CommonAncestorContainer(); - let first_contained_child = - if start_node.is_inclusive_ancestor_of(&end_node) { - // Step 7. - None - } else { - // Step 8. - common_ancestor.children() - .find(|node| Range::partially_contains(self, node)) - }; + let first_contained_child = if start_node.is_inclusive_ancestor_of(&end_node) { + // Step 7. + None + } else { + // Step 8. + common_ancestor + .children() + .find(|node| Range::partially_contains(self, node)) + }; - let last_contained_child = - if end_node.is_inclusive_ancestor_of(&start_node) { - // Step 9. - None - } else { - // Step 10. - common_ancestor.rev_children() - .find(|node| Range::partially_contains(self, node)) - }; + let last_contained_child = if end_node.is_inclusive_ancestor_of(&start_node) { + // Step 9. + None + } else { + // Step 10. + common_ancestor + .rev_children() + .find(|node| Range::partially_contains(self, node)) + }; // Step 11. - let contained_children: Vec<Root<Node>> = - common_ancestor.children().filter(|n| self.contains(n)).collect(); + let contained_children: Vec<DomRoot<Node>> = common_ancestor + .children() + .filter(|n| self.contains(n)) + .collect(); // Step 12. if contained_children.iter().any(|n| n.is_doctype()) { return Err(Error::HierarchyRequest); } - Ok((first_contained_child, last_contained_child, contained_children)) + Ok(( + first_contained_child, + last_contained_child, + contained_children, + )) } // https://dom.spec.whatwg.org/#concept-range-bp-set fn set_start(&self, node: &Node, offset: u32) { + if &self.start.node != node || self.start.offset.get() != offset { + self.report_change(); + } if &self.start.node != node { if self.start.node == self.end.node { node.ranges().push(WeakRef::new(&self)); } else if &self.end.node == node { self.StartContainer().ranges().remove(self); } else { - node.ranges().push(self.StartContainer().ranges().remove(self)); + node.ranges() + .push(self.StartContainer().ranges().remove(self)); } } self.start.set(node, offset); @@ -147,13 +193,17 @@ impl Range { // https://dom.spec.whatwg.org/#concept-range-bp-set fn set_end(&self, node: &Node, offset: u32) { + if &self.end.node != node || self.end.offset.get() != offset { + self.report_change(); + } if &self.end.node != node { if self.end.node == self.start.node { node.ranges().push(WeakRef::new(&self)); } else if &self.start.node == node { self.EndContainer().ranges().remove(self); } else { - node.ranges().push(self.EndContainer().ranges().remove(self)); + node.ranges() + .push(self.EndContainer().ranges().remove(self)); } } self.end.set(node, offset); @@ -162,8 +212,14 @@ impl Range { // https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset fn compare_point(&self, node: &Node, offset: u32) -> Fallible<Ordering> { let start_node = self.StartContainer(); - let start_node_root = start_node.inclusive_ancestors().last().unwrap(); - let node_root = node.inclusive_ancestors().last().unwrap(); + let start_node_root = start_node + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); + let node_root = node + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); if start_node_root != node_root { // Step 1. return Err(Error::WrongDocument); @@ -176,22 +232,45 @@ impl Range { // Step 3. return Err(Error::IndexSize); } - if let Ordering::Less = bp_position(node, offset, &start_node, self.StartOffset()).unwrap() { + if let Ordering::Less = bp_position(node, offset, &start_node, self.StartOffset()).unwrap() + { // Step 4. return Ok(Ordering::Less); } - if let Ordering::Greater = bp_position(node, offset, &self.EndContainer(), self.EndOffset()).unwrap() { + if let Ordering::Greater = + bp_position(node, offset, &self.EndContainer(), self.EndOffset()).unwrap() + { // Step 5. return Ok(Ordering::Greater); } // Step 6. Ok(Ordering::Equal) } + + pub fn associate_selection(&self, selection: &Selection) { + let mut selections = self.associated_selections.borrow_mut(); + if !selections.iter().any(|s| &**s == selection) { + selections.push(Dom::from_ref(selection)); + } + } + + pub fn disassociate_selection(&self, selection: &Selection) { + self.associated_selections + .borrow_mut() + .retain(|s| &**s != selection); + } + + fn report_change(&self) { + self.associated_selections + .borrow() + .iter() + .for_each(|s| s.queue_selectionchange_task()); + } } impl RangeMethods for Range { // https://dom.spec.whatwg.org/#dom-range-startcontainer - fn StartContainer(&self) -> Root<Node> { + fn StartContainer(&self) -> DomRoot<Node> { self.start.node.get() } @@ -201,7 +280,7 @@ impl RangeMethods for Range { } // https://dom.spec.whatwg.org/#dom-range-endcontainer - fn EndContainer(&self) -> Root<Node> { + fn EndContainer(&self) -> DomRoot<Node> { self.end.node.get() } @@ -216,17 +295,10 @@ impl RangeMethods for Range { } // https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer - fn CommonAncestorContainer(&self) -> Root<Node> { - let end_container = self.EndContainer(); - // Step 1. - for container in self.StartContainer().inclusive_ancestors() { - // Step 2. - if container.is_inclusive_ancestor_of(&end_container) { - // Step 3. - return container; - } - } - unreachable!(); + fn CommonAncestorContainer(&self) -> DomRoot<Node> { + self.EndContainer() + .common_ancestor(&self.StartContainer(), ShadowIncluding::No) + .expect("Couldn't find common ancestor container") } // https://dom.spec.whatwg.org/#dom-range-setstart @@ -269,25 +341,25 @@ impl RangeMethods for Range { // https://dom.spec.whatwg.org/#dom-range-setstartbefore fn SetStartBefore(&self, node: &Node) -> ErrorResult { - let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); + let parent = node.GetParentNode().ok_or(Error::InvalidNodeType)?; self.SetStart(&parent, node.index()) } // https://dom.spec.whatwg.org/#dom-range-setstartafter fn SetStartAfter(&self, node: &Node) -> ErrorResult { - let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); + let parent = node.GetParentNode().ok_or(Error::InvalidNodeType)?; self.SetStart(&parent, node.index() + 1) } // https://dom.spec.whatwg.org/#dom-range-setendbefore fn SetEndBefore(&self, node: &Node) -> ErrorResult { - let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); + let parent = node.GetParentNode().ok_or(Error::InvalidNodeType)?; self.SetEnd(&parent, node.index()) } // https://dom.spec.whatwg.org/#dom-range-setendafter fn SetEndAfter(&self, node: &Node) -> ErrorResult { - let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); + let parent = node.GetParentNode().ok_or(Error::InvalidNodeType)?; self.SetEnd(&parent, node.index() + 1) } @@ -303,7 +375,7 @@ impl RangeMethods for Range { // https://dom.spec.whatwg.org/#dom-range-selectnode fn SelectNode(&self, node: &Node) -> ErrorResult { // Steps 1, 2. - let parent = try!(node.GetParentNode().ok_or(Error::InvalidNodeType)); + let parent = node.GetParentNode().ok_or(Error::InvalidNodeType)?; // Step 3. let index = node.index(); // Step 4. @@ -329,32 +401,31 @@ impl RangeMethods for Range { } // https://dom.spec.whatwg.org/#dom-range-compareboundarypoints - fn CompareBoundaryPoints(&self, how: u16, other: &Range) - -> Fallible<i16> { + fn CompareBoundaryPoints(&self, how: u16, other: &Range) -> Fallible<i16> { if how > RangeConstants::END_TO_START { // Step 1. return Err(Error::NotSupported); } - let this_root = self.StartContainer().inclusive_ancestors().last().unwrap(); - let other_root = other.StartContainer().inclusive_ancestors().last().unwrap(); + let this_root = self + .StartContainer() + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); + let other_root = other + .StartContainer() + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); if this_root != other_root { // Step 2. return Err(Error::WrongDocument); } // Step 3. let (this_point, other_point) = match how { - RangeConstants::START_TO_START => { - (&self.start, &other.start) - }, - RangeConstants::START_TO_END => { - (&self.end, &other.start) - }, - RangeConstants::END_TO_END => { - (&self.end, &other.end) - }, - RangeConstants::END_TO_START => { - (&self.start, &other.end) - }, + RangeConstants::START_TO_START => (&self.start, &other.start), + RangeConstants::START_TO_END => (&self.end, &other.start), + RangeConstants::END_TO_END => (&self.end, &other.end), + RangeConstants::END_TO_START => (&self.start, &other.end), _ => unreachable!(), }; // step 4. @@ -366,11 +437,16 @@ impl RangeMethods for Range { } // https://dom.spec.whatwg.org/#dom-range-clonerange - fn CloneRange(&self) -> Root<Range> { + fn CloneRange(&self) -> DomRoot<Range> { let start_node = self.StartContainer(); let owner_doc = start_node.owner_doc(); - Range::new(&owner_doc, &start_node, self.StartOffset(), - &self.EndContainer(), self.EndOffset()) + Range::new( + &owner_doc, + &start_node, + self.StartOffset(), + &self.EndContainer(), + self.EndOffset(), + ) } // https://dom.spec.whatwg.org/#dom-range-ispointinrange @@ -382,27 +458,32 @@ impl RangeMethods for Range { Err(Error::WrongDocument) => { // Step 2. Ok(false) - } + }, Err(error) => Err(error), } } // https://dom.spec.whatwg.org/#dom-range-comparepoint fn ComparePoint(&self, node: &Node, offset: u32) -> Fallible<i16> { - self.compare_point(node, offset).map(|order| { - match order { - Ordering::Less => -1, - Ordering::Equal => 0, - Ordering::Greater => 1, - } + self.compare_point(node, offset).map(|order| match order { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, }) } // https://dom.spec.whatwg.org/#dom-range-intersectsnode fn IntersectsNode(&self, node: &Node) -> bool { let start_node = self.StartContainer(); - let start_node_root = self.StartContainer().inclusive_ancestors().last().unwrap(); - let node_root = node.inclusive_ancestors().last().unwrap(); + let start_node_root = self + .StartContainer() + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); + let node_root = node + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); if start_node_root != node_root { // Step 1. return false; @@ -417,15 +498,15 @@ impl RangeMethods for Range { // Step 4. let offset = node.index(); // Step 5. - Ordering::Greater == bp_position(&parent, offset + 1, - &start_node, self.StartOffset()).unwrap() && - Ordering::Less == bp_position(&parent, offset, - &self.EndContainer(), self.EndOffset()).unwrap() + Ordering::Greater == + bp_position(&parent, offset + 1, &start_node, self.StartOffset()).unwrap() && + Ordering::Less == + bp_position(&parent, offset, &self.EndContainer(), self.EndOffset()).unwrap() } // https://dom.spec.whatwg.org/#dom-range-clonecontents // https://dom.spec.whatwg.org/#concept-range-clone - fn CloneContents(&self) -> Fallible<Root<DocumentFragment>> { + fn CloneContents(&self) -> Fallible<DomRoot<DocumentFragment>> { // Step 3. let start_node = self.StartContainer(); let start_offset = self.StartOffset(); @@ -443,10 +524,12 @@ impl RangeMethods for Range { if end_node == start_node { if let Some(cdata) = start_node.downcast::<CharacterData>() { // Steps 4.1-2. - let data = cdata.SubstringData(start_offset, end_offset - start_offset).unwrap(); + let data = cdata + .SubstringData(start_offset, end_offset - start_offset) + .unwrap(); let clone = cdata.clone_with_data(data, &start_node.owner_doc()); // Step 4.3. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; // Step 4.4 return Ok(fragment); } @@ -454,41 +537,45 @@ impl RangeMethods for Range { // Steps 5-12. let (first_contained_child, last_contained_child, contained_children) = - try!(self.contained_children()); + self.contained_children()?; if let Some(child) = first_contained_child { // Step 13. if let Some(cdata) = child.downcast::<CharacterData>() { assert!(child == start_node); // Steps 13.1-2. - let data = cdata.SubstringData(start_offset, start_node.len() - start_offset).unwrap(); + let data = cdata + .SubstringData(start_offset, start_node.len() - start_offset) + .unwrap(); let clone = cdata.clone_with_data(data, &start_node.owner_doc()); // Step 13.3. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; } else { // Step 14.1. - let clone = child.CloneNode(false); + let clone = child.CloneNode(/* deep */ false)?; // Step 14.2. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; // Step 14.3. - let subrange = Range::new(&clone.owner_doc(), - &start_node, - start_offset, - &child, - child.len()); + let subrange = Range::new( + &clone.owner_doc(), + &start_node, + start_offset, + &child, + child.len(), + ); // Step 14.4. - let subfragment = try!(subrange.CloneContents()); + let subfragment = subrange.CloneContents()?; // Step 14.5. - try!(clone.AppendChild(subfragment.upcast())); + clone.AppendChild(subfragment.upcast())?; } } // Step 15. for child in contained_children { // Step 15.1. - let clone = child.CloneNode(true); + let clone = child.CloneNode(/* deep */ true)?; // Step 15.2. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; } if let Some(child) = last_contained_child { @@ -499,22 +586,18 @@ impl RangeMethods for Range { let data = cdata.SubstringData(0, end_offset).unwrap(); let clone = cdata.clone_with_data(data, &start_node.owner_doc()); // Step 16.3. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; } else { // Step 17.1. - let clone = child.CloneNode(false); + let clone = child.CloneNode(/* deep */ false)?; // Step 17.2. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; // Step 17.3. - let subrange = Range::new(&clone.owner_doc(), - &child, - 0, - &end_node, - end_offset); + let subrange = Range::new(&clone.owner_doc(), &child, 0, &end_node, end_offset); // Step 17.4. - let subfragment = try!(subrange.CloneContents()); + let subfragment = subrange.CloneContents()?; // Step 17.5. - try!(clone.AppendChild(subfragment.upcast())); + clone.AppendChild(subfragment.upcast())?; } } @@ -524,7 +607,7 @@ impl RangeMethods for Range { // https://dom.spec.whatwg.org/#dom-range-extractcontents // https://dom.spec.whatwg.org/#concept-range-extract - fn ExtractContents(&self) -> Fallible<Root<DocumentFragment>> { + fn ExtractContents(&self) -> Fallible<DomRoot<DocumentFragment>> { // Step 3. let start_node = self.StartContainer(); let start_offset = self.StartOffset(); @@ -542,16 +625,17 @@ impl RangeMethods for Range { if end_node == start_node { if let Some(end_data) = end_node.downcast::<CharacterData>() { // Step 4.1. - let clone = end_node.CloneNode(true); + let clone = end_node.CloneNode(/* deep */ true)?; // Step 4.2. let text = end_data.SubstringData(start_offset, end_offset - start_offset); - clone.downcast::<CharacterData>().unwrap().SetData(text.unwrap()); + clone + .downcast::<CharacterData>() + .unwrap() + .SetData(text.unwrap()); // Step 4.3. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; // Step 4.4. - try!(end_data.ReplaceData(start_offset, - end_offset - start_offset, - DOMString::new())); + end_data.ReplaceData(start_offset, end_offset - start_offset, DOMString::new())?; // Step 4.5. return Ok(fragment); } @@ -559,92 +643,101 @@ impl RangeMethods for Range { // Steps 5-12. let (first_contained_child, last_contained_child, contained_children) = - try!(self.contained_children()); + self.contained_children()?; let (new_node, new_offset) = if start_node.is_inclusive_ancestor_of(&end_node) { // Step 13. - (Root::from_ref(&*start_node), start_offset) + (DomRoot::from_ref(&*start_node), start_offset) } else { // Step 14.1-2. - let reference_node = start_node.ancestors() - .take_while(|n| !n.is_inclusive_ancestor_of(&end_node)) - .last() - .unwrap_or(Root::from_ref(&start_node)); + let reference_node = start_node + .ancestors() + .take_while(|n| !n.is_inclusive_ancestor_of(&end_node)) + .last() + .unwrap_or(DomRoot::from_ref(&start_node)); // Step 14.3. - (reference_node.GetParentNode().unwrap(), reference_node.index() + 1) + ( + reference_node.GetParentNode().unwrap(), + reference_node.index() + 1, + ) }; if let Some(child) = first_contained_child { if let Some(start_data) = child.downcast::<CharacterData>() { assert!(child == start_node); // Step 15.1. - let clone = start_node.CloneNode(true); + let clone = start_node.CloneNode(/* deep */ true)?; // Step 15.2. - let text = start_data.SubstringData(start_offset, - start_node.len() - start_offset); - clone.downcast::<CharacterData>().unwrap().SetData(text.unwrap()); + let text = start_data.SubstringData(start_offset, start_node.len() - start_offset); + clone + .downcast::<CharacterData>() + .unwrap() + .SetData(text.unwrap()); // Step 15.3. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; // Step 15.4. - try!(start_data.ReplaceData(start_offset, - start_node.len() - start_offset, - DOMString::new())); + start_data.ReplaceData( + start_offset, + start_node.len() - start_offset, + DOMString::new(), + )?; } else { // Step 16.1. - let clone = child.CloneNode(false); + let clone = child.CloneNode(/* deep */ false)?; // Step 16.2. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; // Step 16.3. - let subrange = Range::new(&clone.owner_doc(), - &start_node, - start_offset, - &child, - child.len()); + let subrange = Range::new( + &clone.owner_doc(), + &start_node, + start_offset, + &child, + child.len(), + ); // Step 16.4. - let subfragment = try!(subrange.ExtractContents()); + let subfragment = subrange.ExtractContents()?; // Step 16.5. - try!(clone.AppendChild(subfragment.upcast())); + clone.AppendChild(subfragment.upcast())?; } } // Step 17. for child in contained_children { - try!(fragment.upcast::<Node>().AppendChild(&child)); + fragment.upcast::<Node>().AppendChild(&child)?; } if let Some(child) = last_contained_child { if let Some(end_data) = child.downcast::<CharacterData>() { assert!(child == end_node); // Step 18.1. - let clone = end_node.CloneNode(true); + let clone = end_node.CloneNode(/* deep */ true)?; // Step 18.2. let text = end_data.SubstringData(0, end_offset); - clone.downcast::<CharacterData>().unwrap().SetData(text.unwrap()); + clone + .downcast::<CharacterData>() + .unwrap() + .SetData(text.unwrap()); // Step 18.3. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; // Step 18.4. - try!(end_data.ReplaceData(0, end_offset, DOMString::new())); + end_data.ReplaceData(0, end_offset, DOMString::new())?; } else { // Step 19.1. - let clone = child.CloneNode(false); + let clone = child.CloneNode(/* deep */ false)?; // Step 19.2. - try!(fragment.upcast::<Node>().AppendChild(&clone)); + fragment.upcast::<Node>().AppendChild(&clone)?; // Step 19.3. - let subrange = Range::new(&clone.owner_doc(), - &child, - 0, - &end_node, - end_offset); + let subrange = Range::new(&clone.owner_doc(), &child, 0, &end_node, end_offset); // Step 19.4. - let subfragment = try!(subrange.ExtractContents()); + let subfragment = subrange.ExtractContents()?; // Step 19.5. - try!(clone.AppendChild(subfragment.upcast())); + clone.AppendChild(subfragment.upcast())?; } } // Step 20. - try!(self.SetStart(&new_node, new_offset)); - try!(self.SetEnd(&new_node, new_offset)); + self.SetStart(&new_node, new_offset)?; + self.SetEnd(&new_node, new_offset)?; // Step 21. Ok(fragment) @@ -667,48 +760,47 @@ impl RangeMethods for Range { } match start_node.type_id() { // Handled under step 2. - NodeTypeId::CharacterData(CharacterDataTypeId::Text) => (), + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => (), NodeTypeId::CharacterData(_) => return Err(Error::HierarchyRequest), - _ => () + _ => (), } // Step 2. - let (reference_node, parent) = - if start_node.type_id() == NodeTypeId::CharacterData(CharacterDataTypeId::Text) { + let (reference_node, parent) = match start_node.type_id() { + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => { // Step 3. let parent = match start_node.GetParentNode() { Some(parent) => parent, // Step 1. - None => return Err(Error::HierarchyRequest) + None => return Err(Error::HierarchyRequest), }; // Step 5. - (Some(Root::from_ref(&*start_node)), parent) - } else { + (Some(DomRoot::from_ref(&*start_node)), parent) + }, + _ => { // Steps 4-5. let child = start_node.ChildNodes().Item(start_offset); - (child, Root::from_ref(&*start_node)) - }; + (child, DomRoot::from_ref(&*start_node)) + }, + }; // Step 6. - try!(Node::ensure_pre_insertion_validity(node, - &parent, - reference_node.r())); + Node::ensure_pre_insertion_validity(node, &parent, reference_node.as_deref())?; // Step 7. let split_text; - let reference_node = - match start_node.downcast::<Text>() { - Some(text) => { - split_text = try!(text.SplitText(start_offset)); - let new_reference = Root::upcast::<Node>(split_text); - assert!(new_reference.GetParentNode().r() == Some(&parent)); - Some(new_reference) - }, - _ => reference_node - }; + let reference_node = match start_node.downcast::<Text>() { + Some(text) => { + split_text = text.SplitText(start_offset)?; + let new_reference = DomRoot::upcast::<Node>(split_text); + assert!(new_reference.GetParentNode().as_deref() == Some(&parent)); + Some(new_reference) + }, + _ => reference_node, + }; // Step 8. - let reference_node = if Some(node) == reference_node.r() { + let reference_node = if Some(node) == reference_node.as_deref() { node.GetNextSibling() } else { reference_node @@ -718,18 +810,20 @@ impl RangeMethods for Range { node.remove_self(); // Step 10. - let new_offset = - reference_node.r().map_or(parent.len(), |node| node.index()); + let new_offset = reference_node + .as_ref() + .map_or(parent.len(), |node| node.index()); // Step 11 - let new_offset = new_offset + if node.type_id() == NodeTypeId::DocumentFragment { - node.len() - } else { - 1 - }; + let new_offset = new_offset + + if let NodeTypeId::DocumentFragment(_) = node.type_id() { + node.len() + } else { + 1 + }; // Step 12. - try!(Node::pre_insert(node, &parent, reference_node.r())); + Node::pre_insert(node, &parent, reference_node.as_deref())?; // Step 13. if self.Collapsed() { @@ -755,9 +849,10 @@ impl RangeMethods for Range { // Step 3. if start_node == end_node { if let Some(text) = start_node.downcast::<CharacterData>() { - return text.ReplaceData(start_offset, - end_offset - start_offset, - DOMString::new()); + if end_offset > start_offset { + self.report_change(); + } + return text.ReplaceData(start_offset, end_offset - start_offset, DOMString::new()); } } @@ -770,7 +865,7 @@ impl RangeMethods for Range { let mut next = iter.next(); while let Some(child) = next { if self.contains(&child) { - contained_children.push(JS::from_ref(&*child)); + contained_children.push(Dom::from_ref(&*child)); next = iter.next_skipping_children(); } else { next = iter.next(); @@ -779,14 +874,14 @@ impl RangeMethods for Range { let (new_node, new_offset) = if start_node.is_inclusive_ancestor_of(&end_node) { // Step 5. - (Root::from_ref(&*start_node), start_offset) + (DomRoot::from_ref(&*start_node), start_offset) } else { // Step 6. - fn compute_reference(start_node: &Node, end_node: &Node) -> (Root<Node>, u32) { - let mut reference_node = Root::from_ref(start_node); + fn compute_reference(start_node: &Node, end_node: &Node) -> (DomRoot<Node>, u32) { + let mut reference_node = DomRoot::from_ref(start_node); while let Some(parent) = reference_node.GetParentNode() { if parent.is_inclusive_ancestor_of(end_node) { - return (parent, reference_node.index() + 1) + return (parent, reference_node.index() + 1); } reference_node = parent; } @@ -798,13 +893,16 @@ impl RangeMethods for Range { // Step 7. if let Some(text) = start_node.downcast::<CharacterData>() { - text.ReplaceData(start_offset, - start_node.len() - start_offset, - DOMString::new()).unwrap(); + text.ReplaceData( + start_offset, + start_node.len() - start_offset, + DOMString::new(), + ) + .unwrap(); } // Step 8. - for child in contained_children.r() { + for child in &*contained_children { child.remove_self(); } @@ -825,30 +923,36 @@ impl RangeMethods for Range { let start = self.StartContainer(); let end = self.EndContainer(); - if start.inclusive_ancestors().any(|n| !n.is_inclusive_ancestor_of(&end) && !n.is::<Text>()) || - end.inclusive_ancestors().any(|n| !n.is_inclusive_ancestor_of(&start) && !n.is::<Text>()) { - return Err(Error::InvalidState); + if start + .inclusive_ancestors(ShadowIncluding::No) + .any(|n| !n.is_inclusive_ancestor_of(&end) && !n.is::<Text>()) || + end.inclusive_ancestors(ShadowIncluding::No) + .any(|n| !n.is_inclusive_ancestor_of(&start) && !n.is::<Text>()) + { + return Err(Error::InvalidState); } // Step 2. match new_parent.type_id() { NodeTypeId::Document(_) | NodeTypeId::DocumentType | - NodeTypeId::DocumentFragment => return Err(Error::InvalidNodeType), - _ => () + NodeTypeId::DocumentFragment(_) => { + return Err(Error::InvalidNodeType); + }, + _ => (), } // Step 3. - let fragment = try!(self.ExtractContents()); + let fragment = self.ExtractContents()?; // Step 4. Node::replace_all(None, new_parent); // Step 5. - try!(self.InsertNode(new_parent)); + self.InsertNode(new_parent)?; // Step 6. - try!(new_parent.AppendChild(fragment.upcast())); + new_parent.AppendChild(fragment.upcast())?; // Step 7. self.SelectNode(new_parent) @@ -867,19 +971,24 @@ impl RangeMethods for Range { // Step 2. if start_node == end_node { - return char_data.SubstringData(self.StartOffset(), - self.EndOffset() - self.StartOffset()).unwrap(); + return char_data + .SubstringData(self.StartOffset(), self.EndOffset() - self.StartOffset()) + .unwrap(); } // Step 3. - s.push_str(&*char_data.SubstringData(self.StartOffset(), - char_data.Length() - self.StartOffset()).unwrap()); + s.push_str( + &*char_data + .SubstringData(self.StartOffset(), char_data.Length() - self.StartOffset()) + .unwrap(), + ); } // Step 4. let ancestor = self.CommonAncestorContainer(); - let mut iter = start_node.following_nodes(&ancestor) - .filter_map(Root::downcast::<Text>); + let mut iter = start_node + .following_nodes(&ancestor) + .filter_map(DomRoot::downcast::<Text>); while let Some(child) = iter.next() { if self.contains(child.upcast()) { @@ -898,27 +1007,31 @@ impl RangeMethods for Range { } // https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#extensions-to-the-range-interface - fn CreateContextualFragment(&self, fragment: DOMString) -> Fallible<Root<DocumentFragment>> { + fn CreateContextualFragment(&self, fragment: DOMString) -> Fallible<DomRoot<DocumentFragment>> { // Step 1. let node = self.StartContainer(); let owner_doc = node.owner_doc(); let element = match node.type_id() { - NodeTypeId::Document(_) | NodeTypeId::DocumentFragment => None, - NodeTypeId::Element(_) => Some(Root::downcast::<Element>(node).unwrap()), + NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_) => None, + NodeTypeId::Element(_) => Some(DomRoot::downcast::<Element>(node).unwrap()), NodeTypeId::CharacterData(CharacterDataTypeId::Comment) | - NodeTypeId::CharacterData(CharacterDataTypeId::Text) => node.GetParentElement(), + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => node.GetParentElement(), NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) | NodeTypeId::DocumentType => unreachable!(), + NodeTypeId::Attr => unreachable!(), }; // Step 2. - let element = Element::fragment_parsing_context(&owner_doc, element.r()); + let element = Element::fragment_parsing_context(&owner_doc, element.as_deref()); // Step 3. - let fragment_node = try!(element.parse_fragment(fragment)); + let fragment_node = element.parse_fragment(fragment)?; // Step 4. - for node in fragment_node.upcast::<Node>().traverse_preorder() { + for node in fragment_node + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::No) + { if let Some(script) = node.downcast::<HTMLScriptElement>() { script.set_already_started(false); script.set_parser_inserted(false); @@ -930,10 +1043,10 @@ impl RangeMethods for Range { } } -#[derive(DenyPublicFields, HeapSizeOf, JSTraceable)] -#[must_root] +#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] pub struct BoundaryPoint { - node: MutJS<Node>, + node: MutDom<Node>, offset: Cell<u32>, } @@ -942,7 +1055,7 @@ impl BoundaryPoint { debug_assert!(!node.is_doctype()); debug_assert!(offset <= node.len()); BoundaryPoint { - node: MutJS::new(node), + node: MutDom::new(node), offset: Cell::new(offset), } } @@ -960,23 +1073,24 @@ impl BoundaryPoint { #[allow(unrooted_must_root)] impl PartialOrd for BoundaryPoint { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - bp_position(&self.node.get(), self.offset.get(), - &other.node.get(), other.offset.get()) + bp_position( + &self.node.get(), + self.offset.get(), + &other.node.get(), + other.offset.get(), + ) } } #[allow(unrooted_must_root)] impl PartialEq for BoundaryPoint { fn eq(&self, other: &Self) -> bool { - self.node.get() == other.node.get() && - self.offset.get() == other.offset.get() + self.node.get() == other.node.get() && self.offset.get() == other.offset.get() } } // https://dom.spec.whatwg.org/#concept-range-bp-position -fn bp_position(a_node: &Node, a_offset: u32, - b_node: &Node, b_offset: u32) - -> Option<Ordering> { +fn bp_position(a_node: &Node, a_offset: u32, b_node: &Node, b_offset: u32) -> Option<Ordering> { if a_node as *const Node == b_node as *const Node { // Step 1. return Some(a_offset.cmp(&b_offset)); @@ -994,10 +1108,10 @@ fn bp_position(a_node: &Node, a_offset: u32, } } else if position & NodeConstants::DOCUMENT_POSITION_CONTAINS != 0 { // Step 3-1, 3-2. - let mut b_ancestors = b_node.inclusive_ancestors(); - let child = b_ancestors.find(|child| { - &*child.GetParentNode().unwrap() == a_node - }).unwrap(); + let mut b_ancestors = b_node.inclusive_ancestors(ShadowIncluding::No); + let child = b_ancestors + .find(|child| &*child.GetParentNode().unwrap() == a_node) + .unwrap(); // Step 3-3. if child.index() < a_offset { Some(Ordering::Greater) @@ -1019,7 +1133,9 @@ pub struct WeakRangeVec { impl WeakRangeVec { /// Create a new vector of weak references. pub fn new() -> Self { - WeakRangeVec { cell: UnsafeCell::new(WeakRefVec::new()) } + WeakRangeVec { + cell: UnsafeCell::new(WeakRefVec::new()), + } } /// Whether that vector of ranges is empty. @@ -1028,19 +1144,19 @@ impl WeakRangeVec { } /// Used for steps 2.1-2. when inserting a node. - /// https://dom.spec.whatwg.org/#concept-node-insert + /// <https://dom.spec.whatwg.org/#concept-node-insert> pub fn increase_above(&self, node: &Node, offset: u32, delta: u32) { self.map_offset_above(node, offset, |offset| offset + delta); } /// Used for steps 4-5. when removing a node. - /// https://dom.spec.whatwg.org/#concept-node-remove + /// <https://dom.spec.whatwg.org/#concept-node-remove> pub fn decrease_above(&self, node: &Node, offset: u32, delta: u32) { self.map_offset_above(node, offset, |offset| offset - delta); } /// Used for steps 2-3. when removing a node. - /// https://dom.spec.whatwg.org/#concept-node-remove + /// <https://dom.spec.whatwg.org/#concept-node-remove> pub fn drain_to_parent(&self, context: &UnbindContext, child: &Node) { if self.is_empty() { return; @@ -1049,7 +1165,7 @@ impl WeakRangeVec { let offset = context.index(); let parent = context.parent; unsafe { - let mut ranges = &mut *self.cell.get(); + let ranges = &mut *self.cell.get(); ranges.update(|entry| { let range = entry.root().unwrap(); @@ -1057,9 +1173,11 @@ impl WeakRangeVec { entry.remove(); } if &range.start.node == child { + range.report_change(); range.start.set(context.parent, offset); } if &range.end.node == child { + range.report_change(); range.end.set(context.parent, offset); } }); @@ -1069,14 +1187,14 @@ impl WeakRangeVec { } /// Used for steps 7.1-2. when normalizing a node. - /// https://dom.spec.whatwg.org/#dom-node-normalize + /// <https://dom.spec.whatwg.org/#dom-node-normalize> pub fn drain_to_preceding_text_sibling(&self, node: &Node, sibling: &Node, length: u32) { if self.is_empty() { return; } unsafe { - let mut ranges = &mut *self.cell.get(); + let ranges = &mut *self.cell.get(); ranges.update(|entry| { let range = entry.root().unwrap(); @@ -1084,9 +1202,11 @@ impl WeakRangeVec { entry.remove(); } if &range.start.node == node { + range.report_change(); range.start.set(sibling, range.StartOffset() + length); } if &range.end.node == node { + range.report_change(); range.end.set(sibling, range.EndOffset() + length); } }); @@ -1096,10 +1216,8 @@ impl WeakRangeVec { } /// Used for steps 7.3-4. when normalizing a node. - /// https://dom.spec.whatwg.org/#dom-node-normalize - pub fn move_to_text_child_at(&self, - node: &Node, offset: u32, - child: &Node, new_offset: u32) { + /// <https://dom.spec.whatwg.org/#dom-node-normalize> + pub fn move_to_text_child_at(&self, node: &Node, offset: u32, child: &Node, new_offset: u32) { unsafe { let child_ranges = &mut *child.ranges().cell.get(); @@ -1113,8 +1231,8 @@ impl WeakRangeVec { let move_end = node_is_end && range.EndOffset() == offset; let remove_from_node = move_start && move_end || - move_start && !node_is_end || - move_end && !node_is_start; + move_start && !node_is_end || + move_end && !node_is_start; let already_in_child = &range.start.node == child || &range.end.node == child; let push_to_child = !already_in_child && (move_start || move_end); @@ -1129,9 +1247,11 @@ impl WeakRangeVec { } if move_start { + range.report_change(); range.start.set(child, new_offset); } if move_end { + range.report_change(); range.end.set(child, new_offset); } }); @@ -1139,10 +1259,14 @@ impl WeakRangeVec { } /// Used for steps 8-11. when replacing character data. - /// https://dom.spec.whatwg.org/#concept-cd-replace - pub fn replace_code_units(&self, - node: &Node, offset: u32, - removed_code_units: u32, added_code_units: u32) { + /// <https://dom.spec.whatwg.org/#concept-cd-replace> + pub fn replace_code_units( + &self, + node: &Node, + offset: u32, + removed_code_units: u32, + added_code_units: u32, + ) { self.map_offset_above(node, offset, |range_offset| { if range_offset <= offset + removed_code_units { offset @@ -1153,10 +1277,8 @@ impl WeakRangeVec { } /// Used for steps 7.2-3. when splitting a text node. - /// https://dom.spec.whatwg.org/#concept-text-split - pub fn move_to_following_text_sibling_above(&self, - node: &Node, offset: u32, - sibling: &Node) { + /// <https://dom.spec.whatwg.org/#concept-text-split> + pub fn move_to_following_text_sibling_above(&self, node: &Node, offset: u32, sibling: &Node) { unsafe { let sibling_ranges = &mut *sibling.ranges().cell.get(); @@ -1172,11 +1294,10 @@ impl WeakRangeVec { let move_end = node_is_end && end_offset > offset; let remove_from_node = move_start && move_end || - move_start && !node_is_end || - move_end && !node_is_start; + move_start && !node_is_end || + move_end && !node_is_start; - let already_in_sibling = - &range.start.node == sibling || &range.end.node == sibling; + let already_in_sibling = &range.start.node == sibling || &range.end.node == sibling; let push_to_sibling = !already_in_sibling && (move_start || move_end); if remove_from_node { @@ -1189,9 +1310,11 @@ impl WeakRangeVec { } if move_start { + range.report_change(); range.start.set(sibling, start_offset - offset); } if move_end { + range.report_change(); range.end.set(sibling, end_offset - offset); } }); @@ -1199,15 +1322,17 @@ impl WeakRangeVec { } /// Used for steps 7.4-5. when splitting a text node. - /// https://dom.spec.whatwg.org/#concept-text-split + /// <https://dom.spec.whatwg.org/#concept-text-split> pub fn increment_at(&self, node: &Node, offset: u32) { unsafe { (*self.cell.get()).update(|entry| { let range = entry.root().unwrap(); if &range.start.node == node && offset == range.StartOffset() { + range.report_change(); range.start.set_offset(offset + 1); } if &range.end.node == node && offset == range.EndOffset() { + range.report_change(); range.end.set_offset(offset + 1); } }); @@ -1220,10 +1345,12 @@ impl WeakRangeVec { let range = entry.root().unwrap(); let start_offset = range.StartOffset(); if &range.start.node == node && start_offset > offset { + range.report_change(); range.start.set_offset(f(start_offset)); } let end_offset = range.EndOffset(); if &range.end.node == node && end_offset > offset { + range.report_change(); range.end.set_offset(f(end_offset)); } }); @@ -1239,18 +1366,16 @@ impl WeakRangeVec { fn remove(&self, range: &Range) -> WeakRef<Range> { unsafe { let ranges = &mut *self.cell.get(); - let position = ranges.iter().position(|ref_| { - ref_ == range - }).unwrap(); + let position = ranges.iter().position(|ref_| ref_ == range).unwrap(); ranges.swap_remove(position) } } } #[allow(unsafe_code)] -impl HeapSizeOf for WeakRangeVec { - fn heap_size_of_children(&self) -> usize { - unsafe { (*self.cell.get()).heap_size_of_children() } +impl MallocSizeOf for WeakRangeVec { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + unsafe { (*self.cell.get()).size_of(ops) } } } diff --git a/components/script/dom/raredata.rs b/components/script/dom/raredata.rs new file mode 100644 index 00000000000..9d4a82d63d6 --- /dev/null +++ b/components/script/dom/raredata.rs @@ -0,0 +1,53 @@ +/* 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 crate::dom::bindings::root::Dom; +use crate::dom::customelementregistry::{ + CustomElementDefinition, CustomElementReaction, CustomElementState, +}; +use crate::dom::mutationobserver::RegisteredObserver; +use crate::dom::node::UniqueId; +use crate::dom::shadowroot::ShadowRoot; +use crate::dom::window::LayoutValue; +use euclid::default::Rect; +use servo_atoms::Atom; +use std::rc::Rc; + +//XXX(ferjm) Ideally merge NodeRareData and ElementRareData so they share +// storage. + +#[derive(Default, JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct NodeRareData { + /// The shadow root the node belongs to. + /// This is None if the node is not in a shadow tree or + /// if it is a ShadowRoot. + pub containing_shadow_root: Option<Dom<ShadowRoot>>, + /// Registered observers for this node. + pub mutation_observers: Vec<RegisteredObserver>, + /// Lazily-generated Unique Id for this node. + pub unique_id: Option<UniqueId>, +} + +#[derive(Default, JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct ElementRareData { + /// https://dom.spec.whatwg.org/#dom-element-shadowroot + /// The ShadowRoot this element is host of. + /// XXX This is currently not exposed to web content. Only for + /// internal use. + pub shadow_root: Option<Dom<ShadowRoot>>, + /// <https://html.spec.whatwg.org/multipage/#custom-element-reaction-queue> + pub custom_element_reaction_queue: Vec<CustomElementReaction>, + /// <https://dom.spec.whatwg.org/#concept-element-custom-element-definition> + #[ignore_malloc_size_of = "Rc"] + pub custom_element_definition: Option<Rc<CustomElementDefinition>>, + /// <https://dom.spec.whatwg.org/#concept-element-custom-element-state> + pub custom_element_state: CustomElementState, + /// The "name" content attribute; not used as frequently as id, but used + /// in named getter loops so it's worth looking up quickly when present + pub name_attribute: Option<Atom>, + /// The client rect reported by layout. + pub client_rect: Option<LayoutValue<Rect<i32>>>, +} diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs new file mode 100644 index 00000000000..90e6c72da70 --- /dev/null +++ b/components/script/dom/readablestream.rs @@ -0,0 +1,540 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::conversions::{ConversionBehavior, ConversionResult}; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::settings_stack::{AutoEntryScript, AutoIncumbentScript}; +use crate::dom::bindings::utils::get_dictionary_property; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::js::conversions::FromJSValConvertible; +use crate::realms::{enter_realm, InRealm}; +use crate::script_runtime::JSContext as SafeJSContext; +use dom_struct::dom_struct; +use js::glue::{ + CreateReadableStreamUnderlyingSource, DeleteReadableStreamUnderlyingSource, + ReadableStreamUnderlyingSourceTraps, +}; +use js::jsapi::{HandleObject, HandleValue, Heap, JSContext, JSObject}; +use js::jsapi::{ + IsReadableStream, NewReadableExternalSourceStreamObject, ReadableStreamClose, + ReadableStreamDefaultReaderRead, ReadableStreamError, ReadableStreamGetReader, + ReadableStreamIsDisturbed, ReadableStreamIsLocked, ReadableStreamIsReadable, + ReadableStreamReaderMode, ReadableStreamReaderReleaseLock, ReadableStreamUnderlyingSource, + ReadableStreamUpdateDataAvailableFromSource, UnwrapReadableStream, +}; +use js::jsval::JSVal; +use js::jsval::UndefinedValue; +use js::rust::HandleValue as SafeHandleValue; +use js::rust::IntoHandle; +use std::cell::{Cell, RefCell}; +use std::os::raw::c_void; +use std::ptr::{self, NonNull}; +use std::rc::Rc; +use std::slice; + +static UNDERLYING_SOURCE_TRAPS: ReadableStreamUnderlyingSourceTraps = + ReadableStreamUnderlyingSourceTraps { + requestData: Some(request_data), + writeIntoReadRequestBuffer: Some(write_into_read_request_buffer), + cancel: Some(cancel), + onClosed: Some(close), + onErrored: Some(error), + finalize: Some(finalize), + }; + +#[dom_struct] +pub struct ReadableStream { + reflector_: Reflector, + #[ignore_malloc_size_of = "SM handles JS values"] + js_stream: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "SM handles JS values"] + js_reader: Heap<*mut JSObject>, + has_reader: Cell<bool>, + #[ignore_malloc_size_of = "Rc is hard"] + external_underlying_source: Option<Rc<ExternalUnderlyingSourceController>>, +} + +impl ReadableStream { + fn new_inherited( + external_underlying_source: Option<Rc<ExternalUnderlyingSourceController>>, + ) -> ReadableStream { + ReadableStream { + reflector_: Reflector::new(), + js_stream: Heap::default(), + js_reader: Heap::default(), + has_reader: Default::default(), + external_underlying_source: external_underlying_source, + } + } + + fn new( + global: &GlobalScope, + external_underlying_source: Option<Rc<ExternalUnderlyingSourceController>>, + ) -> DomRoot<ReadableStream> { + reflect_dom_object( + Box::new(ReadableStream::new_inherited(external_underlying_source)), + global, + ) + } + + /// Used from RustCodegen.py + #[allow(unsafe_code)] + pub unsafe fn from_js( + cx: SafeJSContext, + obj: *mut JSObject, + realm: InRealm, + ) -> Result<DomRoot<ReadableStream>, ()> { + if !IsReadableStream(obj) { + return Err(()); + } + + let global = GlobalScope::from_safe_context(cx, realm); + + let stream = ReadableStream::new(&global, None); + stream.js_stream.set(UnwrapReadableStream(obj)); + + Ok(stream) + } + + /// Build a stream backed by a Rust source that has already been read into memory. + pub fn new_from_bytes(global: &GlobalScope, bytes: Vec<u8>) -> DomRoot<ReadableStream> { + let stream = ReadableStream::new_with_external_underlying_source( + &global, + ExternalUnderlyingSource::Memory(bytes.len()), + ); + stream.enqueue_native(bytes); + stream.close_native(); + stream + } + + /// Build a stream backed by a Rust underlying source. + #[allow(unsafe_code)] + pub fn new_with_external_underlying_source( + global: &GlobalScope, + source: ExternalUnderlyingSource, + ) -> DomRoot<ReadableStream> { + let _ar = enter_realm(global); + let _ais = AutoIncumbentScript::new(global); + let cx = global.get_cx(); + + let source = Rc::new(ExternalUnderlyingSourceController::new(source)); + + let stream = ReadableStream::new(&global, Some(source.clone())); + + unsafe { + let js_wrapper = CreateReadableStreamUnderlyingSource( + &UNDERLYING_SOURCE_TRAPS, + &*source as *const _ as *const c_void, + ); + + rooted!(in(*cx) + let js_stream = NewReadableExternalSourceStreamObject( + *cx, + js_wrapper, + ptr::null_mut(), + HandleObject::null(), + ) + ); + + stream.js_stream.set(UnwrapReadableStream(js_stream.get())); + } + + stream + } + + /// Get a pointer to the underlying JS object. + pub fn get_js_stream(&self) -> NonNull<JSObject> { + NonNull::new(self.js_stream.get()) + .expect("Couldn't get a non-null pointer to JS stream object.") + } + + #[allow(unsafe_code)] + pub fn enqueue_native(&self, bytes: Vec<u8>) { + let global = self.global(); + let _ar = enter_realm(&*global); + let cx = global.get_cx(); + + let handle = unsafe { self.js_stream.handle() }; + + self.external_underlying_source + .as_ref() + .expect("No external source to enqueue bytes.") + .enqueue_chunk(cx, handle, bytes); + } + + #[allow(unsafe_code)] + pub fn error_native(&self, error: Error) { + let global = self.global(); + let _ar = enter_realm(&*global); + let cx = global.get_cx(); + + unsafe { + rooted!(in(*cx) let mut js_error = UndefinedValue()); + error.to_jsval(*cx, &global, js_error.handle_mut()); + ReadableStreamError( + *cx, + self.js_stream.handle(), + js_error.handle().into_handle(), + ); + } + } + + #[allow(unsafe_code)] + pub fn close_native(&self) { + let global = self.global(); + let _ar = enter_realm(&*global); + let cx = global.get_cx(); + + let handle = unsafe { self.js_stream.handle() }; + + self.external_underlying_source + .as_ref() + .expect("No external source to close.") + .close(cx, handle); + } + + /// Does the stream have all data in memory? + pub fn in_memory(&self) -> bool { + self.external_underlying_source + .as_ref() + .map(|source| source.in_memory()) + .unwrap_or(false) + } + + /// Return bytes for synchronous use, if the stream has all data in memory. + pub fn get_in_memory_bytes(&self) -> Option<Vec<u8>> { + self.external_underlying_source + .as_ref() + .and_then(|source| source.get_in_memory_bytes()) + } + + /// Acquires a reader and locks the stream, + /// must be done before `read_a_chunk`. + #[allow(unsafe_code)] + pub fn start_reading(&self) -> Result<(), ()> { + if self.is_locked() || self.is_disturbed() { + return Err(()); + } + + let global = self.global(); + let _ar = enter_realm(&*global); + let cx = global.get_cx(); + + unsafe { + rooted!(in(*cx) let reader = ReadableStreamGetReader( + *cx, + self.js_stream.handle(), + ReadableStreamReaderMode::Default, + )); + + // Note: the stream is locked to the reader. + self.js_reader.set(reader.get()); + } + + self.has_reader.set(true); + Ok(()) + } + + /// Read a chunk from the stream, + /// must be called after `start_reading`, + /// and before `stop_reading`. + #[allow(unsafe_code)] + pub fn read_a_chunk(&self) -> Rc<Promise> { + if !self.has_reader.get() { + panic!("Attempt to read stream chunk without having acquired a reader."); + } + + let global = self.global(); + let _ar = enter_realm(&*global); + let _aes = AutoEntryScript::new(&*global); + + let cx = global.get_cx(); + + unsafe { + rooted!(in(*cx) let promise_obj = ReadableStreamDefaultReaderRead( + *cx, + self.js_reader.handle(), + )); + Promise::new_with_js_promise(promise_obj.handle(), cx) + } + } + + /// Releases the lock on the reader, + /// must be done after `start_reading`. + #[allow(unsafe_code)] + pub fn stop_reading(&self) { + if !self.has_reader.get() { + panic!("ReadableStream::stop_reading called on a readerless stream."); + } + + self.has_reader.set(false); + + let global = self.global(); + let _ar = enter_realm(&*global); + let cx = global.get_cx(); + + unsafe { + ReadableStreamReaderReleaseLock(*cx, self.js_reader.handle()); + // Note: is this the way to nullify the Heap? + self.js_reader.set(ptr::null_mut()); + } + } + + #[allow(unsafe_code)] + pub fn is_locked(&self) -> bool { + // If we natively took a reader, we're locked. + if self.has_reader.get() { + return true; + } + + // Otherwise, still double-check that script didn't lock the stream. + let cx = self.global().get_cx(); + let mut locked_or_disturbed = false; + + unsafe { + ReadableStreamIsLocked(*cx, self.js_stream.handle(), &mut locked_or_disturbed); + } + + locked_or_disturbed + } + + #[allow(unsafe_code)] + pub fn is_disturbed(&self) -> bool { + // Check that script didn't disturb the stream. + let cx = self.global().get_cx(); + let mut locked_or_disturbed = false; + + unsafe { + ReadableStreamIsDisturbed(*cx, self.js_stream.handle(), &mut locked_or_disturbed); + } + + locked_or_disturbed + } +} + +#[allow(unsafe_code)] +unsafe extern "C" fn request_data( + source: *const c_void, + cx: *mut JSContext, + stream: HandleObject, + desired_size: usize, +) { + let source = &*(source as *const ExternalUnderlyingSourceController); + source.pull(SafeJSContext::from_ptr(cx), stream, desired_size); +} + +#[allow(unsafe_code)] +unsafe extern "C" fn write_into_read_request_buffer( + source: *const c_void, + _cx: *mut JSContext, + _stream: HandleObject, + buffer: *mut c_void, + length: usize, + bytes_written: *mut usize, +) { + let source = &*(source as *const ExternalUnderlyingSourceController); + let slice = slice::from_raw_parts_mut(buffer as *mut u8, length); + source.write_into_buffer(slice); + + // Currently we're always able to completely fulfill the write request. + *bytes_written = length; +} + +#[allow(unsafe_code)] +unsafe extern "C" fn cancel( + _source: *const c_void, + _cx: *mut JSContext, + _stream: HandleObject, + _reason: HandleValue, + _resolve_to: *mut JSVal, +) { +} + +#[allow(unsafe_code)] +unsafe extern "C" fn close(_source: *const c_void, _cx: *mut JSContext, _stream: HandleObject) {} + +#[allow(unsafe_code)] +unsafe extern "C" fn error( + _source: *const c_void, + _cx: *mut JSContext, + _stream: HandleObject, + _reason: HandleValue, +) { +} + +#[allow(unsafe_code)] +unsafe extern "C" fn finalize(source: *mut ReadableStreamUnderlyingSource) { + DeleteReadableStreamUnderlyingSource(source); +} + +pub enum ExternalUnderlyingSource { + /// Facilitate partial integration with sources + /// that are currently read into memory. + Memory(usize), + /// A blob as underlying source, with a known total size. + Blob(usize), + /// A fetch response as underlying source. + FetchResponse, +} + +#[derive(JSTraceable, MallocSizeOf)] +struct ExternalUnderlyingSourceController { + /// Loosely matches the underlying queue, + /// <https://streams.spec.whatwg.org/#internal-queues> + buffer: RefCell<Vec<u8>>, + /// Has the stream been closed by native code? + closed: Cell<bool>, + /// Does this stream contains all it's data in memory? + in_memory: Cell<bool>, +} + +impl ExternalUnderlyingSourceController { + fn new(source: ExternalUnderlyingSource) -> ExternalUnderlyingSourceController { + let (buffer, in_mem) = match source { + ExternalUnderlyingSource::Blob(size) => (Vec::with_capacity(size), false), + ExternalUnderlyingSource::Memory(size) => (Vec::with_capacity(size), true), + ExternalUnderlyingSource::FetchResponse => (vec![], false), + }; + ExternalUnderlyingSourceController { + buffer: RefCell::new(buffer), + closed: Cell::new(false), + in_memory: Cell::new(in_mem), + } + } + + /// Does the stream have all data in memory? + pub fn in_memory(&self) -> bool { + self.in_memory.get() + } + + /// Return bytes synchronously if the stream has all data in memory. + pub fn get_in_memory_bytes(&self) -> Option<Vec<u8>> { + if self.in_memory.get() { + return Some(self.buffer.borrow().clone()); + } + None + } + + /// Signal available bytes if the stream is currently readable. + #[allow(unsafe_code)] + fn maybe_signal_available_bytes( + &self, + cx: SafeJSContext, + stream: HandleObject, + available: usize, + ) { + if available == 0 { + return; + } + unsafe { + let mut readable = false; + if !ReadableStreamIsReadable(*cx, stream, &mut readable) { + return; + } + if readable { + ReadableStreamUpdateDataAvailableFromSource(*cx, stream, available as u32); + } + } + } + + /// Close a currently readable js stream. + #[allow(unsafe_code)] + fn maybe_close_js_stream(&self, cx: SafeJSContext, stream: HandleObject) { + unsafe { + let mut readable = false; + if !ReadableStreamIsReadable(*cx, stream, &mut readable) { + return; + } + if readable { + ReadableStreamClose(*cx, stream); + } + } + } + + fn close(&self, cx: SafeJSContext, stream: HandleObject) { + self.closed.set(true); + self.maybe_close_js_stream(cx, stream); + } + + fn enqueue_chunk(&self, cx: SafeJSContext, stream: HandleObject, mut chunk: Vec<u8>) { + let available = { + let mut buffer = self.buffer.borrow_mut(); + chunk.append(&mut buffer); + *buffer = chunk; + buffer.len() + }; + self.maybe_signal_available_bytes(cx, stream, available); + } + + #[allow(unsafe_code)] + fn pull(&self, cx: SafeJSContext, stream: HandleObject, _desired_size: usize) { + // Note: for pull sources, + // this would be the time to ask for a chunk. + + if self.closed.get() { + return self.maybe_close_js_stream(cx, stream); + } + + let available = { + let buffer = self.buffer.borrow(); + buffer.len() + }; + + self.maybe_signal_available_bytes(cx, stream, available); + } + + fn get_chunk_with_length(&self, length: usize) -> Vec<u8> { + let mut buffer = self.buffer.borrow_mut(); + let buffer_len = buffer.len(); + assert!(buffer_len >= length as usize); + buffer.split_off(buffer_len - length) + } + + fn write_into_buffer(&self, dest: &mut [u8]) { + let length = dest.len(); + let chunk = self.get_chunk_with_length(length); + dest.copy_from_slice(chunk.as_slice()); + } +} + +#[allow(unsafe_code)] +/// Get the `done` property of an object that a read promise resolved to. +pub fn get_read_promise_done(cx: SafeJSContext, v: &SafeHandleValue) -> Result<bool, Error> { + unsafe { + rooted!(in(*cx) let object = v.to_object()); + rooted!(in(*cx) let mut done = UndefinedValue()); + match get_dictionary_property(*cx, object.handle(), "done", done.handle_mut()) { + Ok(true) => match bool::from_jsval(*cx, done.handle(), ()) { + Ok(ConversionResult::Success(val)) => Ok(val), + Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.to_string())), + _ => Err(Error::Type("Unknown format for done property.".to_string())), + }, + Ok(false) => Err(Error::Type("Promise has no done property.".to_string())), + Err(()) => Err(Error::JSFailed), + } + } +} + +#[allow(unsafe_code)] +/// Get the `value` property of an object that a read promise resolved to. +pub fn get_read_promise_bytes(cx: SafeJSContext, v: &SafeHandleValue) -> Result<Vec<u8>, Error> { + unsafe { + rooted!(in(*cx) let object = v.to_object()); + rooted!(in(*cx) let mut bytes = UndefinedValue()); + match get_dictionary_property(*cx, object.handle(), "value", bytes.handle_mut()) { + Ok(true) => { + match Vec::<u8>::from_jsval(*cx, bytes.handle(), ConversionBehavior::EnforceRange) { + Ok(ConversionResult::Success(val)) => Ok(val), + Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.to_string())), + _ => Err(Error::Type("Unknown format for bytes read.".to_string())), + } + }, + Ok(false) => Err(Error::Type("Promise has no value property.".to_string())), + Err(()) => Err(Error::JSFailed), + } + } +} diff --git a/components/script/dom/request.rs b/components/script/dom/request.rs index f33c9eed48d..e301d360cc4 100644 --- a/components/script/dom/request.rs +++ b/components/script/dom/request.rs @@ -1,34 +1,35 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use body::{BodyOperations, BodyType, consume_body}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods}; -use dom::bindings::codegen::Bindings::RequestBinding; -use dom::bindings::codegen::Bindings::RequestBinding::ReferrerPolicy; -use dom::bindings::codegen::Bindings::RequestBinding::RequestCache; -use dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials; -use dom::bindings::codegen::Bindings::RequestBinding::RequestDestination; -use dom::bindings::codegen::Bindings::RequestBinding::RequestInfo; -use dom::bindings::codegen::Bindings::RequestBinding::RequestInit; -use dom::bindings::codegen::Bindings::RequestBinding::RequestMethods; -use dom::bindings::codegen::Bindings::RequestBinding::RequestMode; -use dom::bindings::codegen::Bindings::RequestBinding::RequestRedirect; -use dom::bindings::codegen::Bindings::RequestBinding::RequestType; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::{ByteString, DOMString, USVString}; -use dom::bindings::trace::RootedTraceableBox; -use dom::globalscope::GlobalScope; -use dom::headers::{Guard, Headers}; -use dom::promise::Promise; -use dom::xmlhttprequest::Extractable; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::body::Extractable; +use crate::body::{consume_body, BodyMixin, BodyType}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods}; +use crate::dom::bindings::codegen::Bindings::RequestBinding::ReferrerPolicy; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCache; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestDestination; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInfo; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestMethods; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestMode; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestRedirect; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{ByteString, DOMString, USVString}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::globalscope::GlobalScope; +use crate::dom::headers::{Guard, Headers}; +use crate::dom::promise::Promise; +use crate::dom::readablestream::ReadableStream; +use crate::script_runtime::JSContext as SafeJSContext; use dom_struct::dom_struct; -use hyper::method::Method as HttpMethod; -use net_traits::ReferrerPolicy as MsgReferrerPolicy; -use net_traits::request::{Origin, Window}; +use http::header::{HeaderName, HeaderValue}; +use http::method::InvalidMethod; +use http::Method as HttpMethod; +use js::jsapi::JSObject; use net_traits::request::CacheMode as NetTraitsRequestCache; use net_traits::request::CredentialsMode as NetTraitsRequestCredentials; use net_traits::request::Destination as NetTraitsRequestDestination; @@ -36,54 +37,44 @@ use net_traits::request::RedirectMode as NetTraitsRequestRedirect; use net_traits::request::Referrer as NetTraitsRequestReferrer; use net_traits::request::Request as NetTraitsRequest; use net_traits::request::RequestMode as NetTraitsRequestMode; -use net_traits::request::Type as NetTraitsRequestType; +use net_traits::request::{Origin, Window}; +use net_traits::ReferrerPolicy as MsgReferrerPolicy; use servo_url::ServoUrl; -use std::ascii::AsciiExt; -use std::cell::{Cell, Ref}; +use std::ptr::NonNull; use std::rc::Rc; +use std::str::FromStr; #[dom_struct] pub struct Request { reflector_: Reflector, - request: DOMRefCell<NetTraitsRequest>, - body_used: Cell<bool>, - headers: MutNullableJS<Headers>, - mime_type: DOMRefCell<Vec<u8>>, - #[ignore_heap_size_of = "Rc"] - body_promise: DOMRefCell<Option<(Rc<Promise>, BodyType)>>, + request: DomRefCell<NetTraitsRequest>, + body_stream: MutNullableDom<ReadableStream>, + headers: MutNullableDom<Headers>, + mime_type: DomRefCell<Vec<u8>>, } impl Request { - fn new_inherited(global: &GlobalScope, - url: ServoUrl, - is_service_worker_global_scope: bool) -> Request { + fn new_inherited(global: &GlobalScope, url: ServoUrl) -> Request { Request { reflector_: Reflector::new(), - request: DOMRefCell::new( - net_request_from_global(global, - url, - is_service_worker_global_scope)), - body_used: Cell::new(false), + request: DomRefCell::new(net_request_from_global(global, url)), + body_stream: MutNullableDom::new(None), headers: Default::default(), - mime_type: DOMRefCell::new("".to_string().into_bytes()), - body_promise: DOMRefCell::new(None), + mime_type: DomRefCell::new("".to_string().into_bytes()), } } - pub fn new(global: &GlobalScope, - url: ServoUrl, - is_service_worker_global_scope: bool) -> Root<Request> { - reflect_dom_object(box Request::new_inherited(global, - url, - is_service_worker_global_scope), - global, RequestBinding::Wrap) + pub fn new(global: &GlobalScope, url: ServoUrl) -> DomRoot<Request> { + reflect_dom_object(Box::new(Request::new_inherited(global, url)), global) } // https://fetch.spec.whatwg.org/#dom-request - pub fn Constructor(global: &GlobalScope, - input: RequestInfo, - init: RootedTraceableBox<RequestInit>) - -> Fallible<Root<Request>> { + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + mut input: RequestInfo, + init: RootedTraceableBox<RequestInit>, + ) -> Fallible<DomRoot<Request>> { // Step 1 let temporary_request: NetTraitsRequest; @@ -96,65 +87,65 @@ impl Request { // Step 4 let base_url = global.api_base_url(); + // Step 5 TODO: "Let signal be null." + match input { - // Step 5 + // Step 6 RequestInfo::USVString(USVString(ref usv_string)) => { - // Step 5.1 + // Step 6.1 let parsed_url = base_url.join(&usv_string); - // Step 5.2 + // Step 6.2 if parsed_url.is_err() { - return Err(Error::Type("Url could not be parsed".to_string())) + return Err(Error::Type("Url could not be parsed".to_string())); } - // Step 5.3 + // Step 6.3 let url = parsed_url.unwrap(); if includes_credentials(&url) { - return Err(Error::Type("Url includes credentials".to_string())) + return Err(Error::Type("Url includes credentials".to_string())); } - // Step 5.4 - temporary_request = net_request_from_global(global, - url, - false); - // Step 5.5 + // Step 6.4 + temporary_request = net_request_from_global(global, url); + // Step 6.5 fallback_mode = Some(NetTraitsRequestMode::CorsMode); - // Step 5.6 - fallback_credentials = Some(NetTraitsRequestCredentials::Omit); - } - // Step 6 + // Step 6.6 + fallback_credentials = Some(NetTraitsRequestCredentials::CredentialsSameOrigin); + }, + // Step 7 RequestInfo::Request(ref input_request) => { - // Step 6.1 + // This looks like Step 38 + // TODO do this in the right place to not mask other errors if request_is_disturbed(input_request) || request_is_locked(input_request) { - return Err(Error::Type("Input is disturbed or locked".to_string())) + return Err(Error::Type("Input is disturbed or locked".to_string())); } - // Step 6.2 + // Step 7.1 temporary_request = input_request.request.borrow().clone(); - } + // Step 7.2 TODO: "Set signal to input's signal." + }, } - // Step 7 + // Step 8 // TODO: `entry settings object` is not implemented yet. let origin = base_url.origin(); - // Step 8 + // Step 9 let mut window = Window::Client; - // Step 9 + // Step 10 // TODO: `environment settings object` is not implemented in Servo yet. - // Step 10 + // Step 11 if !init.window.handle().is_null_or_undefined() { - return Err(Error::Type("Window is present and is not null".to_string())) + return Err(Error::Type("Window is present and is not null".to_string())); } - // Step 11 + // Step 12 if !init.window.handle().is_undefined() { window = Window::NoWindow; } - // Step 12 + // Step 13 let mut request: NetTraitsRequest; - request = net_request_from_global(global, - temporary_request.current_url(), - false); + request = net_request_from_global(global, temporary_request.current_url()); request.method = temporary_request.method; request.headers = temporary_request.headers.clone(); request.unsafe_request = true; @@ -169,7 +160,7 @@ impl Request { request.redirect_mode = temporary_request.redirect_mode; request.integrity_metadata = temporary_request.integrity_metadata; - // Step 13 + // Step 14 if init.body.is_some() || init.cache.is_some() || init.credentials.is_some() || @@ -180,273 +171,308 @@ impl Request { init.redirect.is_some() || init.referrer.is_some() || init.referrerPolicy.is_some() || - !init.window.handle().is_undefined() { - // Step 13.1 - if request.mode == NetTraitsRequestMode::Navigate { - request.mode = NetTraitsRequestMode::SameOrigin; - } - // Step 13.2 - request.referrer = NetTraitsRequestReferrer::Client; - // Step 13.3 - request.referrer_policy = None; + !init.window.handle().is_undefined() + { + // Step 14.1 + if request.mode == NetTraitsRequestMode::Navigate { + request.mode = NetTraitsRequestMode::SameOrigin; } + // Step 14.2 TODO: "Unset request's reload-navigation flag." + // Step 14.3 TODO: "Unset request's history-navigation flag." + // Step 14.4 + request.referrer = global.get_referrer(); + // Step 14.5 + request.referrer_policy = None; + } - // Step 14 + // Step 15 if let Some(init_referrer) = init.referrer.as_ref() { - // Step 14.1 + // Step 15.1 let ref referrer = init_referrer.0; - // Step 14.2 + // Step 15.2 if referrer.is_empty() { request.referrer = NetTraitsRequestReferrer::NoReferrer; } else { - // Step 14.3 + // Step 15.3.1 let parsed_referrer = base_url.join(referrer); - // Step 14.4 + // Step 15.3.2 if parsed_referrer.is_err() { - return Err(Error::Type( - "Failed to parse referrer url".to_string())); + return Err(Error::Type("Failed to parse referrer url".to_string())); } - // Step 14.5 + // Step 15.3.3 if let Ok(parsed_referrer) = parsed_referrer { if (parsed_referrer.cannot_be_a_base() && parsed_referrer.scheme() == "about" && parsed_referrer.path() == "client") || - parsed_referrer.origin() != origin { - request.referrer = NetTraitsRequestReferrer::Client; - } else { - // Step 14.6 - request.referrer = NetTraitsRequestReferrer::ReferrerUrl(parsed_referrer); - } + parsed_referrer.origin() != origin + { + request.referrer = global.get_referrer(); + } else { + // Step 15.3.4 + request.referrer = NetTraitsRequestReferrer::ReferrerUrl(parsed_referrer); + } } } } - // Step 15 + // Step 16 if let Some(init_referrerpolicy) = init.referrerPolicy.as_ref() { let init_referrer_policy = init_referrerpolicy.clone().into(); request.referrer_policy = Some(init_referrer_policy); } - // Step 16 - let mode = init.mode.as_ref().map(|m| m.clone().into()).or(fallback_mode); - // Step 17 + let mode = init + .mode + .as_ref() + .map(|m| m.clone().into()) + .or(fallback_mode); + + // Step 18 if let Some(NetTraitsRequestMode::Navigate) = mode { return Err(Error::Type("Request mode is Navigate".to_string())); } - // Step 18 + // Step 19 if let Some(m) = mode { request.mode = m; } - // Step 19 - let credentials = init.credentials.as_ref().map(|m| m.clone().into()).or(fallback_credentials); - // Step 20 + let credentials = init + .credentials + .as_ref() + .map(|m| m.clone().into()) + .or(fallback_credentials); + + // Step 21 if let Some(c) = credentials { request.credentials_mode = c; } - // Step 21 + // Step 22 if let Some(init_cache) = init.cache.as_ref() { let cache = init_cache.clone().into(); request.cache_mode = cache; } - // Step 22 + // Step 23 if request.cache_mode == NetTraitsRequestCache::OnlyIfCached { if request.mode != NetTraitsRequestMode::SameOrigin { return Err(Error::Type( - "Cache is 'only-if-cached' and mode is not 'same-origin'".to_string())); + "Cache is 'only-if-cached' and mode is not 'same-origin'".to_string(), + )); } } - // Step 23 + // Step 24 if let Some(init_redirect) = init.redirect.as_ref() { let redirect = init_redirect.clone().into(); request.redirect_mode = redirect; } - // Step 24 + // Step 25 if let Some(init_integrity) = init.integrity.as_ref() { let integrity = init_integrity.clone().to_string(); request.integrity_metadata = integrity; } - // Step 25 + // Step 26 TODO: "If init["keepalive"] exists..." + + // Step 27.1 if let Some(init_method) = init.method.as_ref() { - // Step 25.1 + // Step 27.2 if !is_method(&init_method) { return Err(Error::Type("Method is not a method".to_string())); } if is_forbidden_method(&init_method) { return Err(Error::Type("Method is forbidden".to_string())); } - // Step 25.2 + // Step 27.3 let method = match init_method.as_str() { - Some(s) => normalize_method(s), + Some(s) => normalize_method(s) + .map_err(|e| Error::Type(format!("Method is not valid: {:?}", e)))?, None => return Err(Error::Type("Method is not a valid UTF8".to_string())), }; - // Step 25.3 + // Step 27.4 request.method = method; } - // Step 26 - let r = Request::from_net_request(global, - false, - request); - r.headers.or_init(|| Headers::for_request(&r.global())); + // Step 28 TODO: "If init["signal"] exists..." - // Step 27 - let mut headers_copy = r.Headers(); + // Step 29 + let r = Request::from_net_request(global, request); - // Step 28 - if let Some(possible_header) = init.headers.as_ref() { - match possible_header { - &HeadersInit::Headers(ref init_headers) => { - headers_copy = Root::from_ref(&*init_headers); - } - &HeadersInit::ByteStringSequenceSequence(ref init_sequence) => { - try!(headers_copy.fill(Some( - HeadersInit::ByteStringSequenceSequence(init_sequence.clone())))); + // Step 30 TODO: "If signal is not null..." + + // Step 31 + // "or_init" looks unclear here, but it always enters the block since r + // hasn't had any other way to initialize its headers + r.headers.or_init(|| Headers::for_request(&r.global())); + + // Step 32 - but spec says this should only be when non-empty init? + let headers_copy = init + .headers + .as_ref() + .map(|possible_header| match possible_header { + HeadersInit::ByteStringSequenceSequence(init_sequence) => { + HeadersInit::ByteStringSequenceSequence(init_sequence.clone()) }, - &HeadersInit::ByteStringMozMap(ref init_map) => { - try!(headers_copy.fill(Some( - HeadersInit::ByteStringMozMap(init_map.clone())))); + HeadersInit::ByteStringByteStringRecord(init_map) => { + HeadersInit::ByteStringByteStringRecord(init_map.clone()) }, - } - } + }); - // Step 29 + // Step 32.3 // We cannot empty `r.Headers().header_list` because // we would undo the Step 27 above. One alternative is to set // `headers_copy` as a deep copy of `r.Headers()`. However, - // `r.Headers()` is a `Root<T>`, and therefore it is difficult + // `r.Headers()` is a `DomRoot<T>`, and therefore it is difficult // to obtain a mutable reference to `r.Headers()`. Without the // mutable reference, we cannot mutate `r.Headers()` to be the // deep copied headers in Step 27. - // Step 30 + // Step 32.4 if r.request.borrow().mode == NetTraitsRequestMode::NoCors { let borrowed_request = r.request.borrow(); - // Step 30.1 + // Step 32.4.1 if !is_cors_safelisted_method(&borrowed_request.method) { return Err(Error::Type( - "The mode is 'no-cors' but the method is not a cors-safelisted method".to_string())); - } - // Step 30.2 - if !borrowed_request.integrity_metadata.is_empty() { - return Err(Error::Type("Integrity metadata is not an empty string".to_string())); + "The mode is 'no-cors' but the method is not a cors-safelisted method" + .to_string(), + )); } - // Step 30.3 + // Step 32.4.2 r.Headers().set_guard(Guard::RequestNoCors); } - // Step 31 - match init.headers { + // Step 32.5 + match headers_copy { None => { // This is equivalent to the specification's concept of // "associated headers list". If an init headers is not given, // but an input with headers is given, set request's // headers as the input's Headers. if let RequestInfo::Request(ref input_request) = input { - try!(r.Headers().fill(Some(HeadersInit::Headers(input_request.Headers())))); + r.Headers().copy_from_headers(input_request.Headers())?; } }, - Some(HeadersInit::Headers(_)) => try!(r.Headers().fill(Some(HeadersInit::Headers(headers_copy)))), - _ => {}, + Some(headers_copy) => r.Headers().fill(Some(headers_copy))?, } - // Step 32 - let mut input_body = if let RequestInfo::Request(ref input_request) = input { - let input_request_request = input_request.request.borrow(); - input_request_request.body.clone() + // Step 32.5-6 depending on how we got here + // Copy the headers list onto the headers of net_traits::Request + r.request.borrow_mut().headers = r.Headers().get_headers_list(); + + // Step 33 + let mut input_body = if let RequestInfo::Request(ref mut input_request) = input { + let mut input_request_request = input_request.request.borrow_mut(); + input_request_request.body.take() } else { None }; - // Step 33 + // Step 34 if let Some(init_body_option) = init.body.as_ref() { if init_body_option.is_some() || input_body.is_some() { let req = r.request.borrow(); let req_method = &req.method; match *req_method { - HttpMethod::Get => return Err(Error::Type( - "Init's body is non-null, and request method is GET".to_string())), - HttpMethod::Head => return Err(Error::Type( - "Init's body is non-null, and request method is HEAD".to_string())), + HttpMethod::GET => { + return Err(Error::Type( + "Init's body is non-null, and request method is GET".to_string(), + )); + }, + HttpMethod::HEAD => { + return Err(Error::Type( + "Init's body is non-null, and request method is HEAD".to_string(), + )); + }, _ => {}, } } } - // Step 34 + // Step 35-36 if let Some(Some(ref init_body)) = init.body { - // Step 34.2 - let extracted_body_tmp = init_body.extract(); - input_body = Some(extracted_body_tmp.0); - let content_type = extracted_body_tmp.1; - - // Step 34.3 - if let Some(contents) = content_type { - if !r.Headers().Has(ByteString::new(b"Content-Type".to_vec())).unwrap() { - try!(r.Headers().Append(ByteString::new(b"Content-Type".to_vec()), - ByteString::new(contents.as_bytes().to_vec()))); + // Step 36.2 TODO "If init["keepalive"] exists and is true..." + + // Step 36.3 + let mut extracted_body = init_body.extract(global)?; + + // Step 36.4 + if let Some(contents) = extracted_body.content_type.take() { + let ct_header_name = b"Content-Type"; + if !r + .Headers() + .Has(ByteString::new(ct_header_name.to_vec())) + .unwrap() + { + let ct_header_val = contents.as_bytes(); + r.Headers().Append( + ByteString::new(ct_header_name.to_vec()), + ByteString::new(ct_header_val.to_vec()), + )?; + + // In Servo r.Headers's header list isn't a pointer to + // the same actual list as r.request's, and so we need to + // append to both lists to keep them in sync. + if let Ok(v) = HeaderValue::from_bytes(ct_header_val) { + r.request + .borrow_mut() + .headers + .insert(HeaderName::from_bytes(ct_header_name).unwrap(), v); + } } } + + let (net_body, stream) = extracted_body.into_net_request_body(); + r.body_stream.set(Some(&*stream)); + input_body = Some(net_body); } - // Step 35 + // Step 37 "TODO if body is non-null and body's source is null..." + // This looks like where we need to set the use-preflight flag + // if the request has a body and nothing else has set the flag. + + // Step 38 is done earlier + + // Step 39 + // TODO: `ReadableStream` object is not implemented in Servo yet. + + // Step 40 r.request.borrow_mut().body = input_body; - // Step 36 + // Step 41 let extracted_mime_type = r.Headers().extract_mime_type(); *r.mime_type.borrow_mut() = extracted_mime_type; - // Step 37 - // TODO: `ReadableStream` object is not implemented in Servo yet. - - // Step 38 + // Step 42 Ok(r) } - - // https://fetch.spec.whatwg.org/#concept-body-locked - fn locked(&self) -> bool { - // TODO: ReadableStream is unimplemented. Just return false - // for now. - false - } } impl Request { - fn from_net_request(global: &GlobalScope, - is_service_worker_global_scope: bool, - net_request: NetTraitsRequest) -> Root<Request> { - let r = Request::new(global, - net_request.current_url(), - is_service_worker_global_scope); + fn from_net_request(global: &GlobalScope, net_request: NetTraitsRequest) -> DomRoot<Request> { + let r = Request::new(global, net_request.current_url()); *r.request.borrow_mut() = net_request; r } - fn clone_from(r: &Request) -> Fallible<Root<Request>> { + fn clone_from(r: &Request) -> Fallible<DomRoot<Request>> { let req = r.request.borrow(); let url = req.url(); - let is_service_worker_global_scope = req.is_service_worker_global_scope; - let body_used = r.body_used.get(); let mime_type = r.mime_type.borrow().clone(); let headers_guard = r.Headers().get_guard(); - let r_clone = Request::new(&r.global(), url, is_service_worker_global_scope); + let r_clone = Request::new(&r.global(), url); r_clone.request.borrow_mut().pipeline_id = req.pipeline_id; { let mut borrowed_r_request = r_clone.request.borrow_mut(); borrowed_r_request.origin = req.origin.clone(); } *r_clone.request.borrow_mut() = req.clone(); - r_clone.body_used.set(body_used); *r_clone.mime_type.borrow_mut() = mime_type; - try!(r_clone.Headers().fill(Some(HeadersInit::Headers(r.Headers())))); + r_clone.Headers().copy_from_headers(r.Headers())?; r_clone.Headers().set_guard(headers_guard); Ok(r_clone) } @@ -456,43 +482,32 @@ impl Request { } } -fn net_request_from_global(global: &GlobalScope, - url: ServoUrl, - is_service_worker_global_scope: bool) -> NetTraitsRequest { +fn net_request_from_global(global: &GlobalScope, url: ServoUrl) -> NetTraitsRequest { let origin = Origin::Origin(global.get_url().origin()); + let https_state = global.get_https_state(); let pipeline_id = global.pipeline_id(); - NetTraitsRequest::new(url, - Some(origin), - is_service_worker_global_scope, - Some(pipeline_id)) + let referrer = global.get_referrer(); + NetTraitsRequest::new(url, Some(origin), referrer, Some(pipeline_id), https_state) } // https://fetch.spec.whatwg.org/#concept-method-normalize -fn normalize_method(m: &str) -> HttpMethod { - match m { - m if m.eq_ignore_ascii_case("DELETE") => HttpMethod::Delete, - m if m.eq_ignore_ascii_case("GET") => HttpMethod::Get, - m if m.eq_ignore_ascii_case("HEAD") => HttpMethod::Head, - m if m.eq_ignore_ascii_case("OPTIONS") => HttpMethod::Options, - m if m.eq_ignore_ascii_case("POST") => HttpMethod::Post, - m if m.eq_ignore_ascii_case("PUT") => HttpMethod::Put, - m => HttpMethod::Extension(m.to_string()), - } +fn normalize_method(m: &str) -> Result<HttpMethod, InvalidMethod> { + match_ignore_ascii_case! { m, + "delete" => return Ok(HttpMethod::DELETE), + "get" => return Ok(HttpMethod::GET), + "head" => return Ok(HttpMethod::HEAD), + "options" => return Ok(HttpMethod::OPTIONS), + "post" => return Ok(HttpMethod::POST), + "put" => return Ok(HttpMethod::PUT), + _ => (), + } + debug!("Method: {:?}", m); + HttpMethod::from_str(m) } // https://fetch.spec.whatwg.org/#concept-method fn is_method(m: &ByteString) -> bool { - match m.to_lower().as_str() { - Some("get") => true, - Some("head") => true, - Some("post") => true, - Some("put") => true, - Some("delete") => true, - Some("connect") => true, - Some("options") => true, - Some("trace") => true, - _ => false, - } + m.as_str().is_some() } // https://fetch.spec.whatwg.org/#forbidden-method @@ -507,9 +522,7 @@ fn is_forbidden_method(m: &ByteString) -> bool { // https://fetch.spec.whatwg.org/#cors-safelisted-method fn is_cors_safelisted_method(m: &HttpMethod) -> bool { - m == &HttpMethod::Get || - m == &HttpMethod::Head || - m == &HttpMethod::Post + m == &HttpMethod::GET || m == &HttpMethod::HEAD || m == &HttpMethod::POST } // https://url.spec.whatwg.org/#include-credentials @@ -517,16 +530,14 @@ fn includes_credentials(input: &ServoUrl) -> bool { !input.username().is_empty() || input.password().is_some() } -// TODO: `Readable Stream` object is not implemented in Servo yet. // https://fetch.spec.whatwg.org/#concept-body-disturbed -fn request_is_disturbed(_input: &Request) -> bool { - false +fn request_is_disturbed(input: &Request) -> bool { + input.is_disturbed() } -// TODO: `Readable Stream` object is not implemented in Servo yet. // https://fetch.spec.whatwg.org/#concept-body-locked -fn request_is_locked(_input: &Request) -> bool { - false +fn request_is_locked(input: &Request) -> bool { + input.is_locked() } impl RequestMethods for Request { @@ -543,15 +554,10 @@ impl RequestMethods for Request { } // https://fetch.spec.whatwg.org/#dom-request-headers - fn Headers(&self) -> Root<Headers> { + fn Headers(&self) -> DomRoot<Headers> { self.headers.or_init(|| Headers::new(&self.global())) } - // https://fetch.spec.whatwg.org/#dom-request-type - fn Type(&self) -> RequestType { - self.request.borrow().type_.into() - } - // https://fetch.spec.whatwg.org/#dom-request-destination fn Destination(&self) -> RequestDestination { self.request.borrow().destination.into() @@ -561,23 +567,27 @@ impl RequestMethods for Request { fn Referrer(&self) -> USVString { let r = self.request.borrow(); USVString(match r.referrer { - NetTraitsRequestReferrer::NoReferrer => String::from("no-referrer"), - NetTraitsRequestReferrer::Client => String::from("about:client"), + NetTraitsRequestReferrer::NoReferrer => String::from(""), + NetTraitsRequestReferrer::Client(_) => String::from("about:client"), NetTraitsRequestReferrer::ReferrerUrl(ref u) => { let u_c = u.clone(); u_c.into_string() - } + }, }) } // https://fetch.spec.whatwg.org/#dom-request-referrerpolicy fn ReferrerPolicy(&self) -> ReferrerPolicy { - self.request.borrow().referrer_policy.map(|m| m.into()).unwrap_or(ReferrerPolicy::_empty) + self.request + .borrow() + .referrer_policy + .map(|m| m.into()) + .unwrap_or(ReferrerPolicy::_empty) } // https://fetch.spec.whatwg.org/#dom-request-mode fn Mode(&self) -> RequestMode { - self.request.borrow().mode.into() + self.request.borrow().mode.clone().into() } // https://fetch.spec.whatwg.org/#dom-request-credentials @@ -604,13 +614,18 @@ impl RequestMethods for Request { DOMString::from_string(r.integrity_metadata.clone()) } + /// <https://fetch.spec.whatwg.org/#dom-body-body> + fn GetBody(&self, _cx: SafeJSContext) -> Option<NonNull<JSObject>> { + self.body().map(|stream| stream.get_js_stream()) + } + // https://fetch.spec.whatwg.org/#dom-body-bodyused fn BodyUsed(&self) -> bool { - self.body_used.get() + self.is_disturbed() } // https://fetch.spec.whatwg.org/#dom-request-clone - fn Clone(&self) -> Fallible<Root<Request>> { + fn Clone(&self) -> Fallible<DomRoot<Request>> { // Step 1 if request_is_locked(self) { return Err(Error::Type("Request is locked".to_string())); @@ -623,54 +638,51 @@ impl RequestMethods for Request { Request::clone_from(self) } - #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#dom-body-text fn Text(&self) -> Rc<Promise> { consume_body(self, BodyType::Text) } - #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#dom-body-blob fn Blob(&self) -> Rc<Promise> { consume_body(self, BodyType::Blob) } - #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#dom-body-formdata fn FormData(&self) -> Rc<Promise> { consume_body(self, BodyType::FormData) } - #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#dom-body-json fn Json(&self) -> Rc<Promise> { consume_body(self, BodyType::Json) } -} -impl BodyOperations for Request { - fn get_body_used(&self) -> bool { - self.BodyUsed() + // https://fetch.spec.whatwg.org/#dom-body-arraybuffer + fn ArrayBuffer(&self) -> Rc<Promise> { + consume_body(self, BodyType::ArrayBuffer) } +} - fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) { - assert!(self.body_promise.borrow().is_none()); - self.body_used.set(true); - *self.body_promise.borrow_mut() = Some((p.clone(), body_type)); +impl BodyMixin for Request { + fn is_disturbed(&self) -> bool { + let body_stream = self.body_stream.get(); + body_stream + .as_ref() + .map_or(false, |stream| stream.is_disturbed()) } fn is_locked(&self) -> bool { - self.locked() + let body_stream = self.body_stream.get(); + body_stream.map_or(false, |stream| stream.is_locked()) } - fn take_body(&self) -> Option<Vec<u8>> { - let mut request = self.request.borrow_mut(); - let body = request.body.take(); - Some(body.unwrap_or(vec![])) + fn body(&self) -> Option<DomRoot<ReadableStream>> { + self.body_stream.get() } - fn get_mime_type(&self) -> Ref<Vec<u8>> { - self.mime_type.borrow() + fn get_mime_type(&self) -> Vec<u8> { + self.mime_type.borrow().clone() } } @@ -724,20 +736,21 @@ impl Into<NetTraitsRequestDestination> for RequestDestination { fn into(self) -> NetTraitsRequestDestination { match self { RequestDestination::_empty => NetTraitsRequestDestination::None, + RequestDestination::Audio => NetTraitsRequestDestination::Audio, RequestDestination::Document => NetTraitsRequestDestination::Document, RequestDestination::Embed => NetTraitsRequestDestination::Embed, RequestDestination::Font => NetTraitsRequestDestination::Font, RequestDestination::Image => NetTraitsRequestDestination::Image, RequestDestination::Manifest => NetTraitsRequestDestination::Manifest, - RequestDestination::Media => NetTraitsRequestDestination::Media, RequestDestination::Object => NetTraitsRequestDestination::Object, RequestDestination::Report => NetTraitsRequestDestination::Report, RequestDestination::Script => NetTraitsRequestDestination::Script, - RequestDestination::Serviceworker => NetTraitsRequestDestination::ServiceWorker, RequestDestination::Sharedworker => NetTraitsRequestDestination::SharedWorker, RequestDestination::Style => NetTraitsRequestDestination::Style, + RequestDestination::Track => NetTraitsRequestDestination::Track, + RequestDestination::Video => NetTraitsRequestDestination::Video, RequestDestination::Worker => NetTraitsRequestDestination::Worker, - RequestDestination::Xslt => NetTraitsRequestDestination::XSLT, + RequestDestination::Xslt => NetTraitsRequestDestination::Xslt, } } } @@ -746,50 +759,26 @@ impl Into<RequestDestination> for NetTraitsRequestDestination { fn into(self) -> RequestDestination { match self { NetTraitsRequestDestination::None => RequestDestination::_empty, + NetTraitsRequestDestination::Audio => RequestDestination::Audio, NetTraitsRequestDestination::Document => RequestDestination::Document, NetTraitsRequestDestination::Embed => RequestDestination::Embed, NetTraitsRequestDestination::Font => RequestDestination::Font, NetTraitsRequestDestination::Image => RequestDestination::Image, NetTraitsRequestDestination::Manifest => RequestDestination::Manifest, - NetTraitsRequestDestination::Media => RequestDestination::Media, NetTraitsRequestDestination::Object => RequestDestination::Object, NetTraitsRequestDestination::Report => RequestDestination::Report, NetTraitsRequestDestination::Script => RequestDestination::Script, - NetTraitsRequestDestination::ServiceWorker => RequestDestination::Serviceworker, + NetTraitsRequestDestination::ServiceWorker | + NetTraitsRequestDestination::AudioWorklet | + NetTraitsRequestDestination::PaintWorklet => { + panic!("ServiceWorker request destination should not be exposed to DOM") + }, NetTraitsRequestDestination::SharedWorker => RequestDestination::Sharedworker, NetTraitsRequestDestination::Style => RequestDestination::Style, - NetTraitsRequestDestination::XSLT => RequestDestination::Xslt, + NetTraitsRequestDestination::Track => RequestDestination::Track, + NetTraitsRequestDestination::Video => RequestDestination::Video, NetTraitsRequestDestination::Worker => RequestDestination::Worker, - } - } -} - -impl Into<NetTraitsRequestType> for RequestType { - fn into(self) -> NetTraitsRequestType { - match self { - RequestType::_empty => NetTraitsRequestType::None, - RequestType::Audio => NetTraitsRequestType::Audio, - RequestType::Font => NetTraitsRequestType::Font, - RequestType::Image => NetTraitsRequestType::Image, - RequestType::Script => NetTraitsRequestType::Script, - RequestType::Style => NetTraitsRequestType::Style, - RequestType::Track => NetTraitsRequestType::Track, - RequestType::Video => NetTraitsRequestType::Video, - } - } -} - -impl Into<RequestType> for NetTraitsRequestType { - fn into(self) -> RequestType { - match self { - NetTraitsRequestType::None => RequestType::_empty, - NetTraitsRequestType::Audio => RequestType::Audio, - NetTraitsRequestType::Font => RequestType::Font, - NetTraitsRequestType::Image => RequestType::Image, - NetTraitsRequestType::Script => RequestType::Script, - NetTraitsRequestType::Style => RequestType::Style, - NetTraitsRequestType::Track => RequestType::Track, - NetTraitsRequestType::Video => RequestType::Video, + NetTraitsRequestDestination::Xslt => RequestDestination::Xslt, } } } @@ -812,7 +801,9 @@ impl Into<RequestMode> for NetTraitsRequestMode { NetTraitsRequestMode::SameOrigin => RequestMode::Same_origin, NetTraitsRequestMode::NoCors => RequestMode::No_cors, NetTraitsRequestMode::CorsMode => RequestMode::Cors, - NetTraitsRequestMode::WebSocket => unreachable!("Websocket request mode should never be exposed to JS"), + NetTraitsRequestMode::WebSocket { .. } => { + unreachable!("Websocket request mode should never be exposed to Dom") + }, } } } @@ -824,14 +815,17 @@ impl Into<MsgReferrerPolicy> for ReferrerPolicy { match self { ReferrerPolicy::_empty => MsgReferrerPolicy::NoReferrer, ReferrerPolicy::No_referrer => MsgReferrerPolicy::NoReferrer, - ReferrerPolicy::No_referrer_when_downgrade => - MsgReferrerPolicy::NoReferrerWhenDowngrade, + ReferrerPolicy::No_referrer_when_downgrade => { + MsgReferrerPolicy::NoReferrerWhenDowngrade + }, ReferrerPolicy::Origin => MsgReferrerPolicy::Origin, ReferrerPolicy::Origin_when_cross_origin => MsgReferrerPolicy::OriginWhenCrossOrigin, ReferrerPolicy::Unsafe_url => MsgReferrerPolicy::UnsafeUrl, + ReferrerPolicy::Same_origin => MsgReferrerPolicy::SameOrigin, ReferrerPolicy::Strict_origin => MsgReferrerPolicy::StrictOrigin, - ReferrerPolicy::Strict_origin_when_cross_origin => - MsgReferrerPolicy::StrictOriginWhenCrossOrigin, + ReferrerPolicy::Strict_origin_when_cross_origin => { + MsgReferrerPolicy::StrictOriginWhenCrossOrigin + }, } } } @@ -840,15 +834,17 @@ impl Into<ReferrerPolicy> for MsgReferrerPolicy { fn into(self) -> ReferrerPolicy { match self { MsgReferrerPolicy::NoReferrer => ReferrerPolicy::No_referrer, - MsgReferrerPolicy::NoReferrerWhenDowngrade => - ReferrerPolicy::No_referrer_when_downgrade, + MsgReferrerPolicy::NoReferrerWhenDowngrade => { + ReferrerPolicy::No_referrer_when_downgrade + }, MsgReferrerPolicy::Origin => ReferrerPolicy::Origin, - MsgReferrerPolicy::SameOrigin => ReferrerPolicy::Origin, MsgReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicy::Origin_when_cross_origin, MsgReferrerPolicy::UnsafeUrl => ReferrerPolicy::Unsafe_url, + MsgReferrerPolicy::SameOrigin => ReferrerPolicy::Same_origin, MsgReferrerPolicy::StrictOrigin => ReferrerPolicy::Strict_origin, - MsgReferrerPolicy::StrictOriginWhenCrossOrigin => - ReferrerPolicy::Strict_origin_when_cross_origin, + MsgReferrerPolicy::StrictOriginWhenCrossOrigin => { + ReferrerPolicy::Strict_origin_when_cross_origin + }, } } } @@ -875,13 +871,13 @@ impl Into<RequestRedirect> for NetTraitsRequestRedirect { impl Clone for HeadersInit { fn clone(&self) -> HeadersInit { - match self { - &HeadersInit::Headers(ref h) => - HeadersInit::Headers(h.clone()), - &HeadersInit::ByteStringSequenceSequence(ref b) => - HeadersInit::ByteStringSequenceSequence(b.clone()), - &HeadersInit::ByteStringMozMap(ref m) => - HeadersInit::ByteStringMozMap(m.clone()), + match self { + &HeadersInit::ByteStringSequenceSequence(ref b) => { + HeadersInit::ByteStringSequenceSequence(b.clone()) + }, + &HeadersInit::ByteStringByteStringRecord(ref m) => { + HeadersInit::ByteStringByteStringRecord(m.clone()) + }, } } } diff --git a/components/script/dom/response.rs b/components/script/dom/response.rs index 089a8acca6c..432fcbeb3e4 100644 --- a/components/script/dom/response.rs +++ b/components/script/dom/response.rs @@ -1,31 +1,34 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use body::{BodyOperations, BodyType, consume_body, consume_body_with_promise}; -use core::cell::Cell; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods}; -use dom::bindings::codegen::Bindings::ResponseBinding; -use dom::bindings::codegen::Bindings::ResponseBinding::{ResponseMethods, ResponseType as DOMResponseType}; -use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::{ByteString, USVString}; -use dom::globalscope::GlobalScope; -use dom::headers::{Headers, Guard}; -use dom::headers::{is_vchar, is_obs_text}; -use dom::promise::Promise; -use dom::xmlhttprequest::Extractable; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::body::{consume_body, BodyMixin, BodyType}; +use crate::body::{Extractable, ExtractedBody}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods; +use crate::dom::bindings::codegen::Bindings::ResponseBinding; +use crate::dom::bindings::codegen::Bindings::ResponseBinding::{ + ResponseMethods, ResponseType as DOMResponseType, +}; +use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{ByteString, USVString}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::headers::{is_obs_text, is_vchar}; +use crate::dom::headers::{Guard, Headers}; +use crate::dom::promise::Promise; +use crate::dom::readablestream::{ExternalUnderlyingSource, ReadableStream}; +use crate::script_runtime::JSContext as SafeJSContext; +use crate::script_runtime::StreamConsumer; use dom_struct::dom_struct; -use hyper::header::Headers as HyperHeaders; -use hyper::status::StatusCode; +use http::header::HeaderMap as HyperHeaders; +use hyper::StatusCode; use hyper_serde::Serde; -use net_traits::response::{ResponseBody as NetTraitsResponseBody}; +use js::jsapi::JSObject; use servo_url::ServoUrl; -use std::cell::Ref; -use std::mem; +use std::ptr::NonNull; use std::rc::Rc; use std::str::FromStr; use url::Position; @@ -33,64 +36,75 @@ use url::Position; #[dom_struct] pub struct Response { reflector_: Reflector, - headers_reflector: MutNullableJS<Headers>, - mime_type: DOMRefCell<Vec<u8>>, - body_used: Cell<bool>, + headers_reflector: MutNullableDom<Headers>, + mime_type: DomRefCell<Vec<u8>>, /// `None` can be considered a StatusCode of `0`. - #[ignore_heap_size_of = "Defined in hyper"] - status: DOMRefCell<Option<StatusCode>>, - raw_status: DOMRefCell<Option<(u16, Vec<u8>)>>, - response_type: DOMRefCell<DOMResponseType>, - url: DOMRefCell<Option<ServoUrl>>, - url_list: DOMRefCell<Vec<ServoUrl>>, - // For now use the existing NetTraitsResponseBody enum - body: DOMRefCell<NetTraitsResponseBody>, - #[ignore_heap_size_of = "Rc"] - body_promise: DOMRefCell<Option<(Rc<Promise>, BodyType)>>, + #[ignore_malloc_size_of = "Defined in hyper"] + status: DomRefCell<Option<StatusCode>>, + raw_status: DomRefCell<Option<(u16, Vec<u8>)>>, + response_type: DomRefCell<DOMResponseType>, + url: DomRefCell<Option<ServoUrl>>, + url_list: DomRefCell<Vec<ServoUrl>>, + /// The stream of https://fetch.spec.whatwg.org/#body. + body_stream: MutNullableDom<ReadableStream>, + #[ignore_malloc_size_of = "StreamConsumer"] + stream_consumer: DomRefCell<Option<StreamConsumer>>, + redirected: DomRefCell<bool>, } +#[allow(non_snake_case)] impl Response { - pub fn new_inherited() -> Response { + pub fn new_inherited(global: &GlobalScope) -> Response { + let stream = ReadableStream::new_with_external_underlying_source( + global, + ExternalUnderlyingSource::FetchResponse, + ); Response { reflector_: Reflector::new(), headers_reflector: Default::default(), - mime_type: DOMRefCell::new("".to_string().into_bytes()), - body_used: Cell::new(false), - status: DOMRefCell::new(Some(StatusCode::Ok)), - raw_status: DOMRefCell::new(Some((200, b"OK".to_vec()))), - response_type: DOMRefCell::new(DOMResponseType::Default), - url: DOMRefCell::new(None), - url_list: DOMRefCell::new(vec![]), - body: DOMRefCell::new(NetTraitsResponseBody::Empty), - body_promise: DOMRefCell::new(None), + mime_type: DomRefCell::new("".to_string().into_bytes()), + status: DomRefCell::new(Some(StatusCode::OK)), + raw_status: DomRefCell::new(Some((200, b"".to_vec()))), + response_type: DomRefCell::new(DOMResponseType::Default), + url: DomRefCell::new(None), + url_list: DomRefCell::new(vec![]), + body_stream: MutNullableDom::new(Some(&*stream)), + stream_consumer: DomRefCell::new(None), + redirected: DomRefCell::new(false), } } // https://fetch.spec.whatwg.org/#dom-response - pub fn new(global: &GlobalScope) -> Root<Response> { - reflect_dom_object(box Response::new_inherited(), global, ResponseBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<Response> { + reflect_dom_object(Box::new(Response::new_inherited(global)), global) } - pub fn Constructor(global: &GlobalScope, body: Option<BodyInit>, init: &ResponseBinding::ResponseInit) - -> Fallible<Root<Response>> { + pub fn Constructor( + global: &GlobalScope, + body: Option<BodyInit>, + init: &ResponseBinding::ResponseInit, + ) -> Fallible<DomRoot<Response>> { // Step 1 if init.status < 200 || init.status > 599 { - return Err(Error::Range( - format!("init's status member should be in the range 200 to 599, inclusive, but is {}" - , init.status))); + return Err(Error::Range(format!( + "init's status member should be in the range 200 to 599, inclusive, but is {}", + init.status + ))); } // Step 2 if !is_valid_status_text(&init.statusText) { - return Err(Error::Type("init's statusText member does not match the reason-phrase token production" - .to_string())); + return Err(Error::Type( + "init's statusText member does not match the reason-phrase token production" + .to_string(), + )); } // Step 3 let r = Response::new(global); // Step 4 - *r.status.borrow_mut() = Some(StatusCode::from_u16(init.status)); + *r.status.borrow_mut() = Some(StatusCode::from_u16(init.status).unwrap()); // Step 5 *r.raw_status.borrow_mut() = Some((init.status, init.statusText.clone().into())); @@ -101,7 +115,7 @@ impl Response { r.Headers().empty_header_list(); // Step 6.2 - try!(r.Headers().fill(Some(headers_member.clone()))); + r.Headers().fill(Some(headers_member.clone()))?; } // Step 7 @@ -109,18 +123,31 @@ impl Response { // Step 7.1 if is_null_body_status(init.status) { return Err(Error::Type( - "Body is non-null but init's status member is a null body status".to_string())); + "Body is non-null but init's status member is a null body status".to_string(), + )); }; // Step 7.3 - let (extracted_body, content_type) = body.extract(); - *r.body.borrow_mut() = NetTraitsResponseBody::Done(extracted_body); + let ExtractedBody { + stream, + total_bytes: _, + content_type, + source: _, + } = body.extract(global)?; + + r.body_stream.set(Some(&*stream)); // Step 7.4 if let Some(content_type_contents) = content_type { - if !r.Headers().Has(ByteString::new(b"Content-Type".to_vec())).unwrap() { - try!(r.Headers().Append(ByteString::new(b"Content-Type".to_vec()), - ByteString::new(content_type_contents.as_bytes().to_vec()))); + if !r + .Headers() + .Has(ByteString::new(b"Content-Type".to_vec())) + .unwrap() + { + r.Headers().Append( + ByteString::new(b"Content-Type".to_vec()), + ByteString::new(content_type_contents.as_bytes().to_vec()), + )?; } }; } @@ -139,7 +166,7 @@ impl Response { } // https://fetch.spec.whatwg.org/#dom-response-error - pub fn Error(global: &GlobalScope) -> Root<Response> { + pub fn Error(global: &GlobalScope) -> DomRoot<Response> { let r = Response::new(global); *r.response_type.borrow_mut() = DOMResponseType::Error; r.Headers().set_guard(Guard::Immutable); @@ -148,7 +175,11 @@ impl Response { } // https://fetch.spec.whatwg.org/#dom-response-redirect - pub fn Redirect(global: &GlobalScope, url: USVString, status: u16) -> Fallible<Root<Response>> { + pub fn Redirect( + global: &GlobalScope, + url: USVString, + status: u16, + ) -> Fallible<DomRoot<Response>> { // Step 1 let base_url = global.api_base_url(); let parsed_url = base_url.join(&url.0); @@ -169,12 +200,14 @@ impl Response { let r = Response::new(global); // Step 5 - *r.status.borrow_mut() = Some(StatusCode::from_u16(status)); + *r.status.borrow_mut() = Some(StatusCode::from_u16(status).unwrap()); *r.raw_status.borrow_mut() = Some((status, b"".to_vec())); // Step 6 - let url_bytestring = ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec())); - try!(r.Headers().Set(ByteString::new(b"Location".to_vec()), url_bytestring)); + let url_bytestring = + ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec())); + r.Headers() + .Set(ByteString::new(b"Location".to_vec()), url_bytestring)?; // Step 4 continued // Headers Guard is set to Immutable here to prevent error in Step 6 @@ -184,44 +217,32 @@ impl Response { Ok(r) } - // https://fetch.spec.whatwg.org/#concept-body-locked - fn locked(&self) -> bool { - // TODO: ReadableStream is unimplemented. Just return false - // for now. - false + pub fn error_stream(&self, error: Error) { + if let Some(body) = self.body_stream.get() { + body.error_native(error); + } } } -impl BodyOperations for Response { - fn get_body_used(&self) -> bool { - self.BodyUsed() - } - - fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) { - assert!(self.body_promise.borrow().is_none()); - self.body_used.set(true); - *self.body_promise.borrow_mut() = Some((p.clone(), body_type)); +impl BodyMixin for Response { + fn is_disturbed(&self) -> bool { + self.body_stream + .get() + .map_or(false, |stream| stream.is_disturbed()) } fn is_locked(&self) -> bool { - self.locked() + self.body_stream + .get() + .map_or(false, |stream| stream.is_locked()) } - fn take_body(&self) -> Option<Vec<u8>> { - let body = mem::replace(&mut *self.body.borrow_mut(), NetTraitsResponseBody::Empty); - match body { - NetTraitsResponseBody::Done(bytes) => { - Some(bytes) - }, - body => { - mem::replace(&mut *self.body.borrow_mut(), body); - None - }, - } + fn body(&self) -> Option<DomRoot<ReadableStream>> { + self.body_stream.get() } - fn get_mime_type(&self) -> Ref<Vec<u8>> { - self.mime_type.borrow() + fn get_mime_type(&self) -> Vec<u8> { + self.mime_type.borrow().clone() } } @@ -249,18 +270,22 @@ fn is_null_body_status(status: u16) -> bool { impl ResponseMethods for Response { // https://fetch.spec.whatwg.org/#dom-response-type fn Type(&self) -> DOMResponseType { - *self.response_type.borrow()//into() + *self.response_type.borrow() //into() } // https://fetch.spec.whatwg.org/#dom-response-url fn Url(&self) -> USVString { - USVString(String::from((*self.url.borrow()).as_ref().map(|u| serialize_without_fragment(u)).unwrap_or(""))) + USVString(String::from( + (*self.url.borrow()) + .as_ref() + .map(|u| serialize_without_fragment(u)) + .unwrap_or(""), + )) } // https://fetch.spec.whatwg.org/#dom-response-redirected fn Redirected(&self) -> bool { - let url_list_len = self.url_list.borrow().len(); - url_list_len > 1 + return *self.redirected.borrow(); } // https://fetch.spec.whatwg.org/#dom-response-status @@ -275,9 +300,9 @@ impl ResponseMethods for Response { fn Ok(&self) -> bool { match *self.status.borrow() { Some(s) => { - let status_num = s.to_u16(); + let status_num = s.as_u16(); return status_num >= 200 && status_num <= 299; - } + }, None => false, } } @@ -286,26 +311,27 @@ impl ResponseMethods for Response { fn StatusText(&self) -> ByteString { match *self.raw_status.borrow() { Some((_, ref st)) => ByteString::new(st.clone()), - None => ByteString::new(b"OK".to_vec()), + None => ByteString::new(b"".to_vec()), } } // https://fetch.spec.whatwg.org/#dom-response-headers - fn Headers(&self) -> Root<Headers> { - self.headers_reflector.or_init(|| Headers::for_response(&self.global())) + fn Headers(&self) -> DomRoot<Headers> { + self.headers_reflector + .or_init(|| Headers::for_response(&self.global())) } // https://fetch.spec.whatwg.org/#dom-response-clone - fn Clone(&self) -> Fallible<Root<Response>> { + fn Clone(&self) -> Fallible<DomRoot<Response>> { // Step 1 - if self.is_locked() || self.body_used.get() { + if self.is_locked() || self.is_disturbed() { return Err(Error::Type("cannot clone a disturbed response".to_string())); } // Step 2 let new_response = Response::new(&self.global()); new_response.Headers().set_guard(self.Headers().get_guard()); - try!(new_response.Headers().fill(Some(HeadersInit::Headers(self.Headers())))); + new_response.Headers().copy_from_headers(self.Headers())?; // https://fetch.spec.whatwg.org/#concept-response-clone // Instead of storing a net_traits::Response internally, we @@ -316,8 +342,8 @@ impl ResponseMethods for Response { *new_response.url.borrow_mut() = self.url.borrow().clone(); *new_response.url_list.borrow_mut() = self.url_list.borrow().clone(); - if *self.body.borrow() != NetTraitsResponseBody::Empty { - *new_response.body.borrow_mut() = self.body.borrow().clone(); + if let Some(stream) = self.body_stream.get().clone() { + new_response.body_stream.set(Some(&*stream)); } // Step 3 @@ -329,32 +355,38 @@ impl ResponseMethods for Response { // https://fetch.spec.whatwg.org/#dom-body-bodyused fn BodyUsed(&self) -> bool { - self.body_used.get() + self.is_disturbed() + } + + /// <https://fetch.spec.whatwg.org/#dom-body-body> + fn GetBody(&self, _cx: SafeJSContext) -> Option<NonNull<JSObject>> { + self.body().map(|stream| stream.get_js_stream()) } - #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#dom-body-text fn Text(&self) -> Rc<Promise> { consume_body(self, BodyType::Text) } - #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#dom-body-blob fn Blob(&self) -> Rc<Promise> { consume_body(self, BodyType::Blob) } - #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#dom-body-formdata fn FormData(&self) -> Rc<Promise> { consume_body(self, BodyType::FormData) } - #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#dom-body-json fn Json(&self) -> Rc<Promise> { consume_body(self, BodyType::Json) } + + // https://fetch.spec.whatwg.org/#dom-body-arraybuffer + fn ArrayBuffer(&self) -> Rc<Promise> { + consume_body(self, BodyType::ArrayBuffer) + } } fn serialize_without_fragment(url: &ServoUrl) -> &str { @@ -364,6 +396,7 @@ fn serialize_without_fragment(url: &ServoUrl) -> &str { impl Response { pub fn set_type(&self, new_response_type: DOMResponseType) { *self.response_type.borrow_mut() = new_response_type; + self.set_response_members_by_type(new_response_type); } pub fn set_headers(&self, option_hyper_headers: Option<Serde<HyperHeaders>>) { @@ -371,6 +404,7 @@ impl Response { Some(hyper_headers) => hyper_headers.into_inner(), None => HyperHeaders::new(), }); + *self.mime_type.borrow_mut() = self.Headers().extract_mime_type(); } pub fn set_raw_status(&self, status: Option<(u16, Vec<u8>)>) { @@ -381,11 +415,56 @@ impl Response { *self.url.borrow_mut() = Some(final_url); } + pub fn set_redirected(&self, is_redirected: bool) { + *self.redirected.borrow_mut() = is_redirected; + } + + fn set_response_members_by_type(&self, response_type: DOMResponseType) { + match response_type { + DOMResponseType::Error => { + *self.status.borrow_mut() = None; + self.set_raw_status(None); + self.set_headers(None); + }, + DOMResponseType::Opaque => { + *self.url_list.borrow_mut() = vec![]; + *self.status.borrow_mut() = None; + self.set_raw_status(None); + self.set_headers(None); + self.body_stream.set(None); + }, + DOMResponseType::Opaqueredirect => { + *self.status.borrow_mut() = None; + self.set_raw_status(None); + self.set_headers(None); + self.body_stream.set(None); + }, + DOMResponseType::Default => {}, + DOMResponseType::Basic => {}, + DOMResponseType::Cors => {}, + } + } + + pub fn set_stream_consumer(&self, sc: Option<StreamConsumer>) { + *self.stream_consumer.borrow_mut() = sc; + } + + pub fn stream_chunk(&self, chunk: Vec<u8>) { + // Note, are these two actually mutually exclusive? + if let Some(stream_consumer) = self.stream_consumer.borrow_mut().as_ref() { + stream_consumer.consume_chunk(chunk.as_slice()); + } else if let Some(body) = self.body_stream.get() { + body.enqueue_native(chunk); + } + } + #[allow(unrooted_must_root)] - pub fn finish(&self, body: Vec<u8>) { - *self.body.borrow_mut() = NetTraitsResponseBody::Done(body); - if let Some((p, body_type)) = self.body_promise.borrow_mut().take() { - consume_body_with_promise(self, body_type, &p); + pub fn finish(&self) { + if let Some(body) = self.body_stream.get() { + body.close_native(); + } + if let Some(stream_consumer) = self.stream_consumer.borrow_mut().take() { + stream_consumer.stream_end(); } } } diff --git a/components/script/dom/rtcdatachannel.rs b/components/script/dom/rtcdatachannel.rs new file mode 100644 index 00000000000..052f3f13131 --- /dev/null +++ b/components/script/dom/rtcdatachannel.rs @@ -0,0 +1,387 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::RTCDataChannelBinding::RTCDataChannelInit; +use crate::dom::bindings::codegen::Bindings::RTCDataChannelBinding::RTCDataChannelMethods; +use crate::dom::bindings::codegen::Bindings::RTCDataChannelBinding::RTCDataChannelState; +use crate::dom::bindings::codegen::Bindings::RTCErrorBinding::{RTCErrorDetailType, RTCErrorInit}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::blob::Blob; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageevent::MessageEvent; +use crate::dom::rtcerror::RTCError; +use crate::dom::rtcerrorevent::RTCErrorEvent; +use crate::dom::rtcpeerconnection::RTCPeerConnection; +use dom_struct::dom_struct; +use js::conversions::ToJSValConvertible; +use js::jsapi::{JSAutoRealm, JSObject}; +use js::jsval::UndefinedValue; +use js::rust::CustomAutoRooterGuard; +use js::typedarray::{ArrayBuffer, ArrayBufferView, CreateWith}; +use script_traits::serializable::BlobImpl; +use servo_media::webrtc::{ + DataChannelId, DataChannelInit, DataChannelMessage, DataChannelState, WebRtcError, +}; +use std::cell::Cell; +use std::ptr; + +#[dom_struct] +pub struct RTCDataChannel { + eventtarget: EventTarget, + #[ignore_malloc_size_of = "defined in servo-media"] + servo_media_id: DataChannelId, + peer_connection: Dom<RTCPeerConnection>, + label: USVString, + ordered: bool, + max_packet_life_time: Option<u16>, + max_retransmits: Option<u16>, + protocol: USVString, + negotiated: bool, + id: Option<u16>, + ready_state: Cell<RTCDataChannelState>, + binary_type: DomRefCell<DOMString>, +} + +impl RTCDataChannel { + #[allow(unrooted_must_root)] + pub fn new_inherited( + peer_connection: &RTCPeerConnection, + label: USVString, + options: &RTCDataChannelInit, + servo_media_id: Option<DataChannelId>, + ) -> RTCDataChannel { + let mut init: DataChannelInit = options.into(); + init.label = label.to_string(); + + let controller = peer_connection.get_webrtc_controller().borrow(); + let servo_media_id = servo_media_id.unwrap_or( + controller + .as_ref() + .unwrap() + .create_data_channel(init) + .expect("Expected data channel id"), + ); + + let channel = RTCDataChannel { + eventtarget: EventTarget::new_inherited(), + servo_media_id, + peer_connection: Dom::from_ref(&peer_connection), + label, + ordered: options.ordered, + max_packet_life_time: options.maxPacketLifeTime, + max_retransmits: options.maxRetransmits, + protocol: options.protocol.clone(), + negotiated: options.negotiated, + id: options.id, + ready_state: Cell::new(RTCDataChannelState::Connecting), + binary_type: DomRefCell::new(DOMString::from("blob")), + }; + + channel + } + + pub fn new( + global: &GlobalScope, + peer_connection: &RTCPeerConnection, + label: USVString, + options: &RTCDataChannelInit, + servo_media_id: Option<DataChannelId>, + ) -> DomRoot<RTCDataChannel> { + let rtc_data_channel = reflect_dom_object( + Box::new(RTCDataChannel::new_inherited( + peer_connection, + label, + options, + servo_media_id, + )), + global, + ); + + peer_connection.register_data_channel(rtc_data_channel.servo_media_id, &*rtc_data_channel); + + rtc_data_channel + } + + pub fn on_open(&self) { + let event = Event::new( + &self.global(), + atom!("open"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); + event.upcast::<Event>().fire(self.upcast()); + } + + pub fn on_close(&self) { + let event = Event::new( + &self.global(), + atom!("close"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); + event.upcast::<Event>().fire(self.upcast()); + + self.peer_connection + .unregister_data_channel(&self.servo_media_id); + } + + pub fn on_error(&self, error: WebRtcError) { + let global = self.global(); + let cx = global.get_cx(); + let _ac = JSAutoRealm::new(*cx, self.reflector().get_jsobject().get()); + let init = RTCErrorInit { + errorDetail: RTCErrorDetailType::Data_channel_failure, + httpRequestStatusCode: None, + receivedAlert: None, + sctpCauseCode: None, + sdpLineNumber: None, + sentAlert: None, + }; + let message = match error { + WebRtcError::Backend(message) => DOMString::from(message), + }; + let error = RTCError::new(&global, &init, message); + let event = RTCErrorEvent::new(&global, atom!("error"), false, false, &error); + event.upcast::<Event>().fire(self.upcast()); + } + + #[allow(unsafe_code)] + pub fn on_message(&self, channel_message: DataChannelMessage) { + unsafe { + let global = self.global(); + let cx = global.get_cx(); + let _ac = JSAutoRealm::new(*cx, self.reflector().get_jsobject().get()); + rooted!(in(*cx) let mut message = UndefinedValue()); + + match channel_message { + DataChannelMessage::Text(text) => { + text.to_jsval(*cx, message.handle_mut()); + }, + DataChannelMessage::Binary(data) => match &**self.binary_type.borrow() { + "blob" => { + let blob = + Blob::new(&global, BlobImpl::new_from_bytes(data, "".to_owned())); + blob.to_jsval(*cx, message.handle_mut()); + }, + "arraybuffer" => { + rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>()); + assert!(ArrayBuffer::create( + *cx, + CreateWith::Slice(&data), + array_buffer.handle_mut() + ) + .is_ok()); + + (*array_buffer).to_jsval(*cx, message.handle_mut()); + }, + _ => unreachable!(), + }, + } + + MessageEvent::dispatch_jsval( + self.upcast(), + &global, + message.handle(), + Some(&global.origin().immutable().ascii_serialization()), + None, + vec![], + ); + } + } + + pub fn on_state_change(&self, state: DataChannelState) { + match state { + DataChannelState::Closing => { + let event = Event::new( + &self.global(), + atom!("closing"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); + event.upcast::<Event>().fire(self.upcast()); + }, + _ => {}, + }; + self.ready_state.set(state.into()); + } + + fn send(&self, source: &SendSource) -> Fallible<()> { + if self.ready_state.get() != RTCDataChannelState::Open { + return Err(Error::InvalidState); + } + + let message = match source { + SendSource::String(string) => DataChannelMessage::Text(string.0.clone()), + SendSource::Blob(blob) => { + DataChannelMessage::Binary(blob.get_bytes().unwrap_or(vec![])) + }, + SendSource::ArrayBuffer(array) => DataChannelMessage::Binary(array.to_vec()), + SendSource::ArrayBufferView(array) => DataChannelMessage::Binary(array.to_vec()), + }; + + let controller = self.peer_connection.get_webrtc_controller().borrow(); + controller + .as_ref() + .unwrap() + .send_data_channel_message(&self.servo_media_id, message); + + Ok(()) + } +} + +impl Drop for RTCDataChannel { + fn drop(&mut self) { + self.peer_connection + .unregister_data_channel(&self.servo_media_id); + } +} + +enum SendSource<'a, 'b> { + String(&'a USVString), + Blob(&'a Blob), + ArrayBuffer(CustomAutoRooterGuard<'b, ArrayBuffer>), + ArrayBufferView(CustomAutoRooterGuard<'b, ArrayBufferView>), +} + +impl RTCDataChannelMethods for RTCDataChannel { + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onopen + event_handler!(open, GetOnopen, SetOnopen); + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onbufferedamountlow + event_handler!( + bufferedamountlow, + GetOnbufferedamountlow, + SetOnbufferedamountlow + ); + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onerror + event_handler!(error, GetOnerror, SetOnerror); + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onclosing + event_handler!(closing, GetOnclosing, SetOnclosing); + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onclose + event_handler!(close, GetOnclose, SetOnclose); + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-onmessage + event_handler!(message, GetOnmessage, SetOnmessage); + + // https://www.w3.org/TR/webrtc/#dom-datachannel-label + fn Label(&self) -> USVString { + self.label.clone() + } + // https://www.w3.org/TR/webrtc/#dom-datachannel-ordered + fn Ordered(&self) -> bool { + self.ordered + } + + // https://www.w3.org/TR/webrtc/#dom-datachannel-maxpacketlifetime + fn GetMaxPacketLifeTime(&self) -> Option<u16> { + self.max_packet_life_time + } + + // https://www.w3.org/TR/webrtc/#dom-datachannel-maxretransmits + fn GetMaxRetransmits(&self) -> Option<u16> { + self.max_retransmits + } + + // https://www.w3.org/TR/webrtc/#dom-datachannel-protocol + fn Protocol(&self) -> USVString { + self.protocol.clone() + } + + // https://www.w3.org/TR/webrtc/#dom-datachannel-negotiated + fn Negotiated(&self) -> bool { + self.negotiated + } + + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-id + fn GetId(&self) -> Option<u16> { + self.id + } + + // https://www.w3.org/TR/webrtc/#dom-datachannel-readystate + fn ReadyState(&self) -> RTCDataChannelState { + self.ready_state.get() + } + + // XXX We need a way to know when the underlying data transport + // actually sends data from its queue to decrease buffered amount. + + // fn BufferedAmount(&self) -> u32; + // fn BufferedAmountLowThreshold(&self) -> u32; + // fn SetBufferedAmountLowThreshold(&self, value: u32) -> (); + + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-close + fn Close(&self) { + let controller = self.peer_connection.get_webrtc_controller().borrow(); + controller + .as_ref() + .unwrap() + .close_data_channel(&self.servo_media_id); + } + + // https://www.w3.org/TR/webrtc/#dom-datachannel-binarytype + fn BinaryType(&self) -> DOMString { + self.binary_type.borrow().clone() + } + + // https://www.w3.org/TR/webrtc/#dom-datachannel-binarytype + fn SetBinaryType(&self, value: DOMString) -> Fallible<()> { + if value != "blob" || value != "arraybuffer" { + return Err(Error::Syntax); + } + *self.binary_type.borrow_mut() = value; + Ok(()) + } + + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-send + fn Send(&self, data: USVString) -> Fallible<()> { + self.send(&SendSource::String(&data)) + } + + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-send!overload-1 + fn Send_(&self, data: &Blob) -> Fallible<()> { + self.send(&SendSource::Blob(data)) + } + + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-send!overload-2 + fn Send__(&self, data: CustomAutoRooterGuard<ArrayBuffer>) -> Fallible<()> { + self.send(&SendSource::ArrayBuffer(data)) + } + + // https://www.w3.org/TR/webrtc/#dom-rtcdatachannel-send!overload-3 + fn Send___(&self, data: CustomAutoRooterGuard<ArrayBufferView>) -> Fallible<()> { + self.send(&SendSource::ArrayBufferView(data)) + } +} + +impl From<&RTCDataChannelInit> for DataChannelInit { + fn from(init: &RTCDataChannelInit) -> DataChannelInit { + DataChannelInit { + label: String::new(), + id: init.id, + max_packet_life_time: init.maxPacketLifeTime, + max_retransmits: init.maxRetransmits, + negotiated: init.negotiated, + ordered: init.ordered, + protocol: init.protocol.to_string(), + } + } +} + +impl From<DataChannelState> for RTCDataChannelState { + fn from(state: DataChannelState) -> RTCDataChannelState { + match state { + DataChannelState::New | + DataChannelState::Connecting | + DataChannelState::__Unknown(_) => RTCDataChannelState::Connecting, + DataChannelState::Open => RTCDataChannelState::Open, + DataChannelState::Closing => RTCDataChannelState::Closing, + DataChannelState::Closed => RTCDataChannelState::Closed, + } + } +} diff --git a/components/script/dom/rtcdatachannelevent.rs b/components/script/dom/rtcdatachannelevent.rs new file mode 100644 index 00000000000..95935f68107 --- /dev/null +++ b/components/script/dom/rtcdatachannelevent.rs @@ -0,0 +1,77 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::RTCDataChannelEventBinding::RTCDataChannelEventInit; +use crate::dom::bindings::codegen::Bindings::RTCDataChannelEventBinding::RTCDataChannelEventMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::rtcdatachannel::RTCDataChannel; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct RTCDataChannelEvent { + event: Event, + channel: Dom<RTCDataChannel>, +} + +impl RTCDataChannelEvent { + fn new_inherited(channel: &RTCDataChannel) -> RTCDataChannelEvent { + RTCDataChannelEvent { + event: Event::new_inherited(), + channel: Dom::from_ref(channel), + } + } + + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + channel: &RTCDataChannel, + ) -> DomRoot<RTCDataChannelEvent> { + let event = reflect_dom_object( + Box::new(RTCDataChannelEvent::new_inherited(&channel)), + global, + ); + { + let event = event.upcast::<Event>(); + event.init_event(type_, bubbles, cancelable); + } + event + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &RTCDataChannelEventInit, + ) -> DomRoot<RTCDataChannelEvent> { + RTCDataChannelEvent::new( + &window.global(), + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + &init.channel, + ) + } +} + +impl RTCDataChannelEventMethods for RTCDataChannelEvent { + // https://www.w3.org/TR/webrtc/#dom-datachannelevent-channel + fn Channel(&self) -> DomRoot<RTCDataChannel> { + DomRoot::from_ref(&*self.channel) + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/rtcerror.rs b/components/script/dom/rtcerror.rs new file mode 100644 index 00000000000..615cd267c9f --- /dev/null +++ b/components/script/dom/rtcerror.rs @@ -0,0 +1,90 @@ +/* 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 crate::dom::bindings::codegen::Bindings::RTCErrorBinding::RTCErrorDetailType; +use crate::dom::bindings::codegen::Bindings::RTCErrorBinding::RTCErrorInit; +use crate::dom::bindings::codegen::Bindings::RTCErrorBinding::RTCErrorMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::domexception::{DOMErrorName, DOMException}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct RTCError { + exception: Dom<DOMException>, + error_detail: RTCErrorDetailType, + sdp_line_number: Option<i32>, + http_request_status_code: Option<i32>, + sctp_cause_code: Option<i32>, + received_alert: Option<u32>, + sent_alert: Option<u32>, +} + +impl RTCError { + fn new_inherited(global: &GlobalScope, init: &RTCErrorInit, message: DOMString) -> RTCError { + RTCError { + exception: Dom::from_ref(&*DOMException::new( + global, + DOMErrorName::from(&message).unwrap(), + )), + error_detail: init.errorDetail, + sdp_line_number: init.sdpLineNumber, + http_request_status_code: init.httpRequestStatusCode, + sctp_cause_code: init.sctpCauseCode, + received_alert: init.receivedAlert, + sent_alert: init.sentAlert, + } + } + + pub fn new(global: &GlobalScope, init: &RTCErrorInit, message: DOMString) -> DomRoot<RTCError> { + reflect_dom_object( + Box::new(RTCError::new_inherited(global, init, message)), + global, + ) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + init: &RTCErrorInit, + message: DOMString, + ) -> DomRoot<RTCError> { + RTCError::new(&window.global(), init, message) + } +} + +impl RTCErrorMethods for RTCError { + // https://www.w3.org/TR/webrtc/#dom-rtcerror-errordetail + fn ErrorDetail(&self) -> RTCErrorDetailType { + self.error_detail + } + + // https://www.w3.org/TR/webrtc/#dom-rtcerror-sdplinenumber + fn GetSdpLineNumber(&self) -> Option<i32> { + self.sdp_line_number + } + + // https://www.w3.org/TR/webrtc/#dom-rtcerror + fn GetHttpRequestStatusCode(&self) -> Option<i32> { + self.http_request_status_code + } + + // https://www.w3.org/TR/webrtc/#dom-rtcerror-sctpcausecode + fn GetSctpCauseCode(&self) -> Option<i32> { + self.sctp_cause_code + } + + // https://www.w3.org/TR/webrtc/#dom-rtcerror-receivedalert + fn GetReceivedAlert(&self) -> Option<u32> { + self.received_alert + } + + // https://www.w3.org/TR/webrtc/#dom-rtcerror-sentalert + fn GetSentAlert(&self) -> Option<u32> { + self.sent_alert + } +} diff --git a/components/script/dom/rtcerrorevent.rs b/components/script/dom/rtcerrorevent.rs new file mode 100644 index 00000000000..2d40844423a --- /dev/null +++ b/components/script/dom/rtcerrorevent.rs @@ -0,0 +1,74 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::RTCErrorEventBinding::RTCErrorEventInit; +use crate::dom::bindings::codegen::Bindings::RTCErrorEventBinding::RTCErrorEventMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::rtcerror::RTCError; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct RTCErrorEvent { + event: Event, + error: Dom<RTCError>, +} + +impl RTCErrorEvent { + fn new_inherited(error: &RTCError) -> RTCErrorEvent { + RTCErrorEvent { + event: Event::new_inherited(), + error: Dom::from_ref(error), + } + } + + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + error: &RTCError, + ) -> DomRoot<RTCErrorEvent> { + let event = reflect_dom_object(Box::new(RTCErrorEvent::new_inherited(&error)), global); + { + let event = event.upcast::<Event>(); + event.init_event(type_, bubbles, cancelable); + } + event + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &RTCErrorEventInit, + ) -> DomRoot<RTCErrorEvent> { + RTCErrorEvent::new( + &window.global(), + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + &init.error, + ) + } +} + +impl RTCErrorEventMethods for RTCErrorEvent { + // https://www.w3.org/TR/webrtc/#dom-rtcerrorevent-error + fn Error(&self) -> DomRoot<RTCError> { + DomRoot::from_ref(&*self.error) + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/rtcicecandidate.rs b/components/script/dom/rtcicecandidate.rs new file mode 100644 index 00000000000..371f3d7693a --- /dev/null +++ b/components/script/dom/rtcicecandidate.rs @@ -0,0 +1,109 @@ +/* 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 crate::dom::bindings::codegen::Bindings::RTCIceCandidateBinding::RTCIceCandidateInit; +use crate::dom::bindings::codegen::Bindings::RTCIceCandidateBinding::RTCIceCandidateMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::{DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct RTCIceCandidate { + reflector: Reflector, + candidate: DOMString, + sdp_m_id: Option<DOMString>, + sdp_m_line_index: Option<u16>, + username_fragment: Option<DOMString>, +} + +impl RTCIceCandidate { + pub fn new_inherited( + candidate: DOMString, + sdp_m_id: Option<DOMString>, + sdp_m_line_index: Option<u16>, + username_fragment: Option<DOMString>, + ) -> RTCIceCandidate { + RTCIceCandidate { + reflector: Reflector::new(), + candidate, + sdp_m_id, + sdp_m_line_index, + username_fragment, + } + } + + pub fn new( + global: &GlobalScope, + candidate: DOMString, + sdp_m_id: Option<DOMString>, + sdp_m_line_index: Option<u16>, + username_fragment: Option<DOMString>, + ) -> DomRoot<RTCIceCandidate> { + reflect_dom_object( + Box::new(RTCIceCandidate::new_inherited( + candidate, + sdp_m_id, + sdp_m_line_index, + username_fragment, + )), + global, + ) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + config: &RTCIceCandidateInit, + ) -> Fallible<DomRoot<RTCIceCandidate>> { + if config.sdpMid.is_none() && config.sdpMLineIndex.is_none() { + return Err(Error::Type(format!( + "one of sdpMid and sdpMLineIndex must be set" + ))); + } + Ok(RTCIceCandidate::new( + &window.global(), + config.candidate.clone(), + config.sdpMid.clone(), + config.sdpMLineIndex, + config.usernameFragment.clone(), + )) + } +} + +impl RTCIceCandidateMethods for RTCIceCandidate { + /// https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-candidate + fn Candidate(&self) -> DOMString { + self.candidate.clone() + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-sdpmid + fn GetSdpMid(&self) -> Option<DOMString> { + self.sdp_m_id.clone() + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-sdpmlineindex + fn GetSdpMLineIndex(&self) -> Option<u16> { + self.sdp_m_line_index.clone() + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-usernamefragment + fn GetUsernameFragment(&self) -> Option<DOMString> { + self.username_fragment.clone() + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-tojson + fn ToJSON(&self) -> RTCIceCandidateInit { + RTCIceCandidateInit { + candidate: self.candidate.clone(), + sdpMid: self.sdp_m_id.clone(), + sdpMLineIndex: self.sdp_m_line_index.clone(), + usernameFragment: self.username_fragment.clone(), + } + } +} diff --git a/components/script/dom/rtcpeerconnection.rs b/components/script/dom/rtcpeerconnection.rs new file mode 100644 index 00000000000..9fd651e5a16 --- /dev/null +++ b/components/script/dom/rtcpeerconnection.rs @@ -0,0 +1,824 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::RTCDataChannelBinding::RTCDataChannelInit; +use crate::dom::bindings::codegen::Bindings::RTCIceCandidateBinding::RTCIceCandidateInit; +use crate::dom::bindings::codegen::Bindings::RTCPeerConnectionBinding::RTCPeerConnectionMethods; +use crate::dom::bindings::codegen::Bindings::RTCPeerConnectionBinding::{ + RTCAnswerOptions, RTCBundlePolicy, RTCConfiguration, RTCIceConnectionState, + RTCIceGatheringState, RTCOfferOptions, RTCRtpTransceiverInit, RTCSignalingState, +}; +use crate::dom::bindings::codegen::Bindings::RTCSessionDescriptionBinding::{ + RTCSdpType, RTCSessionDescriptionInit, +}; +use crate::dom::bindings::codegen::UnionTypes::{MediaStreamTrackOrString, StringOrStringSequence}; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::USVString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::mediastream::MediaStream; +use crate::dom::mediastreamtrack::MediaStreamTrack; +use crate::dom::promise::Promise; +use crate::dom::rtcdatachannel::RTCDataChannel; +use crate::dom::rtcdatachannelevent::RTCDataChannelEvent; +use crate::dom::rtcicecandidate::RTCIceCandidate; +use crate::dom::rtcpeerconnectioniceevent::RTCPeerConnectionIceEvent; +use crate::dom::rtcrtptransceiver::RTCRtpTransceiver; +use crate::dom::rtcsessiondescription::RTCSessionDescription; +use crate::dom::rtctrackevent::RTCTrackEvent; +use crate::dom::window::Window; +use crate::realms::{enter_realm, InRealm}; +use crate::task::TaskCanceller; +use crate::task_source::networking::NetworkingTaskSource; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; + +use servo_media::streams::registry::MediaStreamId; +use servo_media::streams::MediaStreamType; +use servo_media::webrtc::{ + BundlePolicy, DataChannelEvent, DataChannelId, DataChannelState, GatheringState, IceCandidate, + IceConnectionState, SdpType, SessionDescription, SignalingState, WebRtcController, + WebRtcSignaller, +}; +use servo_media::ServoMedia; + +use std::cell::Cell; +use std::collections::HashMap; +use std::rc::Rc; + +#[dom_struct] +pub struct RTCPeerConnection { + eventtarget: EventTarget, + #[ignore_malloc_size_of = "defined in servo-media"] + controller: DomRefCell<Option<WebRtcController>>, + closed: Cell<bool>, + // Helps track state changes between the time createOffer/createAnswer + // is called and resolved + offer_answer_generation: Cell<u32>, + #[ignore_malloc_size_of = "promises are hard"] + offer_promises: DomRefCell<Vec<Rc<Promise>>>, + #[ignore_malloc_size_of = "promises are hard"] + answer_promises: DomRefCell<Vec<Rc<Promise>>>, + local_description: MutNullableDom<RTCSessionDescription>, + remote_description: MutNullableDom<RTCSessionDescription>, + gathering_state: Cell<RTCIceGatheringState>, + ice_connection_state: Cell<RTCIceConnectionState>, + signaling_state: Cell<RTCSignalingState>, + #[ignore_malloc_size_of = "defined in servo-media"] + data_channels: DomRefCell<HashMap<DataChannelId, Dom<RTCDataChannel>>>, +} + +struct RTCSignaller { + trusted: Trusted<RTCPeerConnection>, + task_source: NetworkingTaskSource, + canceller: TaskCanceller, +} + +impl WebRtcSignaller for RTCSignaller { + fn on_ice_candidate(&self, _: &WebRtcController, candidate: IceCandidate) { + let this = self.trusted.clone(); + let _ = self.task_source.queue_with_canceller( + task!(on_ice_candidate: move || { + let this = this.root(); + this.on_ice_candidate(candidate); + }), + &self.canceller, + ); + } + + fn on_negotiation_needed(&self, _: &WebRtcController) { + let this = self.trusted.clone(); + let _ = self.task_source.queue_with_canceller( + task!(on_negotiation_needed: move || { + let this = this.root(); + this.on_negotiation_needed(); + }), + &self.canceller, + ); + } + + fn update_gathering_state(&self, state: GatheringState) { + let this = self.trusted.clone(); + let _ = self.task_source.queue_with_canceller( + task!(update_gathering_state: move || { + let this = this.root(); + this.update_gathering_state(state); + }), + &self.canceller, + ); + } + + fn update_ice_connection_state(&self, state: IceConnectionState) { + let this = self.trusted.clone(); + let _ = self.task_source.queue_with_canceller( + task!(update_ice_connection_state: move || { + let this = this.root(); + this.update_ice_connection_state(state); + }), + &self.canceller, + ); + } + + fn update_signaling_state(&self, state: SignalingState) { + let this = self.trusted.clone(); + let _ = self.task_source.queue_with_canceller( + task!(update_signaling_state: move || { + let this = this.root(); + this.update_signaling_state(state); + }), + &self.canceller, + ); + } + + fn on_add_stream(&self, id: &MediaStreamId, ty: MediaStreamType) { + let this = self.trusted.clone(); + let id = *id; + let _ = self.task_source.queue_with_canceller( + task!(on_add_stream: move || { + let this = this.root(); + this.on_add_stream(id, ty); + }), + &self.canceller, + ); + } + + fn on_data_channel_event( + &self, + channel: DataChannelId, + event: DataChannelEvent, + _: &WebRtcController, + ) { + // XXX(ferjm) get label and options from channel properties. + let this = self.trusted.clone(); + let _ = self.task_source.queue_with_canceller( + task!(on_data_channel_event: move || { + let this = this.root(); + let global = this.global(); + let _ac = enter_realm(&*global); + this.on_data_channel_event(channel, event); + }), + &self.canceller, + ); + } + + fn close(&self) { + // do nothing + } +} + +impl RTCPeerConnection { + pub fn new_inherited() -> RTCPeerConnection { + RTCPeerConnection { + eventtarget: EventTarget::new_inherited(), + controller: DomRefCell::new(None), + closed: Cell::new(false), + offer_answer_generation: Cell::new(0), + offer_promises: DomRefCell::new(vec![]), + answer_promises: DomRefCell::new(vec![]), + local_description: Default::default(), + remote_description: Default::default(), + gathering_state: Cell::new(RTCIceGatheringState::New), + ice_connection_state: Cell::new(RTCIceConnectionState::New), + signaling_state: Cell::new(RTCSignalingState::Stable), + data_channels: DomRefCell::new(HashMap::new()), + } + } + + pub fn new(global: &GlobalScope, config: &RTCConfiguration) -> DomRoot<RTCPeerConnection> { + let this = reflect_dom_object(Box::new(RTCPeerConnection::new_inherited()), global); + let signaller = this.make_signaller(); + *this.controller.borrow_mut() = Some(ServoMedia::get().unwrap().create_webrtc(signaller)); + if let Some(ref servers) = config.iceServers { + if let Some(ref server) = servers.get(0) { + let server = match server.urls { + StringOrStringSequence::String(ref s) => Some(s.clone()), + StringOrStringSequence::StringSequence(ref s) => s.get(0).cloned(), + }; + if let Some(server) = server { + let policy = match config.bundlePolicy { + RTCBundlePolicy::Balanced => BundlePolicy::Balanced, + RTCBundlePolicy::Max_compat => BundlePolicy::MaxCompat, + RTCBundlePolicy::Max_bundle => BundlePolicy::MaxBundle, + }; + this.controller + .borrow() + .as_ref() + .unwrap() + .configure(server.to_string(), policy); + } + } + } + this + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + config: &RTCConfiguration, + ) -> Fallible<DomRoot<RTCPeerConnection>> { + Ok(RTCPeerConnection::new(&window.global(), config)) + } + + pub fn get_webrtc_controller(&self) -> &DomRefCell<Option<WebRtcController>> { + &self.controller + } + + fn make_signaller(&self) -> Box<dyn WebRtcSignaller> { + let trusted = Trusted::new(self); + let (task_source, canceller) = self + .global() + .as_window() + .task_manager() + .networking_task_source_with_canceller(); + Box::new(RTCSignaller { + trusted, + task_source, + canceller, + }) + } + + fn on_ice_candidate(&self, candidate: IceCandidate) { + if self.closed.get() { + return; + } + let candidate = RTCIceCandidate::new( + &self.global(), + candidate.candidate.into(), + None, + Some(candidate.sdp_mline_index as u16), + None, + ); + let event = RTCPeerConnectionIceEvent::new( + &self.global(), + atom!("icecandidate"), + Some(&candidate), + None, + true, + ); + event.upcast::<Event>().fire(self.upcast()); + } + + fn on_negotiation_needed(&self) { + if self.closed.get() { + return; + } + let event = Event::new( + &self.global(), + atom!("negotiationneeded"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); + event.upcast::<Event>().fire(self.upcast()); + } + + fn on_add_stream(&self, id: MediaStreamId, ty: MediaStreamType) { + if self.closed.get() { + return; + } + let track = MediaStreamTrack::new(&self.global(), id, ty); + let event = RTCTrackEvent::new(&self.global(), atom!("track"), false, false, &track); + event.upcast::<Event>().fire(self.upcast()); + } + + fn on_data_channel_event(&self, channel_id: DataChannelId, event: DataChannelEvent) { + if self.closed.get() { + return; + } + + match event { + DataChannelEvent::NewChannel => { + let channel = RTCDataChannel::new( + &self.global(), + &self, + USVString::from("".to_owned()), + &RTCDataChannelInit::empty(), + Some(channel_id), + ); + + let event = RTCDataChannelEvent::new( + &self.global(), + atom!("datachannel"), + false, + false, + &channel, + ); + event.upcast::<Event>().fire(self.upcast()); + }, + _ => { + let channel = if let Some(channel) = self.data_channels.borrow().get(&channel_id) { + DomRoot::from_ref(&**channel) + } else { + warn!( + "Got an event for an unregistered data channel {:?}", + channel_id + ); + return; + }; + + match event { + DataChannelEvent::Open => channel.on_open(), + DataChannelEvent::Close => channel.on_close(), + DataChannelEvent::Error(error) => channel.on_error(error), + DataChannelEvent::OnMessage(message) => channel.on_message(message), + DataChannelEvent::StateChange(state) => channel.on_state_change(state), + DataChannelEvent::NewChannel => unreachable!(), + } + }, + }; + } + + pub fn register_data_channel(&self, id: DataChannelId, channel: &RTCDataChannel) { + if self + .data_channels + .borrow_mut() + .insert(id, Dom::from_ref(channel)) + .is_some() + { + warn!("Data channel already registered {:?}", id); + } + } + + pub fn unregister_data_channel(&self, id: &DataChannelId) { + self.data_channels.borrow_mut().remove(&id); + } + + /// https://www.w3.org/TR/webrtc/#update-ice-gathering-state + fn update_gathering_state(&self, state: GatheringState) { + // step 1 + if self.closed.get() { + return; + } + + // step 2 (state derivation already done by gstreamer) + let state: RTCIceGatheringState = state.into(); + + // step 3 + if state == self.gathering_state.get() { + return; + } + + // step 4 + self.gathering_state.set(state); + + // step 5 + let event = Event::new( + &self.global(), + atom!("icegatheringstatechange"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); + event.upcast::<Event>().fire(self.upcast()); + + // step 6 + if state == RTCIceGatheringState::Complete { + let event = RTCPeerConnectionIceEvent::new( + &self.global(), + atom!("icecandidate"), + None, + None, + true, + ); + event.upcast::<Event>().fire(self.upcast()); + } + } + + /// https://www.w3.org/TR/webrtc/#update-ice-connection-state + fn update_ice_connection_state(&self, state: IceConnectionState) { + // step 1 + if self.closed.get() { + return; + } + + // step 2 (state derivation already done by gstreamer) + let state: RTCIceConnectionState = state.into(); + + // step 3 + if state == self.ice_connection_state.get() { + return; + } + + // step 4 + self.ice_connection_state.set(state); + + // step 5 + let event = Event::new( + &self.global(), + atom!("iceconnectionstatechange"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); + event.upcast::<Event>().fire(self.upcast()); + } + + fn update_signaling_state(&self, state: SignalingState) { + if self.closed.get() { + return; + } + + let state: RTCSignalingState = state.into(); + + if state == self.signaling_state.get() { + return; + } + + self.signaling_state.set(state); + + let event = Event::new( + &self.global(), + atom!("signalingstatechange"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); + event.upcast::<Event>().fire(self.upcast()); + } + + fn create_offer(&self) { + let generation = self.offer_answer_generation.get(); + let (task_source, canceller) = self + .global() + .as_window() + .task_manager() + .networking_task_source_with_canceller(); + let this = Trusted::new(self); + self.controller.borrow_mut().as_ref().unwrap().create_offer( + (move |desc: SessionDescription| { + let _ = task_source.queue_with_canceller( + task!(offer_created: move || { + let this = this.root(); + if this.offer_answer_generation.get() != generation { + // the state has changed since we last created the offer, + // create a fresh one + this.create_offer(); + } else { + let init: RTCSessionDescriptionInit = desc.into(); + for promise in this.offer_promises.borrow_mut().drain(..) { + promise.resolve_native(&init); + } + } + }), + &canceller, + ); + }) + .into(), + ); + } + + fn create_answer(&self) { + let generation = self.offer_answer_generation.get(); + let (task_source, canceller) = self + .global() + .as_window() + .task_manager() + .networking_task_source_with_canceller(); + let this = Trusted::new(self); + self.controller + .borrow_mut() + .as_ref() + .unwrap() + .create_answer( + (move |desc: SessionDescription| { + let _ = task_source.queue_with_canceller( + task!(answer_created: move || { + let this = this.root(); + if this.offer_answer_generation.get() != generation { + // the state has changed since we last created the offer, + // create a fresh one + this.create_answer(); + } else { + let init: RTCSessionDescriptionInit = desc.into(); + for promise in this.answer_promises.borrow_mut().drain(..) { + promise.resolve_native(&init); + } + } + }), + &canceller, + ); + }) + .into(), + ); + } +} + +impl RTCPeerConnectionMethods for RTCPeerConnection { + // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-icecandidate + event_handler!(icecandidate, GetOnicecandidate, SetOnicecandidate); + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-ontrack + event_handler!(track, GetOntrack, SetOntrack); + + // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-iceconnectionstatechange + event_handler!( + iceconnectionstatechange, + GetOniceconnectionstatechange, + SetOniceconnectionstatechange + ); + + // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-icegatheringstatechange + event_handler!( + icegatheringstatechange, + GetOnicegatheringstatechange, + SetOnicegatheringstatechange + ); + + // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-onnegotiationneeded + event_handler!( + negotiationneeded, + GetOnnegotiationneeded, + SetOnnegotiationneeded + ); + + // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-signalingstatechange + event_handler!( + signalingstatechange, + GetOnsignalingstatechange, + SetOnsignalingstatechange + ); + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-ondatachannel + event_handler!(datachannel, GetOndatachannel, SetOndatachannel); + + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addicecandidate + fn AddIceCandidate(&self, candidate: &RTCIceCandidateInit, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); + if candidate.sdpMid.is_none() && candidate.sdpMLineIndex.is_none() { + p.reject_error(Error::Type(format!( + "one of sdpMid and sdpMLineIndex must be set" + ))); + return p; + } + + // XXXManishearth add support for sdpMid + if candidate.sdpMLineIndex.is_none() { + p.reject_error(Error::Type(format!( + "servo only supports sdpMLineIndex right now" + ))); + return p; + } + + // XXXManishearth this should be enqueued + // https://w3c.github.io/webrtc-pc/#enqueue-an-operation + + self.controller + .borrow_mut() + .as_ref() + .unwrap() + .add_ice_candidate(IceCandidate { + sdp_mline_index: candidate.sdpMLineIndex.unwrap() as u32, + candidate: candidate.candidate.to_string(), + }); + + // XXXManishearth add_ice_candidate should have a callback + p.resolve_native(&()); + p + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer + fn CreateOffer(&self, _options: &RTCOfferOptions, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); + if self.closed.get() { + p.reject_error(Error::InvalidState); + return p; + } + self.offer_promises.borrow_mut().push(p.clone()); + self.create_offer(); + p + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer + fn CreateAnswer(&self, _options: &RTCAnswerOptions, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); + if self.closed.get() { + p.reject_error(Error::InvalidState); + return p; + } + self.answer_promises.borrow_mut().push(p.clone()); + self.create_answer(); + p + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-localdescription + fn GetLocalDescription(&self) -> Option<DomRoot<RTCSessionDescription>> { + self.local_description.get() + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-remotedescription + fn GetRemoteDescription(&self) -> Option<DomRoot<RTCSessionDescription>> { + self.remote_description.get() + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-setlocaldescription + fn SetLocalDescription(&self, desc: &RTCSessionDescriptionInit, comp: InRealm) -> Rc<Promise> { + // XXXManishearth validate the current state + let p = Promise::new_in_current_realm(&self.global(), comp); + let this = Trusted::new(self); + let desc: SessionDescription = desc.into(); + let trusted_promise = TrustedPromise::new(p.clone()); + let (task_source, canceller) = self + .global() + .as_window() + .task_manager() + .networking_task_source_with_canceller(); + self.controller + .borrow_mut() + .as_ref() + .unwrap() + .set_local_description(desc.clone(), (move || { + let _ = task_source.queue_with_canceller( + task!(local_description_set: move || { + // XXXManishearth spec actually asks for an intricate + // dance between pending/current local/remote descriptions + let this = this.root(); + let desc = desc.into(); + let desc = RTCSessionDescription::Constructor(&this.global().as_window(), &desc).unwrap(); + this.local_description.set(Some(&desc)); + trusted_promise.root().resolve_native(&()) + }), + &canceller, + ); + }).into()); + p + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-setremotedescription + fn SetRemoteDescription(&self, desc: &RTCSessionDescriptionInit, comp: InRealm) -> Rc<Promise> { + // XXXManishearth validate the current state + let p = Promise::new_in_current_realm(&self.global(), comp); + let this = Trusted::new(self); + let desc: SessionDescription = desc.into(); + let trusted_promise = TrustedPromise::new(p.clone()); + let (task_source, canceller) = self + .global() + .as_window() + .task_manager() + .networking_task_source_with_canceller(); + self.controller + .borrow_mut() + .as_ref() + .unwrap() + .set_remote_description(desc.clone(), (move || { + let _ = task_source.queue_with_canceller( + task!(remote_description_set: move || { + // XXXManishearth spec actually asks for an intricate + // dance between pending/current local/remote descriptions + let this = this.root(); + let desc = desc.into(); + let desc = RTCSessionDescription::Constructor(&this.global().as_window(), &desc).unwrap(); + this.remote_description.set(Some(&desc)); + trusted_promise.root().resolve_native(&()) + }), + &canceller, + ); + }).into()); + p + } + + // https://w3c.github.io/webrtc-pc/#legacy-interface-extensions + fn AddStream(&self, stream: &MediaStream) { + for track in &*stream.get_tracks() { + self.controller + .borrow() + .as_ref() + .unwrap() + .add_stream(&track.id()); + } + } + + /// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-icegatheringstate + fn IceGatheringState(&self) -> RTCIceGatheringState { + self.gathering_state.get() + } + + /// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-iceconnectionstate + fn IceConnectionState(&self) -> RTCIceConnectionState { + self.ice_connection_state.get() + } + + /// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-signalingstate + fn SignalingState(&self) -> RTCSignalingState { + self.signaling_state.get() + } + + /// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close + fn Close(&self) { + // Step 1 + if self.closed.get() { + return; + } + // Step 2 + self.closed.set(true); + + // Step 4 + self.signaling_state.set(RTCSignalingState::Closed); + + // Step 5 handled by backend + self.controller.borrow_mut().as_ref().unwrap().quit(); + + // Step 6 + for (_, val) in self.data_channels.borrow().iter() { + val.on_state_change(DataChannelState::Closed); + } + + // Step 7-10 + // (no current support for transports, etc) + + // Step 11 + self.ice_connection_state.set(RTCIceConnectionState::Closed); + + // Step 11 + // (no current support for connection state) + } + + /// https://www.w3.org/TR/webrtc/#dom-peerconnection-createdatachannel + fn CreateDataChannel( + &self, + label: USVString, + init: &RTCDataChannelInit, + ) -> DomRoot<RTCDataChannel> { + RTCDataChannel::new(&self.global(), &self, label, init, None) + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver + fn AddTransceiver( + &self, + _track_or_kind: MediaStreamTrackOrString, + init: &RTCRtpTransceiverInit, + ) -> DomRoot<RTCRtpTransceiver> { + RTCRtpTransceiver::new(&self.global(), init.direction) + } +} + +impl From<SessionDescription> for RTCSessionDescriptionInit { + fn from(desc: SessionDescription) -> Self { + let type_ = match desc.type_ { + SdpType::Answer => RTCSdpType::Answer, + SdpType::Offer => RTCSdpType::Offer, + SdpType::Pranswer => RTCSdpType::Pranswer, + SdpType::Rollback => RTCSdpType::Rollback, + }; + RTCSessionDescriptionInit { + type_, + sdp: desc.sdp.into(), + } + } +} + +impl<'a> From<&'a RTCSessionDescriptionInit> for SessionDescription { + fn from(desc: &'a RTCSessionDescriptionInit) -> Self { + let type_ = match desc.type_ { + RTCSdpType::Answer => SdpType::Answer, + RTCSdpType::Offer => SdpType::Offer, + RTCSdpType::Pranswer => SdpType::Pranswer, + RTCSdpType::Rollback => SdpType::Rollback, + }; + SessionDescription { + type_, + sdp: desc.sdp.to_string(), + } + } +} + +impl From<GatheringState> for RTCIceGatheringState { + fn from(state: GatheringState) -> Self { + match state { + GatheringState::New => RTCIceGatheringState::New, + GatheringState::Gathering => RTCIceGatheringState::Gathering, + GatheringState::Complete => RTCIceGatheringState::Complete, + } + } +} + +impl From<IceConnectionState> for RTCIceConnectionState { + fn from(state: IceConnectionState) -> Self { + match state { + IceConnectionState::New => RTCIceConnectionState::New, + IceConnectionState::Checking => RTCIceConnectionState::Checking, + IceConnectionState::Connected => RTCIceConnectionState::Connected, + IceConnectionState::Completed => RTCIceConnectionState::Completed, + IceConnectionState::Disconnected => RTCIceConnectionState::Disconnected, + IceConnectionState::Failed => RTCIceConnectionState::Failed, + IceConnectionState::Closed => RTCIceConnectionState::Closed, + } + } +} + +impl From<SignalingState> for RTCSignalingState { + fn from(state: SignalingState) -> Self { + match state { + SignalingState::Stable => RTCSignalingState::Stable, + SignalingState::HaveLocalOffer => RTCSignalingState::Have_local_offer, + SignalingState::HaveRemoteOffer => RTCSignalingState::Have_remote_offer, + SignalingState::HaveLocalPranswer => RTCSignalingState::Have_local_pranswer, + SignalingState::HaveRemotePranswer => RTCSignalingState::Have_remote_pranswer, + SignalingState::Closed => RTCSignalingState::Closed, + } + } +} diff --git a/components/script/dom/rtcpeerconnectioniceevent.rs b/components/script/dom/rtcpeerconnectioniceevent.rs new file mode 100644 index 00000000000..e88222e34ad --- /dev/null +++ b/components/script/dom/rtcpeerconnectioniceevent.rs @@ -0,0 +1,91 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::RTCPeerConnectionIceEventBinding::RTCPeerConnectionIceEventInit; +use crate::dom::bindings::codegen::Bindings::RTCPeerConnectionIceEventBinding::RTCPeerConnectionIceEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::rtcicecandidate::RTCIceCandidate; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct RTCPeerConnectionIceEvent { + event: Event, + candidate: Option<Dom<RTCIceCandidate>>, + url: Option<DOMString>, +} + +impl RTCPeerConnectionIceEvent { + pub fn new_inherited( + candidate: Option<&RTCIceCandidate>, + url: Option<DOMString>, + ) -> RTCPeerConnectionIceEvent { + RTCPeerConnectionIceEvent { + event: Event::new_inherited(), + candidate: candidate.map(Dom::from_ref), + url, + } + } + + pub fn new( + global: &GlobalScope, + ty: Atom, + candidate: Option<&RTCIceCandidate>, + url: Option<DOMString>, + trusted: bool, + ) -> DomRoot<RTCPeerConnectionIceEvent> { + let e = reflect_dom_object( + Box::new(RTCPeerConnectionIceEvent::new_inherited(candidate, url)), + global, + ); + let evt = e.upcast::<Event>(); + evt.init_event(ty, false, false); // XXXManishearth bubbles/cancelable? + evt.set_trusted(trusted); + e + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + ty: DOMString, + init: &RTCPeerConnectionIceEventInit, + ) -> Fallible<DomRoot<RTCPeerConnectionIceEvent>> { + Ok(RTCPeerConnectionIceEvent::new( + &window.global(), + ty.into(), + init.candidate + .as_ref() + .and_then(|x| x.as_ref()) + .map(|x| &**x), + init.url.as_ref().and_then(|x| x.clone()), + false, + )) + } +} + +impl RTCPeerConnectionIceEventMethods for RTCPeerConnectionIceEvent { + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-candidate + fn GetCandidate(&self) -> Option<DomRoot<RTCIceCandidate>> { + self.candidate.as_ref().map(|x| DomRoot::from_ref(&**x)) + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-url + fn GetUrl(&self) -> Option<DOMString> { + self.url.clone() + } + + /// https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/rtcrtpsender.rs b/components/script/dom/rtcrtpsender.rs new file mode 100644 index 00000000000..10be5a6d79a --- /dev/null +++ b/components/script/dom/rtcrtpsender.rs @@ -0,0 +1,57 @@ +/* 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 crate::dom::bindings::codegen::Bindings::RTCRtpSenderBinding::RTCRtpSenderMethods; +use crate::dom::bindings::codegen::Bindings::RTCRtpSenderBinding::{ + RTCRtcpParameters, RTCRtpParameters, RTCRtpSendParameters, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use dom_struct::dom_struct; +use std::rc::Rc; + +#[dom_struct] +pub struct RTCRtpSender { + reflector_: Reflector, +} + +impl RTCRtpSender { + fn new_inherited() -> Self { + Self { + reflector_: Reflector::new(), + } + } + + pub(crate) fn new(global: &GlobalScope) -> DomRoot<Self> { + reflect_dom_object(Box::new(Self::new_inherited()), global) + } +} + +impl RTCRtpSenderMethods for RTCRtpSender { + // https://w3c.github.io/webrtc-pc/#dom-rtcrtpsender-getparameters + fn GetParameters(&self) -> RTCRtpSendParameters { + RTCRtpSendParameters { + parent: RTCRtpParameters { + headerExtensions: vec![], + rtcp: RTCRtcpParameters { + cname: None, + reducedSize: None, + }, + codecs: vec![], + }, + transactionId: DOMString::new(), + encodings: vec![], + } + } + + // https://w3c.github.io/webrtc-pc/#dom-rtcrtpsender-setparameters + fn SetParameters(&self, _parameters: &RTCRtpSendParameters) -> Rc<Promise> { + let promise = Promise::new(&self.global()); + promise.resolve_native(&()); + promise + } +} diff --git a/components/script/dom/rtcrtptransceiver.rs b/components/script/dom/rtcrtptransceiver.rs new file mode 100644 index 00000000000..5c041bb1ae6 --- /dev/null +++ b/components/script/dom/rtcrtptransceiver.rs @@ -0,0 +1,55 @@ +/* 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 crate::dom::bindings::codegen::Bindings::RTCRtpTransceiverBinding::{ + RTCRtpTransceiverDirection, RTCRtpTransceiverMethods, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::rtcrtpsender::RTCRtpSender; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct RTCRtpTransceiver { + reflector_: Reflector, + sender: Dom<RTCRtpSender>, + direction: Cell<RTCRtpTransceiverDirection>, +} + +impl RTCRtpTransceiver { + fn new_inherited(global: &GlobalScope, direction: RTCRtpTransceiverDirection) -> Self { + let sender = RTCRtpSender::new(global); + Self { + reflector_: Reflector::new(), + direction: Cell::new(direction), + sender: Dom::from_ref(&*sender), + } + } + + pub(crate) fn new( + global: &GlobalScope, + direction: RTCRtpTransceiverDirection, + ) -> DomRoot<Self> { + reflect_dom_object(Box::new(Self::new_inherited(global, direction)), global) + } +} + +impl RTCRtpTransceiverMethods for RTCRtpTransceiver { + /// https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction + fn Direction(&self) -> RTCRtpTransceiverDirection { + self.direction.get() + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction + fn SetDirection(&self, direction: RTCRtpTransceiverDirection) { + self.direction.set(direction); + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-sender + fn Sender(&self) -> DomRoot<RTCRtpSender> { + DomRoot::from_ref(&*self.sender) + } +} diff --git a/components/script/dom/rtcsessiondescription.rs b/components/script/dom/rtcsessiondescription.rs new file mode 100644 index 00000000000..2ff7157e5d4 --- /dev/null +++ b/components/script/dom/rtcsessiondescription.rs @@ -0,0 +1,68 @@ +/* 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 crate::dom::bindings::codegen::Bindings::RTCSessionDescriptionBinding::RTCSessionDescriptionMethods; +use crate::dom::bindings::codegen::Bindings::RTCSessionDescriptionBinding::{ + RTCSdpType, RTCSessionDescriptionInit, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::{DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct RTCSessionDescription { + reflector: Reflector, + ty: RTCSdpType, + sdp: DOMString, +} + +impl RTCSessionDescription { + pub fn new_inherited(ty: RTCSdpType, sdp: DOMString) -> RTCSessionDescription { + RTCSessionDescription { + reflector: Reflector::new(), + ty, + sdp, + } + } + + pub fn new( + global: &GlobalScope, + ty: RTCSdpType, + sdp: DOMString, + ) -> DomRoot<RTCSessionDescription> { + reflect_dom_object( + Box::new(RTCSessionDescription::new_inherited(ty, sdp)), + global, + ) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + config: &RTCSessionDescriptionInit, + ) -> Fallible<DomRoot<RTCSessionDescription>> { + Ok(RTCSessionDescription::new( + &window.global(), + config.type_, + config.sdp.clone(), + )) + } +} + +impl RTCSessionDescriptionMethods for RTCSessionDescription { + /// https://w3c.github.io/webrtc-pc/#dom-rtcsessiondescription-type + fn Type(&self) -> RTCSdpType { + self.ty + } + + /// https://w3c.github.io/webrtc-pc/#dom-rtcsessiondescription-sdp + fn Sdp(&self) -> DOMString { + self.sdp.clone() + } +} diff --git a/components/script/dom/rtctrackevent.rs b/components/script/dom/rtctrackevent.rs new file mode 100644 index 00000000000..0cf8605170b --- /dev/null +++ b/components/script/dom/rtctrackevent.rs @@ -0,0 +1,75 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::RTCTrackEventBinding::{self, RTCTrackEventMethods}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::mediastreamtrack::MediaStreamTrack; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct RTCTrackEvent { + event: Event, + track: Dom<MediaStreamTrack>, +} + +impl RTCTrackEvent { + #[allow(unrooted_must_root)] + fn new_inherited(track: &MediaStreamTrack) -> RTCTrackEvent { + RTCTrackEvent { + event: Event::new_inherited(), + track: Dom::from_ref(track), + } + } + + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + track: &MediaStreamTrack, + ) -> DomRoot<RTCTrackEvent> { + let trackevent = reflect_dom_object(Box::new(RTCTrackEvent::new_inherited(&track)), global); + { + let event = trackevent.upcast::<Event>(); + event.init_event(type_, bubbles, cancelable); + } + trackevent + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &RTCTrackEventBinding::RTCTrackEventInit, + ) -> Fallible<DomRoot<RTCTrackEvent>> { + Ok(RTCTrackEvent::new( + &window.global(), + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + &init.track, + )) + } +} + +impl RTCTrackEventMethods for RTCTrackEvent { + // https://w3c.github.io/webrtc-pc/#dom-rtctrackevent-track + fn Track(&self) -> DomRoot<MediaStreamTrack> { + DomRoot::from_ref(&*self.track) + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/screen.rs b/components/script/dom/screen.rs index 80e5bf8d5c6..18e137346e5 100644 --- a/components/script/dom/screen.rs +++ b/components/script/dom/screen.rs @@ -1,34 +1,88 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::ScreenBinding; -use dom::bindings::codegen::Bindings::ScreenBinding::ScreenMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::ScreenBinding::ScreenMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use dom_struct::dom_struct; +use euclid::Size2D; +use profile_traits::ipc; +use script_traits::ScriptMsg; +use style_traits::CSSPixel; +use webrender_api::units::DeviceIntSize; #[dom_struct] pub struct Screen { reflector_: Reflector, + window: Dom<Window>, } impl Screen { - fn new_inherited() -> Screen { + fn new_inherited(window: &Window) -> Screen { Screen { reflector_: Reflector::new(), + window: Dom::from_ref(&window), } } - pub fn new(window: &Window) -> Root<Screen> { - reflect_dom_object(box Screen::new_inherited(), - window, - ScreenBinding::Wrap) + pub fn new(window: &Window) -> DomRoot<Screen> { + reflect_dom_object(Box::new(Screen::new_inherited(window)), window) + } + + fn screen_size(&self) -> Size2D<u32, CSSPixel> { + let (send, recv) = + ipc::channel::<DeviceIntSize>(self.global().time_profiler_chan().clone()).unwrap(); + self.window + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(ScriptMsg::GetScreenSize(send)) + .unwrap(); + let dpr = self.window.device_pixel_ratio(); + let screen = recv.recv().unwrap_or(Size2D::zero()); + (screen.to_f32() / dpr).to_u32() + } + + fn screen_avail_size(&self) -> Size2D<u32, CSSPixel> { + let (send, recv) = + ipc::channel::<DeviceIntSize>(self.global().time_profiler_chan().clone()).unwrap(); + self.window + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(ScriptMsg::GetScreenAvailSize(send)) + .unwrap(); + let dpr = self.window.device_pixel_ratio(); + let screen = recv.recv().unwrap_or(Size2D::zero()); + (screen.to_f32() / dpr).to_u32() } } impl ScreenMethods for Screen { + // https://drafts.csswg.org/cssom-view/#dom-screen-availwidth + fn AvailWidth(&self) -> Finite<f64> { + Finite::wrap(self.screen_avail_size().width as f64) + } + + // https://drafts.csswg.org/cssom-view/#dom-screen-availheight + fn AvailHeight(&self) -> Finite<f64> { + Finite::wrap(self.screen_avail_size().height as f64) + } + + // https://drafts.csswg.org/cssom-view/#dom-screen-width + fn Width(&self) -> Finite<f64> { + Finite::wrap(self.screen_size().width as f64) + } + + // https://drafts.csswg.org/cssom-view/#dom-screen-height + fn Height(&self) -> Finite<f64> { + Finite::wrap(self.screen_size().height as f64) + } + // https://drafts.csswg.org/cssom-view/#dom-screen-colordepth fn ColorDepth(&self) -> u32 { 24 diff --git a/components/script/dom/selection.rs b/components/script/dom/selection.rs new file mode 100644 index 00000000000..75348556ba0 --- /dev/null +++ b/components/script/dom/selection.rs @@ -0,0 +1,514 @@ +/* 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 crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; +use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods; +use crate::dom::bindings::codegen::Bindings::SelectionBinding::SelectionMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::eventtarget::EventTarget; +use crate::dom::node::{window_from_node, Node}; +use crate::dom::range::Range; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[derive(Clone, Copy, JSTraceable, MallocSizeOf)] +enum Direction { + Forwards, + Backwards, + Directionless, +} + +#[dom_struct] +pub struct Selection { + reflector_: Reflector, + document: Dom<Document>, + range: MutNullableDom<Range>, + direction: Cell<Direction>, + task_queued: Cell<bool>, +} + +impl Selection { + fn new_inherited(document: &Document) -> Selection { + Selection { + reflector_: Reflector::new(), + document: Dom::from_ref(document), + range: MutNullableDom::new(None), + direction: Cell::new(Direction::Directionless), + task_queued: Cell::new(false), + } + } + + pub fn new(document: &Document) -> DomRoot<Selection> { + reflect_dom_object( + Box::new(Selection::new_inherited(document)), + &*document.global(), + ) + } + + fn set_range(&self, range: &Range) { + // If we are setting to literally the same Range object + // (not just the same positions), then there's nothing changing + // and no task to queue. + if let Some(existing) = self.range.get() { + if &*existing == range { + return; + } + } + self.range.set(Some(range)); + range.associate_selection(self); + self.queue_selectionchange_task(); + } + + fn clear_range(&self) { + // If we already don't have a a Range object, then there's + // nothing changing and no task to queue. + if let Some(range) = self.range.get() { + range.disassociate_selection(self); + self.range.set(None); + self.queue_selectionchange_task(); + } + } + + pub fn queue_selectionchange_task(&self) { + if self.task_queued.get() { + // Spec doesn't specify not to queue multiple tasks, + // but it's much easier to code range operations if + // change notifications within a method are idempotent. + return; + } + let this = Trusted::new(self); + let window = window_from_node(&*self.document); + window + .task_manager() + .user_interaction_task_source() // w3c/selection-api#117 + .queue( + task!(selectionchange_task_steps: move || { + let this = this.root(); + this.task_queued.set(false); + this.document.upcast::<EventTarget>().fire_event(atom!("selectionchange")); + }), + window.upcast(), + ) + .expect("Couldn't queue selectionchange task!"); + self.task_queued.set(true); + } + + fn is_same_root(&self, node: &Node) -> bool { + &*node.GetRootNode(&GetRootNodeOptions::empty()) == self.document.upcast::<Node>() + } +} + +impl SelectionMethods for Selection { + // https://w3c.github.io/selection-api/#dom-selection-anchornode + fn GetAnchorNode(&self) -> Option<DomRoot<Node>> { + if let Some(range) = self.range.get() { + match self.direction.get() { + Direction::Forwards => Some(range.StartContainer()), + _ => Some(range.EndContainer()), + } + } else { + None + } + } + + // https://w3c.github.io/selection-api/#dom-selection-anchoroffset + fn AnchorOffset(&self) -> u32 { + if let Some(range) = self.range.get() { + match self.direction.get() { + Direction::Forwards => range.StartOffset(), + _ => range.EndOffset(), + } + } else { + 0 + } + } + + // https://w3c.github.io/selection-api/#dom-selection-focusnode + fn GetFocusNode(&self) -> Option<DomRoot<Node>> { + if let Some(range) = self.range.get() { + match self.direction.get() { + Direction::Forwards => Some(range.EndContainer()), + _ => Some(range.StartContainer()), + } + } else { + None + } + } + + // https://w3c.github.io/selection-api/#dom-selection-focusoffset + fn FocusOffset(&self) -> u32 { + if let Some(range) = self.range.get() { + match self.direction.get() { + Direction::Forwards => range.EndOffset(), + _ => range.StartOffset(), + } + } else { + 0 + } + } + + // https://w3c.github.io/selection-api/#dom-selection-iscollapsed + fn IsCollapsed(&self) -> bool { + if let Some(range) = self.range.get() { + range.Collapsed() + } else { + true + } + } + + // https://w3c.github.io/selection-api/#dom-selection-rangecount + fn RangeCount(&self) -> u32 { + if self.range.get().is_some() { + 1 + } else { + 0 + } + } + + // https://w3c.github.io/selection-api/#dom-selection-type + fn Type(&self) -> DOMString { + if let Some(range) = self.range.get() { + if range.Collapsed() { + DOMString::from("Caret") + } else { + DOMString::from("Range") + } + } else { + DOMString::from("None") + } + } + + // https://w3c.github.io/selection-api/#dom-selection-getrangeat + fn GetRangeAt(&self, index: u32) -> Fallible<DomRoot<Range>> { + if index != 0 { + Err(Error::IndexSize) + } else if let Some(range) = self.range.get() { + Ok(DomRoot::from_ref(&range)) + } else { + Err(Error::IndexSize) + } + } + + // https://w3c.github.io/selection-api/#dom-selection-addrange + fn AddRange(&self, range: &Range) { + // Step 1 + if !self.is_same_root(&*range.StartContainer()) { + return; + } + + // Step 2 + if self.RangeCount() != 0 { + return; + } + + // Step 3 + self.set_range(range); + // Are we supposed to set Direction here? w3c/selection-api#116 + self.direction.set(Direction::Forwards); + } + + // https://w3c.github.io/selection-api/#dom-selection-removerange + fn RemoveRange(&self, range: &Range) -> ErrorResult { + if let Some(own_range) = self.range.get() { + if &*own_range == range { + self.clear_range(); + return Ok(()); + } + } + Err(Error::NotFound) + } + + // https://w3c.github.io/selection-api/#dom-selection-removeallranges + fn RemoveAllRanges(&self) { + self.clear_range(); + } + + // https://w3c.github.io/selection-api/#dom-selection-empty + // TODO: When implementing actual selection UI, this may be the correct + // method to call as the abandon-selection action + fn Empty(&self) { + self.clear_range(); + } + + // https://w3c.github.io/selection-api/#dom-selection-collapse + fn Collapse(&self, node: Option<&Node>, offset: u32) -> ErrorResult { + if let Some(node) = node { + if node.is_doctype() { + // w3c/selection-api#118 + return Err(Error::InvalidNodeType); + } + if offset > node.len() { + // Step 2 + return Err(Error::IndexSize); + } + + if !self.is_same_root(node) { + // Step 3 + return Ok(()); + } + + // Steps 4-5 + let range = Range::new(&self.document, node, offset, node, offset); + + // Step 6 + self.set_range(&range); + // Are we supposed to set Direction here? w3c/selection-api#116 + // + self.direction.set(Direction::Forwards); + } else { + // Step 1 + self.clear_range(); + } + Ok(()) + } + + // https://w3c.github.io/selection-api/#dom-selection-setposition + // TODO: When implementing actual selection UI, this may be the correct + // method to call as the start-of-selection action, after a + // selectstart event has fired and not been cancelled. + fn SetPosition(&self, node: Option<&Node>, offset: u32) -> ErrorResult { + self.Collapse(node, offset) + } + + // https://w3c.github.io/selection-api/#dom-selection-collapsetostart + fn CollapseToStart(&self) -> ErrorResult { + if let Some(range) = self.range.get() { + self.Collapse(Some(&*range.StartContainer()), range.StartOffset()) + } else { + Err(Error::InvalidState) + } + } + + // https://w3c.github.io/selection-api/#dom-selection-collapsetoend + fn CollapseToEnd(&self) -> ErrorResult { + if let Some(range) = self.range.get() { + self.Collapse(Some(&*range.EndContainer()), range.EndOffset()) + } else { + Err(Error::InvalidState) + } + } + + // https://w3c.github.io/selection-api/#dom-selection-extend + // TODO: When implementing actual selection UI, this may be the correct + // method to call as the continue-selection action + fn Extend(&self, node: &Node, offset: u32) -> ErrorResult { + if !self.is_same_root(node) { + // Step 1 + return Ok(()); + } + if let Some(range) = self.range.get() { + if node.is_doctype() { + // w3c/selection-api#118 + return Err(Error::InvalidNodeType); + } + + if offset > node.len() { + // As with is_doctype, not explicit in selection spec steps here + // but implied by which exceptions are thrown in WPT tests + return Err(Error::IndexSize); + } + + // Step 4 + if !self.is_same_root(&*range.StartContainer()) { + // Step 5, and its following 8 and 9 + self.set_range(&*Range::new(&self.document, node, offset, node, offset)); + self.direction.set(Direction::Forwards); + } else { + let old_anchor_node = &*self.GetAnchorNode().unwrap(); // has range, therefore has anchor node + let old_anchor_offset = self.AnchorOffset(); + let is_old_anchor_before_or_equal = { + if old_anchor_node == node { + old_anchor_offset <= offset + } else { + old_anchor_node.is_before(node) + } + }; + if is_old_anchor_before_or_equal { + // Step 6, and its following 8 and 9 + self.set_range(&*Range::new( + &self.document, + old_anchor_node, + old_anchor_offset, + node, + offset, + )); + self.direction.set(Direction::Forwards); + } else { + // Step 7, and its following 8 and 9 + self.set_range(&*Range::new( + &self.document, + node, + offset, + old_anchor_node, + old_anchor_offset, + )); + self.direction.set(Direction::Backwards); + } + }; + } else { + // Step 2 + return Err(Error::InvalidState); + } + return Ok(()); + } + + // https://w3c.github.io/selection-api/#dom-selection-setbaseandextent + fn SetBaseAndExtent( + &self, + anchor_node: &Node, + anchor_offset: u32, + focus_node: &Node, + focus_offset: u32, + ) -> ErrorResult { + // Step 1 + if anchor_node.is_doctype() || focus_node.is_doctype() { + // w3c/selection-api#118 + return Err(Error::InvalidNodeType); + } + + if anchor_offset > anchor_node.len() || focus_offset > focus_node.len() { + return Err(Error::IndexSize); + } + + // Step 2 + if !self.is_same_root(anchor_node) || !self.is_same_root(focus_node) { + return Ok(()); + } + + // Steps 5-7 + let is_focus_before_anchor = { + if anchor_node == focus_node { + focus_offset < anchor_offset + } else { + focus_node.is_before(anchor_node) + } + }; + if is_focus_before_anchor { + self.set_range(&*Range::new( + &self.document, + focus_node, + focus_offset, + anchor_node, + anchor_offset, + )); + self.direction.set(Direction::Backwards); + } else { + self.set_range(&*Range::new( + &self.document, + anchor_node, + anchor_offset, + focus_node, + focus_offset, + )); + self.direction.set(Direction::Forwards); + } + Ok(()) + } + + // https://w3c.github.io/selection-api/#dom-selection-selectallchildren + fn SelectAllChildren(&self, node: &Node) -> ErrorResult { + if node.is_doctype() { + // w3c/selection-api#118 + return Err(Error::InvalidNodeType); + } + if !self.is_same_root(node) { + return Ok(()); + } + + // Spec wording just says node length here, but WPT specifically + // wants number of children (the main difference is that it's 0 + // for cdata). + self.set_range(&*Range::new( + &self.document, + node, + 0, + node, + node.children_count(), + )); + + self.direction.set(Direction::Forwards); + Ok(()) + } + + // https://w3c.github.io/selection-api/#dom-selection-deletecontents + fn DeleteFromDocument(&self) -> ErrorResult { + if let Some(range) = self.range.get() { + // Since the range is changing, it should trigger a + // selectionchange event as it would if if mutated any other way + return range.DeleteContents(); + } + return Ok(()); + } + + // https://w3c.github.io/selection-api/#dom-selection-containsnode + fn ContainsNode(&self, node: &Node, allow_partial_containment: bool) -> bool { + // TODO: Spec requires a "visually equivalent to" check, which is + // probably up to a layout query. This is therefore not a full implementation. + if !self.is_same_root(node) { + return false; + } + if let Some(range) = self.range.get() { + let start_node = &*range.StartContainer(); + if !self.is_same_root(start_node) { + // node can't be contained in a range with a different root + return false; + } + if allow_partial_containment { + // Spec seems to be incorrect here, w3c/selection-api#116 + if node.is_before(start_node) { + return false; + } + let end_node = &*range.EndContainer(); + if end_node.is_before(node) { + return false; + } + if node == start_node { + return range.StartOffset() < node.len(); + } + if node == end_node { + return range.EndOffset() > 0; + } + return true; + } else { + if node.is_before(start_node) { + return false; + } + let end_node = &*range.EndContainer(); + if end_node.is_before(node) { + return false; + } + if node == start_node { + return range.StartOffset() == 0; + } + if node == end_node { + return range.EndOffset() == node.len(); + } + return true; + } + } else { + // No range + return false; + } + } + + // https://w3c.github.io/selection-api/#dom-selection-stringifier + fn Stringifier(&self) -> DOMString { + // The spec as of Jan 31 2020 just says + // "See W3C bug 10583." for this method. + // Stringifying the range seems at least approximately right + // and passes the non-style-dependent case in the WPT tests. + if let Some(range) = self.range.get() { + range.Stringifier() + } else { + DOMString::from("") + } + } +} diff --git a/components/script/dom/serviceworker.rs b/components/script/dom/serviceworker.rs index b59c1da1afc..bcbdd158a40 100644 --- a/components/script/dom/serviceworker.rs +++ b/components/script/dom/serviceworker.rs @@ -1,24 +1,30 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::abstractworker::SimpleWorkerErrorHandler; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::ServiceWorkerBinding::{ServiceWorkerMethods, ServiceWorkerState, Wrap}; -use dom::bindings::error::{ErrorResult, Error}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::USVString; -use dom::bindings::structuredclone::StructuredCloneData; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::abstractworker::SimpleWorkerErrorHandler; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::PostMessageOptions; +use crate::dom::bindings::codegen::Bindings::ServiceWorkerBinding::{ + ServiceWorkerMethods, ServiceWorkerState, +}; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; +use crate::task::TaskOnce; use dom_struct::dom_struct; -use js::jsapi::{HandleValue, JSContext}; -use script_thread::Runnable; -use script_traits::{ScriptMsg, DOMMessage}; +use js::jsapi::{Heap, JSObject}; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; +use msg::constellation_msg::ServiceWorkerId; +use script_traits::{DOMMessage, ScriptMsg}; use servo_url::ServoUrl; use std::cell::Cell; @@ -27,32 +33,41 @@ pub type TrustedServiceWorkerAddress = Trusted<ServiceWorker>; #[dom_struct] pub struct ServiceWorker { eventtarget: EventTarget, - script_url: DOMRefCell<String>, + script_url: DomRefCell<String>, scope_url: ServoUrl, state: Cell<ServiceWorkerState>, - skip_waiting: Cell<bool> + worker_id: ServiceWorkerId, } impl ServiceWorker { - fn new_inherited(script_url: &str, - skip_waiting: bool, - scope_url: ServoUrl) -> ServiceWorker { + fn new_inherited( + script_url: &str, + scope_url: ServoUrl, + worker_id: ServiceWorkerId, + ) -> ServiceWorker { ServiceWorker { eventtarget: EventTarget::new_inherited(), - script_url: DOMRefCell::new(String::from(script_url)), + script_url: DomRefCell::new(String::from(script_url)), state: Cell::new(ServiceWorkerState::Installing), scope_url: scope_url, - skip_waiting: Cell::new(skip_waiting) + worker_id, } } - pub fn install_serviceworker(global: &GlobalScope, - script_url: ServoUrl, - scope_url: ServoUrl, - skip_waiting: bool) -> Root<ServiceWorker> { - reflect_dom_object(box ServiceWorker::new_inherited(script_url.as_str(), - skip_waiting, - scope_url), global, Wrap) + pub fn new( + global: &GlobalScope, + script_url: ServoUrl, + scope_url: ServoUrl, + worker_id: ServiceWorkerId, + ) -> DomRoot<ServiceWorker> { + reflect_dom_object( + Box::new(ServiceWorker::new_inherited( + script_url.as_str(), + scope_url, + worker_id, + )), + global, + ) } pub fn dispatch_simple_error(address: TrustedServiceWorkerAddress) { @@ -62,12 +77,41 @@ impl ServiceWorker { pub fn set_transition_state(&self, state: ServiceWorkerState) { self.state.set(state); - self.upcast::<EventTarget>().fire_event(atom!("statechange")); + self.upcast::<EventTarget>() + .fire_event(atom!("statechange")); } pub fn get_script_url(&self) -> ServoUrl { ServoUrl::parse(&self.script_url.borrow().clone()).unwrap() } + + /// https://w3c.github.io/ServiceWorker/#service-worker-postmessage + fn post_message_impl( + &self, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + // Step 1 + if let ServiceWorkerState::Redundant = self.state.get() { + return Err(Error::InvalidState); + } + // Step 7 + let data = structuredclone::write(cx, message, Some(transfer))?; + let incumbent = GlobalScope::incumbent().expect("no incumbent global?"); + let msg_vec = DOMMessage { + origin: incumbent.origin().immutable().clone(), + data, + }; + let _ = self + .global() + .script_to_constellation_chan() + .send(ScriptMsg::ForwardDOMMessage( + msg_vec, + self.scope_url.clone(), + )); + Ok(()) + } } impl ServiceWorkerMethods for ServiceWorker { @@ -81,21 +125,32 @@ impl ServiceWorkerMethods for ServiceWorker { USVString(self.script_url.borrow().clone()) } - #[allow(unsafe_code)] - // https://w3c.github.io/ServiceWorker/#service-worker-postmessage - unsafe fn PostMessage(&self, cx: *mut JSContext, message: HandleValue) -> ErrorResult { - // Step 1 - if let ServiceWorkerState::Redundant = self.state.get() { - return Err(Error::InvalidState); - } - // Step 7 - let data = try!(StructuredCloneData::write(cx, message)); - let msg_vec = DOMMessage(data.move_to_arraybuffer()); - let _ = - self.global() - .constellation_chan() - .send(ScriptMsg::ForwardDOMMessage(msg_vec, self.scope_url.clone())); - Ok(()) + /// https://w3c.github.io/ServiceWorker/#service-worker-postmessage + fn PostMessage( + &self, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + self.post_message_impl(cx, message, transfer) + } + + /// https://w3c.github.io/ServiceWorker/#service-worker-postmessage + fn PostMessage_( + &self, + cx: JSContext, + message: HandleValue, + options: RootedTraceableBox<PostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .transfer + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); + self.post_message_impl(cx, message, guard) } // https://w3c.github.io/ServiceWorker/#service-worker-container-onerror-attribute @@ -105,10 +160,9 @@ impl ServiceWorkerMethods for ServiceWorker { event_handler!(statechange, GetOnstatechange, SetOnstatechange); } -impl Runnable for SimpleWorkerErrorHandler<ServiceWorker> { +impl TaskOnce for SimpleWorkerErrorHandler<ServiceWorker> { #[allow(unrooted_must_root)] - fn handler(self: Box<SimpleWorkerErrorHandler<ServiceWorker>>) { - let this = *self; - ServiceWorker::dispatch_simple_error(this.addr); + fn run_once(self) { + ServiceWorker::dispatch_simple_error(self.addr); } } diff --git a/components/script/dom/serviceworkercontainer.rs b/components/script/dom/serviceworkercontainer.rs index ba2e327409f..b65dcd59b7d 100644 --- a/components/script/dom/serviceworkercontainer.rs +++ b/components/script/dom/serviceworkercontainer.rs @@ -1,30 +1,38 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::{ServiceWorkerContainerMethods, Wrap}; -use dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::RegistrationOptions; -use dom::bindings::error::Error; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::USVString; -use dom::client::Client; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; -use dom::serviceworker::ServiceWorker; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::RegistrationOptions; +use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::ServiceWorkerContainerMethods; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::refcounted::TrustedPromise; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::USVString; +use crate::dom::client::Client; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::serviceworker::ServiceWorker; +use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; +use crate::realms::enter_realm; +use crate::realms::InRealm; +use crate::task::TaskCanceller; +use crate::task_source::dom_manipulation::DOMManipulationTaskSource; +use crate::task_source::TaskSource; +use crate::task_source::TaskSourceName; use dom_struct::dom_struct; -use script_thread::ScriptThread; -use serviceworkerjob::{Job, JobType}; -use std::ascii::AsciiExt; +use ipc_channel::ipc; +use ipc_channel::router::ROUTER; +use script_traits::{Job, JobError, JobResult, JobResultValue, JobType, ScriptMsg}; use std::default::Default; use std::rc::Rc; #[dom_struct] pub struct ServiceWorkerContainer { eventtarget: EventTarget, - controller: MutNullableJS<ServiceWorker>, - client: JS<Client> + controller: MutNullableDom<ServiceWorker>, + client: Dom<Client>, } impl ServiceWorkerContainer { @@ -32,89 +40,231 @@ impl ServiceWorkerContainer { ServiceWorkerContainer { eventtarget: EventTarget::new_inherited(), controller: Default::default(), - client: JS::from_ref(client), + client: Dom::from_ref(client), } } #[allow(unrooted_must_root)] - pub fn new(global: &GlobalScope) -> Root<ServiceWorkerContainer> { + pub fn new(global: &GlobalScope) -> DomRoot<ServiceWorkerContainer> { let client = Client::new(&global.as_window()); let container = ServiceWorkerContainer::new_inherited(&*client); - reflect_dom_object(box container, global, Wrap) + reflect_dom_object(Box::new(container), global) } } impl ServiceWorkerContainerMethods for ServiceWorkerContainer { // https://w3c.github.io/ServiceWorker/#service-worker-container-controller-attribute - fn GetController(&self) -> Option<Root<ServiceWorker>> { + fn GetController(&self) -> Option<DomRoot<ServiceWorker>> { self.client.get_controller() } - #[allow(unrooted_must_root)] - // https://w3c.github.io/ServiceWorker/#service-worker-container-register-method and - A - // https://w3c.github.io/ServiceWorker/#start-register-algorithm - B - fn Register(&self, - script_url: USVString, - options: &RegistrationOptions) -> Rc<Promise> { + /// https://w3c.github.io/ServiceWorker/#dom-serviceworkercontainer-register - A + /// and https://w3c.github.io/ServiceWorker/#start-register - B + fn Register( + &self, + script_url: USVString, + options: &RegistrationOptions, + comp: InRealm, + ) -> Rc<Promise> { + // A: Step 2. + let global = self.client.global(); + // A: Step 1 - let promise = Promise::new(&*self.global()); - let ctx = (&*self.global()).get_cx(); + let promise = Promise::new_in_current_realm(&*global, comp); let USVString(ref script_url) = script_url; - let api_base_url = self.global().api_base_url(); - // A: Step 3-5 + + // A: Step 3 + let api_base_url = global.api_base_url(); let script_url = match api_base_url.join(script_url) { Ok(url) => url, Err(_) => { - promise.reject_error(ctx, Error::Type("Invalid script URL".to_owned())); + // B: Step 1 + promise.reject_error(Error::Type("Invalid script URL".to_owned())); return promise; - } + }, }; - // B: Step 2 - match script_url.scheme() { - "https" | "http" => {}, - _ => { - promise.reject_error(ctx, Error::Type("Only secure origins are allowed".to_owned())); - return promise; - } - } - // B: Step 3 - if script_url.path().to_ascii_lowercase().contains("%2f") || - script_url.path().to_ascii_lowercase().contains("%5c") { - promise.reject_error(ctx, Error::Type("Script URL contains forbidden characters".to_owned())); - return promise; - } - // B: Step 4-5 + + // A: Step 4-5 let scope = match options.scope { Some(ref scope) => { let &USVString(ref inner_scope) = scope; match api_base_url.join(inner_scope) { Ok(url) => url, Err(_) => { - promise.reject_error(ctx, Error::Type("Invalid scope URL".to_owned())); + promise.reject_error(Error::Type("Invalid scope URL".to_owned())); return promise; - } + }, } }, - None => script_url.join("./").unwrap() + None => script_url.join("./").unwrap(), }; + + // A: Step 6 -> invoke B. + + // B: Step 3 + match script_url.scheme() { + "https" | "http" => {}, + _ => { + promise.reject_error(Error::Type("Only secure origins are allowed".to_owned())); + return promise; + }, + } + // B: Step 4 + if script_url.path().to_ascii_lowercase().contains("%2f") || + script_url.path().to_ascii_lowercase().contains("%5c") + { + promise.reject_error(Error::Type( + "Script URL contains forbidden characters".to_owned(), + )); + return promise; + } + // B: Step 6 match scope.scheme() { "https" | "http" => {}, _ => { - promise.reject_error(ctx, Error::Type("Only secure origins are allowed".to_owned())); + promise.reject_error(Error::Type("Only secure origins are allowed".to_owned())); return promise; - } + }, } // B: Step 7 if scope.path().to_ascii_lowercase().contains("%2f") || - scope.path().to_ascii_lowercase().contains("%5c") { - promise.reject_error(ctx, Error::Type("Scope URL contains forbidden characters".to_owned())); + scope.path().to_ascii_lowercase().contains("%5c") + { + promise.reject_error(Error::Type( + "Scope URL contains forbidden characters".to_owned(), + )); return promise; } - // B: Step 8 - let job = Job::create_job(JobType::Register, scope, script_url, promise.clone(), &*self.client); - ScriptThread::schedule_job(job, &*self.global()); + // Setup the callback for reject/resolve of the promise, + // from steps running "in-parallel" from here in the serviceworker manager. + let (task_source, task_canceller) = ( + global.dom_manipulation_task_source(), + global.task_canceller(TaskSourceName::DOMManipulation), + ); + + let mut handler = RegisterJobResultHandler { + trusted_promise: Some(TrustedPromise::new(promise.clone())), + task_source, + task_canceller, + }; + + let (job_result_sender, job_result_receiver) = ipc::channel().expect("ipc channel failure"); + + ROUTER.add_route( + job_result_receiver.to_opaque(), + Box::new(move |message| { + let msg = message.to(); + match msg { + Ok(msg) => handler.handle(msg), + Err(err) => warn!("Error receiving a JobResult: {:?}", err), + } + }), + ); + + let scope_things = + ServiceWorkerRegistration::create_scope_things(&*global, script_url.clone()); + + // B: Step 8 - 13 + let job = Job::create_job( + JobType::Register, + scope, + script_url, + job_result_sender, + self.client.creation_url(), + Some(scope_things), + ); + + // B: Step 14: schedule job. + let _ = global + .script_to_constellation_chan() + .send(ScriptMsg::ScheduleJob(job)); + + // A: Step 7 promise } } + +/// Callback for resolve/reject job promise for Register. +/// <https://w3c.github.io/ServiceWorker/#register> +struct RegisterJobResultHandler { + trusted_promise: Option<TrustedPromise>, + task_source: DOMManipulationTaskSource, + task_canceller: TaskCanceller, +} + +impl RegisterJobResultHandler { + /// <https://w3c.github.io/ServiceWorker/#reject-job-promise> + /// <https://w3c.github.io/ServiceWorker/#resolve-job-promise> + /// Handle a result to either resolve or reject the register job promise. + pub fn handle(&mut self, result: JobResult) { + match result { + JobResult::RejectPromise(error) => { + let promise = self + .trusted_promise + .take() + .expect("No promise to resolve for SW Register job."); + + // Step 1 + let _ = self.task_source.queue_with_canceller( + task!(reject_promise_with_security_error: move || { + let promise = promise.root(); + let _ac = enter_realm(&*promise.global()); + match error { + JobError::TypeError => { + promise.reject_error(Error::Type("Failed to register a ServiceWorker".to_string())); + }, + JobError::SecurityError => { + promise.reject_error(Error::Security); + }, + } + + }), + &self.task_canceller, + ); + + // TODO: step 2, handle equivalent jobs. + }, + JobResult::ResolvePromise(job, value) => { + let promise = self + .trusted_promise + .take() + .expect("No promise to resolve for SW Register job."); + + // Step 1 + let _ = self.task_source.queue_with_canceller( + task!(resolve_promise: move || { + let promise = promise.root(); + let global = promise.global(); + let _ac = enter_realm(&*global); + + // Step 1.1 + let JobResultValue::Registration { + id, + installing_worker, + waiting_worker, + active_worker, + } = value; + + // Step 1.2 (Job type is "register"). + let registration = global.get_serviceworker_registration( + &job.script_url, + &job.scope_url, + id, + installing_worker, + waiting_worker, + active_worker, + ); + + // Step 1.4 + promise.resolve_native(&*registration); + }), + &self.task_canceller, + ); + + // TODO: step 2, handle equivalent jobs. + }, + } + } +} diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index ef69014c109..089b8c83590 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -1,141 +1,282 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use devtools; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::devtools; +use crate::dom::abstractworker::WorkerScriptMsg; +use crate::dom::abstractworkerglobalscope::{run_worker_event_loop, WorkerEventLoopMethods}; +use crate::dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding; +use crate::dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding::ServiceWorkerGlobalScopeMethods; +use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, RootCollection, ThreadLocalStackRoots}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::structuredclone; +use crate::dom::dedicatedworkerglobalscope::AutoWorkerReset; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::extendableevent::ExtendableEvent; +use crate::dom::extendablemessageevent::ExtendableMessageEvent; +use crate::dom::globalscope::GlobalScope; +use crate::dom::identityhub::Identities; +use crate::dom::worker::TrustedWorkerAddress; +use crate::dom::workerglobalscope::WorkerGlobalScope; +use crate::fetch::load_whole_resource; +use crate::realms::{enter_realm, AlreadyInRealm, InRealm}; +use crate::script_runtime::{ + new_rt_and_cx, CommonScriptMsg, ContextForRequestInterrupt, JSContext as SafeJSContext, + Runtime, ScriptChan, +}; +use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; +use crate::task_source::TaskSourceName; +use crossbeam_channel::{after, unbounded, Receiver, Sender}; use devtools_traits::DevtoolScriptControlMsg; -use dom::abstractworker::WorkerScriptMsg; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding; -use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding::ServiceWorkerGlobalScopeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{Root, RootCollection}; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::extendableevent::ExtendableEvent; -use dom::extendablemessageevent::ExtendableMessageEvent; -use dom::globalscope::GlobalScope; -use dom::workerglobalscope::WorkerGlobalScope; use dom_struct::dom_struct; -use ipc_channel::ipc::{self, IpcSender, IpcReceiver}; +use ipc_channel::ipc::{IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; -use js::jsapi::{JS_SetInterruptCallback, JSAutoCompartment, JSContext}; +use js::jsapi::{JSContext, JS_AddInterruptCallback}; use js::jsval::UndefinedValue; -use js::rust::Runtime; -use net_traits::{load_whole_resource, IpcSend, CustomResponseMediator}; -use net_traits::request::{CredentialsMode, Destination, RequestInit, Type as RequestType}; -use script_runtime::{CommonScriptMsg, StackRootTLS, get_reports, new_rt_and_cx, ScriptChan}; -use script_traits::{TimerEvent, WorkerGlobalScopeInit, ScopeThings, ServiceWorkerMsg, WorkerScriptLoadOrigin}; -use servo_config::prefs::PREFS; +use msg::constellation_msg::PipelineId; +use net_traits::request::{CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder}; +use net_traits::{CustomResponseMediator, IpcSend}; +use parking_lot::Mutex; +use script_traits::{ScopeThings, ServiceWorkerMsg, WorkerGlobalScopeInit, WorkerScriptLoadOrigin}; +use servo_config::pref; use servo_rand::random; use servo_url::{MutableOrigin, ServoUrl}; -use std::sync::mpsc::{Receiver, RecvError, Select, Sender, channel}; -use std::thread; -use std::time::Duration; -use style::thread_state::{self, IN_WORKER, SCRIPT}; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; +use std::time::{Duration, Instant}; +use style::thread_state::{self, ThreadState}; /// Messages used to control service worker event loop pub enum ServiceWorkerScriptMsg { /// Message common to all workers CommonWorker(WorkerScriptMsg), - // Message to request a custom response by the service worker - Response(CustomResponseMediator) + /// Message to request a custom response by the service worker + Response(CustomResponseMediator), + /// Wake-up call from the task queue. + WakeUp, +} + +impl QueuedTaskConversion for ServiceWorkerScriptMsg { + fn task_source_name(&self) -> Option<&TaskSourceName> { + let script_msg = match self { + ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg, + _ => return None, + }; + match script_msg { + CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => { + Some(&task_source) + }, + _ => None, + } + } + + fn pipeline_id(&self) -> Option<PipelineId> { + // Workers always return None, since the pipeline_id is only used to check for document activity, + // and this check does not apply to worker event-loops. + None + } + + fn into_queued_task(self) -> Option<QueuedTask> { + let script_msg = match self { + ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg, + _ => return None, + }; + let (category, boxed, pipeline_id, task_source) = match script_msg { + CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => { + (category, boxed, pipeline_id, task_source) + }, + _ => return None, + }; + Some((None, category, boxed, pipeline_id, task_source)) + } + + fn from_queued_task(queued_task: QueuedTask) -> Self { + let (_worker, category, boxed, pipeline_id, task_source) = queued_task; + let script_msg = CommonScriptMsg::Task(category, boxed, pipeline_id, task_source); + ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) + } + + fn inactive_msg() -> Self { + // Inactive is only relevant in the context of a browsing-context event-loop. + panic!("Workers should never receive messages marked as inactive"); + } + + fn wake_up_msg() -> Self { + ServiceWorkerScriptMsg::WakeUp + } + + fn is_wake_up(&self) -> bool { + match self { + ServiceWorkerScriptMsg::WakeUp => true, + _ => false, + } + } +} + +/// Messages sent from the owning registration. +pub enum ServiceWorkerControlMsg { + /// Shutdown. + Exit, } pub enum MixedMessage { FromServiceWorker(ServiceWorkerScriptMsg), FromDevtools(DevtoolScriptControlMsg), - FromTimeoutThread(()) + FromControl(ServiceWorkerControlMsg), } -#[derive(JSTraceable, Clone)] +#[derive(Clone, JSTraceable)] pub struct ServiceWorkerChan { - pub sender: Sender<ServiceWorkerScriptMsg> + pub sender: Sender<ServiceWorkerScriptMsg>, } impl ScriptChan for ServiceWorkerChan { fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> { self.sender - .send(ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(msg))) + .send(ServiceWorkerScriptMsg::CommonWorker( + WorkerScriptMsg::Common(msg), + )) .map_err(|_| ()) } - fn clone(&self) -> Box<ScriptChan + Send> { - box ServiceWorkerChan { + fn clone(&self) -> Box<dyn ScriptChan + Send> { + Box::new(ServiceWorkerChan { sender: self.sender.clone(), - } + }) } } +unsafe_no_jsmanaged_fields!(TaskQueue<ServiceWorkerScriptMsg>); + #[dom_struct] pub struct ServiceWorkerGlobalScope { workerglobalscope: WorkerGlobalScope, - #[ignore_heap_size_of = "Defined in std"] - receiver: Receiver<ServiceWorkerScriptMsg>, - #[ignore_heap_size_of = "Defined in std"] + + #[ignore_malloc_size_of = "Defined in std"] + task_queue: TaskQueue<ServiceWorkerScriptMsg>, + + #[ignore_malloc_size_of = "Defined in std"] own_sender: Sender<ServiceWorkerScriptMsg>, - #[ignore_heap_size_of = "Defined in std"] - timer_event_port: Receiver<()>, - #[ignore_heap_size_of = "Defined in std"] + + /// A port on which a single "time-out" message can be received, + /// indicating the sw should stop running, + /// while still draining the task-queue + // and running all enqueued, and not cancelled, tasks. + #[ignore_malloc_size_of = "Defined in std"] + time_out_port: Receiver<Instant>, + + #[ignore_malloc_size_of = "Defined in std"] swmanager_sender: IpcSender<ServiceWorkerMsg>, + scope_url: ServoUrl, + + /// A receiver of control messages, + /// currently only used to signal shutdown. + #[ignore_malloc_size_of = "Channels are hard"] + control_receiver: Receiver<ServiceWorkerControlMsg>, +} + +impl WorkerEventLoopMethods for ServiceWorkerGlobalScope { + type WorkerMsg = ServiceWorkerScriptMsg; + type ControlMsg = ServiceWorkerControlMsg; + type Event = MixedMessage; + + fn task_queue(&self) -> &TaskQueue<ServiceWorkerScriptMsg> { + &self.task_queue + } + + fn handle_event(&self, event: MixedMessage) -> bool { + self.handle_mixed_message(event) + } + + fn handle_worker_post_event(&self, _worker: &TrustedWorkerAddress) -> Option<AutoWorkerReset> { + None + } + + fn from_control_msg(&self, msg: ServiceWorkerControlMsg) -> MixedMessage { + MixedMessage::FromControl(msg) + } + + fn from_worker_msg(&self, msg: ServiceWorkerScriptMsg) -> MixedMessage { + MixedMessage::FromServiceWorker(msg) + } + + fn from_devtools_msg(&self, msg: DevtoolScriptControlMsg) -> MixedMessage { + MixedMessage::FromDevtools(msg) + } + + fn control_receiver(&self) -> &Receiver<ServiceWorkerControlMsg> { + &self.control_receiver + } } impl ServiceWorkerGlobalScope { - fn new_inherited(init: WorkerGlobalScopeInit, - worker_url: ServoUrl, - from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, - runtime: Runtime, - own_sender: Sender<ServiceWorkerScriptMsg>, - receiver: Receiver<ServiceWorkerScriptMsg>, - timer_event_chan: IpcSender<TimerEvent>, - timer_event_port: Receiver<()>, - swmanager_sender: IpcSender<ServiceWorkerMsg>, - scope_url: ServoUrl) - -> ServiceWorkerGlobalScope { + fn new_inherited( + init: WorkerGlobalScopeInit, + worker_url: ServoUrl, + from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, + runtime: Runtime, + own_sender: Sender<ServiceWorkerScriptMsg>, + receiver: Receiver<ServiceWorkerScriptMsg>, + time_out_port: Receiver<Instant>, + swmanager_sender: IpcSender<ServiceWorkerMsg>, + scope_url: ServoUrl, + control_receiver: Receiver<ServiceWorkerControlMsg>, + closing: Arc<AtomicBool>, + ) -> ServiceWorkerGlobalScope { ServiceWorkerGlobalScope { - workerglobalscope: WorkerGlobalScope::new_inherited(init, - worker_url, - runtime, - from_devtools_receiver, - timer_event_chan, - None), - receiver: receiver, - timer_event_port: timer_event_port, + workerglobalscope: WorkerGlobalScope::new_inherited( + init, + DOMString::new(), + WorkerType::Classic, // FIXME(cybai): Should be provided from `Run Service Worker` + worker_url, + runtime, + from_devtools_receiver, + closing, + Arc::new(Mutex::new(Identities::new())), + ), + task_queue: TaskQueue::new(receiver, own_sender.clone()), own_sender: own_sender, + time_out_port, swmanager_sender: swmanager_sender, - scope_url: scope_url + scope_url: scope_url, + control_receiver, } } #[allow(unsafe_code)] - pub fn new(init: WorkerGlobalScopeInit, - worker_url: ServoUrl, - from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, - runtime: Runtime, - own_sender: Sender<ServiceWorkerScriptMsg>, - receiver: Receiver<ServiceWorkerScriptMsg>, - timer_event_chan: IpcSender<TimerEvent>, - timer_event_port: Receiver<()>, - swmanager_sender: IpcSender<ServiceWorkerMsg>, - scope_url: ServoUrl) - -> Root<ServiceWorkerGlobalScope> { + pub fn new( + init: WorkerGlobalScopeInit, + worker_url: ServoUrl, + from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, + runtime: Runtime, + own_sender: Sender<ServiceWorkerScriptMsg>, + receiver: Receiver<ServiceWorkerScriptMsg>, + time_out_port: Receiver<Instant>, + swmanager_sender: IpcSender<ServiceWorkerMsg>, + scope_url: ServoUrl, + control_receiver: Receiver<ServiceWorkerControlMsg>, + closing: Arc<AtomicBool>, + ) -> DomRoot<ServiceWorkerGlobalScope> { let cx = runtime.cx(); - let scope = box ServiceWorkerGlobalScope::new_inherited(init, - worker_url, - from_devtools_receiver, - runtime, - own_sender, - receiver, - timer_event_chan, - timer_event_port, - swmanager_sender, - scope_url); - unsafe { - ServiceWorkerGlobalScopeBinding::Wrap(cx, scope) - } + let scope = Box::new(ServiceWorkerGlobalScope::new_inherited( + init, + worker_url, + from_devtools_receiver, + runtime, + own_sender, + receiver, + time_out_port, + swmanager_sender, + scope_url, + control_receiver, + closing, + )); + unsafe { ServiceWorkerGlobalScopeBinding::Wrap(SafeJSContext::from_ptr(cx), scope) } } pub fn origin(&self) -> MutableOrigin { @@ -143,188 +284,192 @@ impl ServiceWorkerGlobalScope { } #[allow(unsafe_code)] - pub fn run_serviceworker_scope(scope_things: ScopeThings, - own_sender: Sender<ServiceWorkerScriptMsg>, - receiver: Receiver<ServiceWorkerScriptMsg>, - devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>, - swmanager_sender: IpcSender<ServiceWorkerMsg>, - scope_url: ServoUrl) { - let ScopeThings { script_url, - init, - worker_load_origin, - .. } = scope_things; + // https://html.spec.whatwg.org/multipage/#run-a-worker + pub fn run_serviceworker_scope( + scope_things: ScopeThings, + own_sender: Sender<ServiceWorkerScriptMsg>, + receiver: Receiver<ServiceWorkerScriptMsg>, + devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>, + swmanager_sender: IpcSender<ServiceWorkerMsg>, + scope_url: ServoUrl, + control_receiver: Receiver<ServiceWorkerControlMsg>, + context_sender: Sender<ContextForRequestInterrupt>, + closing: Arc<AtomicBool>, + ) -> JoinHandle<()> { + let ScopeThings { + script_url, + init, + worker_load_origin, + .. + } = scope_things; let serialized_worker_url = script_url.to_string(); - thread::Builder::new().name(format!("ServiceWorker for {}", serialized_worker_url)).spawn(move || { - thread_state::initialize(SCRIPT | IN_WORKER); - let roots = RootCollection::new(); - let _stack_roots_tls = StackRootTLS::new(&roots); - - let WorkerScriptLoadOrigin { referrer_url, referrer_policy, pipeline_id } = worker_load_origin; - - let request = RequestInit { - url: script_url.clone(), - type_: RequestType::Script, - destination: Destination::ServiceWorker, - credentials_mode: CredentialsMode::Include, - use_url_credentials: true, - origin: script_url, - pipeline_id: pipeline_id, - referrer_url: referrer_url, - referrer_policy: referrer_policy, - .. RequestInit::default() - }; - - let (url, source) = match load_whole_resource(request, - &init.resource_threads.sender()) { - Err(_) => { - println!("error loading script {}", serialized_worker_url); - return; - } - Ok((metadata, bytes)) => { - (metadata.final_url, String::from_utf8(bytes).unwrap()) + let origin = scope_url.origin(); + thread::Builder::new() + .name(format!("ServiceWorker for {}", serialized_worker_url)) + .spawn(move || { + thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER); + let runtime = new_rt_and_cx(None); + let _ = context_sender.send(ContextForRequestInterrupt::new(runtime.cx())); + + let roots = RootCollection::new(); + let _stack_roots = ThreadLocalStackRoots::new(&roots); + + let WorkerScriptLoadOrigin { + referrer_url, + referrer_policy, + pipeline_id, + } = worker_load_origin; + + // Service workers are time limited + // https://w3c.github.io/ServiceWorker/#service-worker-lifetime + let sw_lifetime_timeout = pref!(dom.serviceworker.timeout_seconds) as u64; + let time_out_port = after(Duration::new(sw_lifetime_timeout, 0)); + + let (devtools_mpsc_chan, devtools_mpsc_port) = unbounded(); + ROUTER + .route_ipc_receiver_to_crossbeam_sender(devtools_receiver, devtools_mpsc_chan); + + let resource_threads_sender = init.resource_threads.sender(); + let global = ServiceWorkerGlobalScope::new( + init, + script_url.clone(), + devtools_mpsc_port, + runtime, + own_sender, + receiver, + time_out_port, + swmanager_sender, + scope_url, + control_receiver, + closing, + ); + + let referrer = referrer_url + .map(|url| Referrer::ReferrerUrl(url)) + .unwrap_or_else(|| global.upcast::<GlobalScope>().get_referrer()); + + let request = RequestBuilder::new(script_url, referrer) + .destination(Destination::ServiceWorker) + .credentials_mode(CredentialsMode::Include) + .parser_metadata(ParserMetadata::NotParserInserted) + .use_url_credentials(true) + .pipeline_id(Some(pipeline_id)) + .referrer_policy(referrer_policy) + .origin(origin); + + let (_url, source) = + match load_whole_resource(request, &resource_threads_sender, &*global.upcast()) + { + Err(_) => { + println!("error loading script {}", serialized_worker_url); + return; + }, + Ok((metadata, bytes)) => { + (metadata.final_url, String::from_utf8(bytes).unwrap()) + }, + }; + + let scope = global.upcast::<WorkerGlobalScope>(); + + unsafe { + // Handle interrupt requests + JS_AddInterruptCallback(*scope.get_cx(), Some(interrupt_callback)); } - }; - - let runtime = unsafe { new_rt_and_cx() }; - - let (devtools_mpsc_chan, devtools_mpsc_port) = channel(); - ROUTER.route_ipc_receiver_to_mpsc_sender(devtools_receiver, devtools_mpsc_chan); - // TODO XXXcreativcoder use this timer_ipc_port, when we have a service worker instance here - let (timer_ipc_chan, _timer_ipc_port) = ipc::channel().unwrap(); - let (timer_chan, timer_port) = channel(); - let global = ServiceWorkerGlobalScope::new( - init, url, devtools_mpsc_port, runtime, - own_sender, receiver, - timer_ipc_chan, timer_port, swmanager_sender, scope_url); - let scope = global.upcast::<WorkerGlobalScope>(); - - unsafe { - // Handle interrupt requests - JS_SetInterruptCallback(scope.runtime(), Some(interrupt_callback)); - } - - scope.execute_script(DOMString::from(source)); - // Service workers are time limited - thread::Builder::new().name("SWTimeoutThread".to_owned()).spawn(move || { - let sw_lifetime_timeout = PREFS.get("dom.serviceworker.timeout_seconds").as_u64().unwrap(); - thread::sleep(Duration::new(sw_lifetime_timeout, 0)); - let _ = timer_chan.send(()); - }).expect("Thread spawning failed"); - - global.dispatch_activate(); - let reporter_name = format!("service-worker-reporter-{}", random::<u64>()); - scope.upcast::<GlobalScope>().mem_profiler_chan().run_with_memory_reporting(|| { - // https://html.spec.whatwg.org/multipage/#event-loop-processing-model - // Step 1 - while let Ok(event) = global.receive_event() { - // Step 3 - if !global.handle_event(event) { - break; - } - // Step 6 - global.upcast::<WorkerGlobalScope>().perform_a_microtask_checkpoint(); - } - }, reporter_name, scope.script_chan(), CommonScriptMsg::CollectReports); - }).expect("Thread spawning failed"); + + scope.execute_script(DOMString::from(source)); + + global.dispatch_activate(); + let reporter_name = format!("service-worker-reporter-{}", random::<u64>()); + scope + .upcast::<GlobalScope>() + .mem_profiler_chan() + .run_with_memory_reporting( + || { + // Step 29, Run the responsible event loop specified + // by inside settings until it is destroyed. + // The worker processing model remains on this step + // until the event loop is destroyed, + // which happens after the closing flag is set to true, + // or until the worker has run beyond its allocated time. + while !scope.is_closing() && !global.has_timed_out() { + run_worker_event_loop(&*global, None); + } + }, + reporter_name, + scope.script_chan(), + CommonScriptMsg::CollectReports, + ); + scope.clear_js_runtime(); + }) + .expect("Thread spawning failed") } - fn handle_event(&self, event: MixedMessage) -> bool { - match event { - MixedMessage::FromDevtools(msg) => { - match msg { - DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) => - devtools::handle_evaluate_js(self.upcast(), string, sender), - DevtoolScriptControlMsg::GetCachedMessages(pipe_id, message_types, sender) => - devtools::handle_get_cached_messages(pipe_id, message_types, sender), - DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, bool_val) => - devtools::handle_wants_live_notifications(self.upcast(), bool_val), - _ => debug!("got an unusable devtools control message inside the worker!"), - } - true - } + fn handle_mixed_message(&self, msg: MixedMessage) -> bool { + match msg { + MixedMessage::FromDevtools(msg) => match msg { + DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) => { + devtools::handle_evaluate_js(self.upcast(), string, sender) + }, + DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, bool_val) => { + devtools::handle_wants_live_notifications(self.upcast(), bool_val) + }, + _ => debug!("got an unusable devtools control message inside the worker!"), + }, MixedMessage::FromServiceWorker(msg) => { self.handle_script_event(msg); - true - } - MixedMessage::FromTimeoutThread(_) => { - let _ = self.swmanager_sender.send(ServiceWorkerMsg::Timeout(self.scope_url.clone())); - false - } + }, + MixedMessage::FromControl(ServiceWorkerControlMsg::Exit) => { + return false; + }, } + true + } + + fn has_timed_out(&self) -> bool { + // TODO: https://w3c.github.io/ServiceWorker/#service-worker-lifetime + false } fn handle_script_event(&self, msg: ServiceWorkerScriptMsg) { use self::ServiceWorkerScriptMsg::*; match msg { - CommonWorker(WorkerScriptMsg::DOMMessage(data)) => { + CommonWorker(WorkerScriptMsg::DOMMessage { data, .. }) => { let scope = self.upcast::<WorkerGlobalScope>(); let target = self.upcast(); - let _ac = JSAutoCompartment::new(scope.get_cx(), scope.reflector().get_jsobject().get()); - rooted!(in(scope.get_cx()) let mut message = UndefinedValue()); - data.read(scope.upcast(), message.handle_mut()); - ExtendableMessageEvent::dispatch_jsval(target, scope.upcast(), message.handle()); - }, - CommonWorker(WorkerScriptMsg::Common(CommonScriptMsg::RunnableMsg(_, runnable))) => { - runnable.handler() + let _ac = enter_realm(&*scope); + rooted!(in(*scope.get_cx()) let mut message = UndefinedValue()); + if let Ok(ports) = structuredclone::read(scope.upcast(), data, message.handle_mut()) + { + ExtendableMessageEvent::dispatch_jsval( + target, + scope.upcast(), + message.handle(), + ports, + ); + } else { + ExtendableMessageEvent::dispatch_error(target, scope.upcast()); + } }, - CommonWorker(WorkerScriptMsg::Common(CommonScriptMsg::CollectReports(reports_chan))) => { - let scope = self.upcast::<WorkerGlobalScope>(); - let cx = scope.get_cx(); - let path_seg = format!("url({})", scope.get_url()); - let reports = get_reports(cx, path_seg); - reports_chan.send(reports); + CommonWorker(WorkerScriptMsg::Common(msg)) => { + self.upcast::<WorkerGlobalScope>().process_event(msg); }, Response(mediator) => { // TODO XXXcreativcoder This will eventually use a FetchEvent interface to fire event // when we have the Request and Response dom api's implemented - // https://slightlyoff.github.io/ServiceWorker/spec/service_worker_1/index.html#fetch-event-section + // https://w3c.github.io/ServiceWorker/#fetchevent-interface self.upcast::<EventTarget>().fire_event(atom!("fetch")); let _ = mediator.response_chan.send(None); - } - } - } - - #[allow(unsafe_code)] - fn receive_event(&self) -> Result<MixedMessage, RecvError> { - let scope = self.upcast::<WorkerGlobalScope>(); - let worker_port = &self.receiver; - let devtools_port = scope.from_devtools_receiver(); - let timer_event_port = &self.timer_event_port; - - let sel = Select::new(); - let mut worker_handle = sel.handle(worker_port); - let mut devtools_handle = sel.handle(devtools_port); - let mut timer_port_handle = sel.handle(timer_event_port); - unsafe { - worker_handle.add(); - if scope.from_devtools_sender().is_some() { - devtools_handle.add(); - } - timer_port_handle.add(); - } - - let ret = sel.wait(); - if ret == worker_handle.id() { - Ok(MixedMessage::FromServiceWorker(try!(worker_port.recv()))) - }else if ret == devtools_handle.id() { - Ok(MixedMessage::FromDevtools(try!(devtools_port.recv()))) - } else if ret == timer_port_handle.id() { - Ok(MixedMessage::FromTimeoutThread(try!(timer_event_port.recv()))) - } else { - panic!("unexpected select result!") + }, + WakeUp => {}, } } - pub fn process_event(&self, msg: CommonScriptMsg) { - self.handle_script_event(ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(msg))); - } - - pub fn script_chan(&self) -> Box<ScriptChan + Send> { - box ServiceWorkerChan { - sender: self.own_sender.clone() - } + pub fn script_chan(&self) -> Box<dyn ScriptChan + Send> { + Box::new(ServiceWorkerChan { + sender: self.own_sender.clone(), + }) } fn dispatch_activate(&self) { @@ -336,9 +481,10 @@ impl ServiceWorkerGlobalScope { #[allow(unsafe_code)] unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool { + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + let global = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)); let worker = - Root::downcast::<WorkerGlobalScope>(GlobalScope::from_context(cx)) - .expect("global is not a worker scope"); + DomRoot::downcast::<WorkerGlobalScope>(global).expect("global is not a worker scope"); assert!(worker.is::<ServiceWorkerGlobalScope>()); // A false response causes the script to terminate @@ -346,6 +492,9 @@ unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool { } impl ServiceWorkerGlobalScopeMethods for ServiceWorkerGlobalScope { - // https://w3c.github.io/ServiceWorker/#service-worker-global-scope-onmessage-attribute + // https://w3c.github.io/ServiceWorker/#dom-serviceworkerglobalscope-onmessage event_handler!(message, GetOnmessage, SetOnmessage); + + // https://w3c.github.io/ServiceWorker/#dom-serviceworkerglobalscope-onmessageerror + event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); } diff --git a/components/script/dom/serviceworkerregistration.rs b/components/script/dom/serviceworkerregistration.rs index b296b62b6c5..0c1734b41cc 100644 --- a/components/script/dom/serviceworkerregistration.rs +++ b/components/script/dom/serviceworkerregistration.rs @@ -1,54 +1,100 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::ServiceWorkerBinding::ServiceWorkerState; -use dom::bindings::codegen::Bindings::ServiceWorkerRegistrationBinding::{ServiceWorkerRegistrationMethods, Wrap}; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::USVString; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::serviceworker::ServiceWorker; -use dom::workerglobalscope::prepare_workerscope_init; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::ServiceWorkerRegistrationBinding::ServiceWorkerRegistrationMethods; +use crate::dom::bindings::codegen::Bindings::ServiceWorkerRegistrationBinding::ServiceWorkerUpdateViaCache; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{ByteString, USVString}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::navigationpreloadmanager::NavigationPreloadManager; +use crate::dom::serviceworker::ServiceWorker; +use crate::dom::workerglobalscope::prepare_workerscope_init; +use devtools_traits::WorkerId; use dom_struct::dom_struct; -use script_traits::{WorkerScriptLoadOrigin, ScopeThings}; +use msg::constellation_msg::ServiceWorkerRegistrationId; +use script_traits::{ScopeThings, WorkerScriptLoadOrigin}; use servo_url::ServoUrl; use std::cell::Cell; - +use uuid::Uuid; #[dom_struct] pub struct ServiceWorkerRegistration { eventtarget: EventTarget, - active: Option<JS<ServiceWorker>>, - installing: Option<JS<ServiceWorker>>, - waiting: Option<JS<ServiceWorker>>, + active: DomRefCell<Option<Dom<ServiceWorker>>>, + installing: DomRefCell<Option<Dom<ServiceWorker>>>, + waiting: DomRefCell<Option<Dom<ServiceWorker>>>, + navigation_preload: MutNullableDom<NavigationPreloadManager>, scope: ServoUrl, - uninstalling: Cell<bool> + navigation_preload_enabled: Cell<bool>, + navigation_preload_header_value: DomRefCell<Option<ByteString>>, + update_via_cache: ServiceWorkerUpdateViaCache, + uninstalling: Cell<bool>, + registration_id: ServiceWorkerRegistrationId, } impl ServiceWorkerRegistration { - fn new_inherited(active_sw: &ServiceWorker, scope: ServoUrl) -> ServiceWorkerRegistration { + fn new_inherited( + scope: ServoUrl, + registration_id: ServiceWorkerRegistrationId, + ) -> ServiceWorkerRegistration { ServiceWorkerRegistration { eventtarget: EventTarget::new_inherited(), - active: Some(JS::from_ref(active_sw)), - installing: None, - waiting: None, + active: DomRefCell::new(None), + installing: DomRefCell::new(None), + waiting: DomRefCell::new(None), + navigation_preload: MutNullableDom::new(None), scope: scope, - uninstalling: Cell::new(false) + navigation_preload_enabled: Cell::new(false), + navigation_preload_header_value: DomRefCell::new(None), + update_via_cache: ServiceWorkerUpdateViaCache::Imports, + uninstalling: Cell::new(false), + registration_id, } } + #[allow(unrooted_must_root)] - pub fn new(global: &GlobalScope, - script_url: &ServoUrl, - scope: ServoUrl) -> Root<ServiceWorkerRegistration> { - let active_worker = ServiceWorker::install_serviceworker(global, script_url.clone(), scope.clone(), true); - active_worker.set_transition_state(ServiceWorkerState::Installed); - reflect_dom_object(box ServiceWorkerRegistration::new_inherited(&*active_worker, scope), global, Wrap) + pub fn new( + global: &GlobalScope, + scope: ServoUrl, + registration_id: ServiceWorkerRegistrationId, + ) -> DomRoot<ServiceWorkerRegistration> { + reflect_dom_object( + Box::new(ServiceWorkerRegistration::new_inherited( + scope, + registration_id, + )), + global, + ) } - pub fn get_installed(&self) -> &ServiceWorker { - self.active.as_ref().unwrap() + /// Does this registration have an active worker? + pub fn is_active(&self) -> bool { + self.active.borrow().is_some() + } + + pub fn set_installing(&self, worker: &ServiceWorker) { + *self.installing.borrow_mut() = Some(Dom::from_ref(worker)); + } + + pub fn get_navigation_preload_header_value(&self) -> Option<ByteString> { + self.navigation_preload_header_value.borrow().clone() + } + + pub fn set_navigation_preload_header_value(&self, value: ByteString) { + let mut header_value = self.navigation_preload_header_value.borrow_mut(); + *header_value = Some(value); + } + + pub fn get_navigation_preload_enabled(&self) -> bool { + self.navigation_preload_enabled.get() + } + + pub fn set_navigation_preload_enabled(&self, flag: bool) { + self.navigation_preload_enabled.set(flag) } pub fn get_uninstalling(&self) -> bool { @@ -63,30 +109,31 @@ impl ServiceWorkerRegistration { let worker_load_origin = WorkerScriptLoadOrigin { referrer_url: None, referrer_policy: None, - pipeline_id: Some(global.pipeline_id()) + pipeline_id: global.pipeline_id(), }; - let worker_id = global.get_next_worker_id(); + let worker_id = WorkerId(Uuid::new_v4()); let devtools_chan = global.devtools_chan().cloned(); - let init = prepare_workerscope_init(&global, None); + let init = prepare_workerscope_init(&global, None, None); ScopeThings { script_url: script_url, init: init, worker_load_origin: worker_load_origin, devtools_chan: devtools_chan, - worker_id: worker_id + worker_id: worker_id, } } // https://w3c.github.io/ServiceWorker/#get-newest-worker-algorithm - pub fn get_newest_worker(&self) -> Option<Root<ServiceWorker>> { - if self.installing.as_ref().is_some() { - self.installing.as_ref().map(|sw| Root::from_ref(&**sw)) - } else if self.waiting.as_ref().is_some() { - self.waiting.as_ref().map(|sw| Root::from_ref(&**sw)) - } else { - self.active.as_ref().map(|sw| Root::from_ref(&**sw)) - } + pub fn get_newest_worker(&self) -> Option<DomRoot<ServiceWorker>> { + let installing = self.installing.borrow(); + let waiting = self.waiting.borrow(); + let active = self.active.borrow(); + installing + .as_ref() + .map(|sw| DomRoot::from_ref(&**sw)) + .or_else(|| waiting.as_ref().map(|sw| DomRoot::from_ref(&**sw))) + .or_else(|| active.as_ref().map(|sw| DomRoot::from_ref(&**sw))) } } @@ -100,27 +147,51 @@ pub fn longest_prefix_match(stored_scope: &ServoUrl, potential_match: &ServoUrl) return false; } - stored_scope.path().chars().zip(potential_match.path().chars()).all(|(scope, matched)| scope == matched) + stored_scope + .path() + .chars() + .zip(potential_match.path().chars()) + .all(|(scope, matched)| scope == matched) } impl ServiceWorkerRegistrationMethods for ServiceWorkerRegistration { // https://w3c.github.io/ServiceWorker/#service-worker-registration-installing-attribute - fn GetInstalling(&self) -> Option<Root<ServiceWorker>> { - self.installing.as_ref().map(|sw| Root::from_ref(&**sw)) + fn GetInstalling(&self) -> Option<DomRoot<ServiceWorker>> { + self.installing + .borrow() + .as_ref() + .map(|sw| DomRoot::from_ref(&**sw)) } // https://w3c.github.io/ServiceWorker/#service-worker-registration-active-attribute - fn GetActive(&self) -> Option<Root<ServiceWorker>> { - self.active.as_ref().map(|sw| Root::from_ref(&**sw)) + fn GetActive(&self) -> Option<DomRoot<ServiceWorker>> { + self.active + .borrow() + .as_ref() + .map(|sw| DomRoot::from_ref(&**sw)) } // https://w3c.github.io/ServiceWorker/#service-worker-registration-waiting-attribute - fn GetWaiting(&self) -> Option<Root<ServiceWorker>> { - self.waiting.as_ref().map(|sw| Root::from_ref(&**sw)) + fn GetWaiting(&self) -> Option<DomRoot<ServiceWorker>> { + self.waiting + .borrow() + .as_ref() + .map(|sw| DomRoot::from_ref(&**sw)) } // https://w3c.github.io/ServiceWorker/#service-worker-registration-scope-attribute fn Scope(&self) -> USVString { USVString(self.scope.as_str().to_owned()) } + + // https://w3c.github.io/ServiceWorker/#service-worker-registration-updateviacache + fn UpdateViaCache(&self) -> ServiceWorkerUpdateViaCache { + self.update_via_cache + } + + // https://w3c.github.io/ServiceWorker/#service-worker-registration-navigationpreload + fn NavigationPreload(&self) -> DomRoot<NavigationPreloadManager> { + self.navigation_preload + .or_init(|| NavigationPreloadManager::new(&self.global(), &self)) + } } diff --git a/components/script/dom/servoparser/async_html.rs b/components/script/dom/servoparser/async_html.rs new file mode 100644 index 00000000000..8b0a156842d --- /dev/null +++ b/components/script/dom/servoparser/async_html.rs @@ -0,0 +1,891 @@ +/* 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/. */ + +#![allow(unrooted_must_root)] + +use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::comment::Comment; +use crate::dom::document::Document; +use crate::dom::documenttype::DocumentType; +use crate::dom::element::{Element, ElementCreator}; +use crate::dom::htmlformelement::{FormControlElementHelpers, HTMLFormElement}; +use crate::dom::htmlscriptelement::HTMLScriptElement; +use crate::dom::htmltemplateelement::HTMLTemplateElement; +use crate::dom::node::Node; +use crate::dom::processinginstruction::ProcessingInstruction; +use crate::dom::servoparser::{create_element_for_token, ElementAttribute, ParsingAlgorithm}; +use crate::dom::virtualmethods::vtable_for; +use crossbeam_channel::{unbounded, Receiver, Sender}; +use html5ever::buffer_queue::BufferQueue; +use html5ever::tendril::fmt::UTF8; +use html5ever::tendril::{SendTendril, StrTendril, Tendril}; +use html5ever::tokenizer::{Tokenizer as HtmlTokenizer, TokenizerOpts, TokenizerResult}; +use html5ever::tree_builder::{ + ElementFlags, NextParserState, NodeOrText as HtmlNodeOrText, QuirksMode, TreeSink, +}; +use html5ever::tree_builder::{TreeBuilder, TreeBuilderOpts}; +use html5ever::{Attribute as HtmlAttribute, ExpandedName, QualName}; +use servo_url::ServoUrl; +use std::borrow::Cow; +use std::cell::Cell; +use std::collections::vec_deque::VecDeque; +use std::collections::HashMap; +use std::thread; +use style::context::QuirksMode as ServoQuirksMode; + +type ParseNodeId = usize; + +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub struct ParseNode { + id: ParseNodeId, + qual_name: Option<QualName>, +} + +#[derive(JSTraceable, MallocSizeOf)] +enum NodeOrText { + Node(ParseNode), + Text(String), +} + +#[derive(JSTraceable, MallocSizeOf)] +struct Attribute { + name: QualName, + value: String, +} + +#[derive(JSTraceable, MallocSizeOf)] +enum ParseOperation { + GetTemplateContents { + target: ParseNodeId, + contents: ParseNodeId, + }, + + CreateElement { + node: ParseNodeId, + name: QualName, + attrs: Vec<Attribute>, + current_line: u64, + }, + + CreateComment { + text: String, + node: ParseNodeId, + }, + AppendBeforeSibling { + sibling: ParseNodeId, + node: NodeOrText, + }, + AppendBasedOnParentNode { + element: ParseNodeId, + prev_element: ParseNodeId, + node: NodeOrText, + }, + Append { + parent: ParseNodeId, + node: NodeOrText, + }, + + AppendDoctypeToDocument { + name: String, + public_id: String, + system_id: String, + }, + + AddAttrsIfMissing { + target: ParseNodeId, + attrs: Vec<Attribute>, + }, + RemoveFromParent { + target: ParseNodeId, + }, + MarkScriptAlreadyStarted { + node: ParseNodeId, + }, + ReparentChildren { + parent: ParseNodeId, + new_parent: ParseNodeId, + }, + + AssociateWithForm { + target: ParseNodeId, + form: ParseNodeId, + element: ParseNodeId, + prev_element: Option<ParseNodeId>, + }, + + CreatePI { + node: ParseNodeId, + target: String, + data: String, + }, + + Pop { + node: ParseNodeId, + }, + + SetQuirksMode { + #[ignore_malloc_size_of = "Defined in style"] + mode: ServoQuirksMode, + }, +} + +#[derive(MallocSizeOf)] +enum ToTokenizerMsg { + // From HtmlTokenizer + TokenizerResultDone { + #[ignore_malloc_size_of = "Defined in html5ever"] + updated_input: VecDeque<SendTendril<UTF8>>, + }, + TokenizerResultScript { + script: ParseNode, + #[ignore_malloc_size_of = "Defined in html5ever"] + updated_input: VecDeque<SendTendril<UTF8>>, + }, + End, // Sent to Tokenizer to signify HtmlTokenizer's end method has returned + + // From Sink + ProcessOperation(ParseOperation), +} + +#[derive(MallocSizeOf)] +enum ToHtmlTokenizerMsg { + Feed { + #[ignore_malloc_size_of = "Defined in html5ever"] + input: VecDeque<SendTendril<UTF8>>, + }, + End, + SetPlainTextState, +} + +fn create_buffer_queue(mut buffers: VecDeque<SendTendril<UTF8>>) -> BufferQueue { + let mut buffer_queue = BufferQueue::new(); + while let Some(st) = buffers.pop_front() { + buffer_queue.push_back(StrTendril::from(st)); + } + buffer_queue +} + +// The async HTML Tokenizer consists of two separate types working together: the Tokenizer +// (defined below), which lives on the main thread, and the HtmlTokenizer, defined in html5ever, which +// lives on the parser thread. +// Steps: +// 1. A call to Tokenizer::new will spin up a new parser thread, creating an HtmlTokenizer instance, +// which starts listening for messages from Tokenizer. +// 2. Upon receiving an input from ServoParser, the Tokenizer forwards it to HtmlTokenizer, where it starts +// creating the necessary tree actions based on the input. +// 3. HtmlTokenizer sends these tree actions to the Tokenizer as soon as it creates them. The Tokenizer +// then executes the received actions. +// +// _____________ _______________ +// | | | | +// | | | | +// | | ToHtmlTokenizerMsg | | +// | |------------------------>| HtmlTokenizer | +// | | | | +// | Tokenizer | ToTokenizerMsg | | +// | |<------------------------| ________ | +// | | | | | | +// | | ToTokenizerMsg | | Sink | | +// | |<------------------------|---| | | +// | | | |________| | +// |_____________| |_______________| +// +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct Tokenizer { + document: Dom<Document>, + #[ignore_malloc_size_of = "Defined in std"] + receiver: Receiver<ToTokenizerMsg>, + #[ignore_malloc_size_of = "Defined in std"] + html_tokenizer_sender: Sender<ToHtmlTokenizerMsg>, + #[ignore_malloc_size_of = "Defined in std"] + nodes: HashMap<ParseNodeId, Dom<Node>>, + url: ServoUrl, + parsing_algorithm: ParsingAlgorithm, +} + +impl Tokenizer { + pub fn new( + document: &Document, + url: ServoUrl, + fragment_context: Option<super::FragmentContext>, + ) -> Self { + // Messages from the Tokenizer (main thread) to HtmlTokenizer (parser thread) + let (to_html_tokenizer_sender, html_tokenizer_receiver) = unbounded(); + // Messages from HtmlTokenizer and Sink (parser thread) to Tokenizer (main thread) + let (to_tokenizer_sender, tokenizer_receiver) = unbounded(); + + let algorithm = match fragment_context { + Some(_) => ParsingAlgorithm::Fragment, + None => ParsingAlgorithm::Normal, + }; + + let mut tokenizer = Tokenizer { + document: Dom::from_ref(document), + receiver: tokenizer_receiver, + html_tokenizer_sender: to_html_tokenizer_sender, + nodes: HashMap::new(), + url: url, + parsing_algorithm: algorithm, + }; + tokenizer.insert_node(0, Dom::from_ref(document.upcast())); + + let mut sink = Sink::new(to_tokenizer_sender.clone()); + let mut ctxt_parse_node = None; + let mut form_parse_node = None; + let mut fragment_context_is_some = false; + if let Some(fc) = fragment_context { + let node = sink.new_parse_node(); + tokenizer.insert_node(node.id, Dom::from_ref(fc.context_elem)); + ctxt_parse_node = Some(node); + + form_parse_node = fc.form_elem.map(|form_elem| { + let node = sink.new_parse_node(); + tokenizer.insert_node(node.id, Dom::from_ref(form_elem)); + node + }); + fragment_context_is_some = true; + }; + + // Create new thread for HtmlTokenizer. This is where parser actions + // will be generated from the input provided. These parser actions are then passed + // onto the main thread to be executed. + thread::Builder::new() + .name(String::from("HTML Parser")) + .spawn(move || { + run( + sink, + fragment_context_is_some, + ctxt_parse_node, + form_parse_node, + to_tokenizer_sender, + html_tokenizer_receiver, + ); + }) + .expect("HTML Parser thread spawning failed"); + + tokenizer + } + + pub fn feed(&mut self, input: &mut BufferQueue) -> Result<(), DomRoot<HTMLScriptElement>> { + let mut send_tendrils = VecDeque::new(); + while let Some(str) = input.pop_front() { + send_tendrils.push_back(SendTendril::from(str)); + } + + // Send message to parser thread, asking it to start reading from the input. + // Parser operation messages will be sent to main thread as they are evaluated. + self.html_tokenizer_sender + .send(ToHtmlTokenizerMsg::Feed { + input: send_tendrils, + }) + .unwrap(); + + loop { + match self + .receiver + .recv() + .expect("Unexpected channel panic in main thread.") + { + ToTokenizerMsg::ProcessOperation(parse_op) => self.process_operation(parse_op), + ToTokenizerMsg::TokenizerResultDone { updated_input } => { + let buffer_queue = create_buffer_queue(updated_input); + *input = buffer_queue; + return Ok(()); + }, + ToTokenizerMsg::TokenizerResultScript { + script, + updated_input, + } => { + let buffer_queue = create_buffer_queue(updated_input); + *input = buffer_queue; + let script = self.get_node(&script.id); + return Err(DomRoot::from_ref(script.downcast().unwrap())); + }, + ToTokenizerMsg::End => unreachable!(), + }; + } + } + + pub fn end(&mut self) { + self.html_tokenizer_sender + .send(ToHtmlTokenizerMsg::End) + .unwrap(); + loop { + match self + .receiver + .recv() + .expect("Unexpected channel panic in main thread.") + { + ToTokenizerMsg::ProcessOperation(parse_op) => self.process_operation(parse_op), + ToTokenizerMsg::End => return, + _ => unreachable!(), + }; + } + } + + pub fn url(&self) -> &ServoUrl { + &self.url + } + + pub fn set_plaintext_state(&mut self) { + self.html_tokenizer_sender + .send(ToHtmlTokenizerMsg::SetPlainTextState) + .unwrap(); + } + + fn insert_node(&mut self, id: ParseNodeId, node: Dom<Node>) { + assert!(self.nodes.insert(id, node).is_none()); + } + + fn get_node<'a>(&'a self, id: &ParseNodeId) -> &'a Dom<Node> { + self.nodes.get(id).expect("Node not found!") + } + + fn append_before_sibling(&mut self, sibling: ParseNodeId, node: NodeOrText) { + let node = match node { + NodeOrText::Node(n) => { + HtmlNodeOrText::AppendNode(Dom::from_ref(&**self.get_node(&n.id))) + }, + NodeOrText::Text(text) => HtmlNodeOrText::AppendText(Tendril::from(text)), + }; + let sibling = &**self.get_node(&sibling); + let parent = &*sibling + .GetParentNode() + .expect("append_before_sibling called on node without parent"); + + super::insert(parent, Some(sibling), node, self.parsing_algorithm); + } + + fn append(&mut self, parent: ParseNodeId, node: NodeOrText) { + let node = match node { + NodeOrText::Node(n) => { + HtmlNodeOrText::AppendNode(Dom::from_ref(&**self.get_node(&n.id))) + }, + NodeOrText::Text(text) => HtmlNodeOrText::AppendText(Tendril::from(text)), + }; + + let parent = &**self.get_node(&parent); + super::insert(parent, None, node, self.parsing_algorithm); + } + + fn has_parent_node(&self, node: ParseNodeId) -> bool { + self.get_node(&node).GetParentNode().is_some() + } + + fn same_tree(&self, x: ParseNodeId, y: ParseNodeId) -> bool { + let x = self.get_node(&x); + let y = self.get_node(&y); + + let x = x.downcast::<Element>().expect("Element node expected"); + let y = y.downcast::<Element>().expect("Element node expected"); + x.is_in_same_home_subtree(y) + } + + fn process_operation(&mut self, op: ParseOperation) { + let document = DomRoot::from_ref(&**self.get_node(&0)); + let document = document + .downcast::<Document>() + .expect("Document node should be downcasted!"); + match op { + ParseOperation::GetTemplateContents { target, contents } => { + let target = DomRoot::from_ref(&**self.get_node(&target)); + let template = target + .downcast::<HTMLTemplateElement>() + .expect("Tried to extract contents from non-template element while parsing"); + self.insert_node(contents, Dom::from_ref(template.Content().upcast())); + }, + ParseOperation::CreateElement { + node, + name, + attrs, + current_line, + } => { + let attrs = attrs + .into_iter() + .map(|attr| ElementAttribute::new(attr.name, DOMString::from(attr.value))) + .collect(); + let element = create_element_for_token( + name, + attrs, + &*self.document, + ElementCreator::ParserCreated(current_line), + ParsingAlgorithm::Normal, + ); + self.insert_node(node, Dom::from_ref(element.upcast())); + }, + ParseOperation::CreateComment { text, node } => { + let comment = Comment::new(DOMString::from(text), document); + self.insert_node(node, Dom::from_ref(&comment.upcast())); + }, + ParseOperation::AppendBeforeSibling { sibling, node } => { + self.append_before_sibling(sibling, node); + }, + ParseOperation::Append { parent, node } => { + self.append(parent, node); + }, + ParseOperation::AppendBasedOnParentNode { + element, + prev_element, + node, + } => { + if self.has_parent_node(element) { + self.append_before_sibling(element, node); + } else { + self.append(prev_element, node); + } + }, + ParseOperation::AppendDoctypeToDocument { + name, + public_id, + system_id, + } => { + let doctype = DocumentType::new( + DOMString::from(String::from(name)), + Some(DOMString::from(public_id)), + Some(DOMString::from(system_id)), + document, + ); + + document + .upcast::<Node>() + .AppendChild(doctype.upcast()) + .expect("Appending failed"); + }, + ParseOperation::AddAttrsIfMissing { target, attrs } => { + let elem = self + .get_node(&target) + .downcast::<Element>() + .expect("tried to set attrs on non-Element in HTML parsing"); + for attr in attrs { + elem.set_attribute_from_parser(attr.name, DOMString::from(attr.value), None); + } + }, + ParseOperation::RemoveFromParent { target } => { + if let Some(ref parent) = self.get_node(&target).GetParentNode() { + parent.RemoveChild(&**self.get_node(&target)).unwrap(); + } + }, + ParseOperation::MarkScriptAlreadyStarted { node } => { + let script = self.get_node(&node).downcast::<HTMLScriptElement>(); + script.map(|script| script.set_already_started(true)); + }, + ParseOperation::ReparentChildren { parent, new_parent } => { + let parent = self.get_node(&parent); + let new_parent = self.get_node(&new_parent); + while let Some(child) = parent.GetFirstChild() { + new_parent.AppendChild(&child).unwrap(); + } + }, + ParseOperation::AssociateWithForm { + target, + form, + element, + prev_element, + } => { + let tree_node = prev_element.map_or(element, |prev| { + if self.has_parent_node(element) { + element + } else { + prev + } + }); + + if !self.same_tree(tree_node, form) { + return; + } + let form = self.get_node(&form); + let form = DomRoot::downcast::<HTMLFormElement>(DomRoot::from_ref(&**form)) + .expect("Owner must be a form element"); + + let node = self.get_node(&target); + let elem = node.downcast::<Element>(); + let control = elem.and_then(|e| e.as_maybe_form_control()); + + if let Some(control) = control { + control.set_form_owner_from_parser(&form); + } else { + // TODO remove this code when keygen is implemented. + assert_eq!( + node.NodeName(), + "KEYGEN", + "Unknown form-associatable element" + ); + } + }, + ParseOperation::Pop { node } => { + vtable_for(self.get_node(&node)).pop(); + }, + ParseOperation::CreatePI { node, target, data } => { + let pi = ProcessingInstruction::new( + DOMString::from(target), + DOMString::from(data), + document, + ); + self.insert_node(node, Dom::from_ref(pi.upcast())); + }, + ParseOperation::SetQuirksMode { mode } => { + document.set_quirks_mode(mode); + }, + } + } +} + +fn run( + sink: Sink, + fragment_context_is_some: bool, + ctxt_parse_node: Option<ParseNode>, + form_parse_node: Option<ParseNode>, + sender: Sender<ToTokenizerMsg>, + receiver: Receiver<ToHtmlTokenizerMsg>, +) { + let options = TreeBuilderOpts { + ignore_missing_rules: true, + ..Default::default() + }; + + let mut html_tokenizer = if fragment_context_is_some { + let tb = + TreeBuilder::new_for_fragment(sink, ctxt_parse_node.unwrap(), form_parse_node, options); + + let tok_options = TokenizerOpts { + initial_state: Some(tb.tokenizer_state_for_context_elem()), + ..Default::default() + }; + + HtmlTokenizer::new(tb, tok_options) + } else { + HtmlTokenizer::new(TreeBuilder::new(sink, options), Default::default()) + }; + + loop { + match receiver + .recv() + .expect("Unexpected channel panic in html parser thread") + { + ToHtmlTokenizerMsg::Feed { input } => { + let mut input = create_buffer_queue(input); + let res = html_tokenizer.feed(&mut input); + + // Gather changes to 'input' and place them in 'updated_input', + // which will be sent to the main thread to update feed method's 'input' + let mut updated_input = VecDeque::new(); + while let Some(st) = input.pop_front() { + updated_input.push_back(SendTendril::from(st)); + } + + let res = match res { + TokenizerResult::Done => ToTokenizerMsg::TokenizerResultDone { updated_input }, + TokenizerResult::Script(script) => ToTokenizerMsg::TokenizerResultScript { + script, + updated_input, + }, + }; + sender.send(res).unwrap(); + }, + ToHtmlTokenizerMsg::End => { + html_tokenizer.end(); + sender.send(ToTokenizerMsg::End).unwrap(); + break; + }, + ToHtmlTokenizerMsg::SetPlainTextState => html_tokenizer.set_plaintext_state(), + }; + } +} + +#[derive(Default, JSTraceable, MallocSizeOf)] +struct ParseNodeData { + contents: Option<ParseNode>, + is_integration_point: bool, +} + +pub struct Sink { + current_line: u64, + parse_node_data: HashMap<ParseNodeId, ParseNodeData>, + next_parse_node_id: Cell<ParseNodeId>, + document_node: ParseNode, + sender: Sender<ToTokenizerMsg>, +} + +impl Sink { + fn new(sender: Sender<ToTokenizerMsg>) -> Sink { + let mut sink = Sink { + current_line: 1, + parse_node_data: HashMap::new(), + next_parse_node_id: Cell::new(1), + document_node: ParseNode { + id: 0, + qual_name: None, + }, + sender: sender, + }; + let data = ParseNodeData::default(); + sink.insert_parse_node_data(0, data); + sink + } + + fn new_parse_node(&mut self) -> ParseNode { + let id = self.next_parse_node_id.get(); + let data = ParseNodeData::default(); + self.insert_parse_node_data(id, data); + self.next_parse_node_id.set(id + 1); + ParseNode { + id: id, + qual_name: None, + } + } + + fn send_op(&self, op: ParseOperation) { + self.sender + .send(ToTokenizerMsg::ProcessOperation(op)) + .unwrap(); + } + + fn insert_parse_node_data(&mut self, id: ParseNodeId, data: ParseNodeData) { + assert!(self.parse_node_data.insert(id, data).is_none()); + } + + fn get_parse_node_data<'a>(&'a self, id: &'a ParseNodeId) -> &'a ParseNodeData { + self.parse_node_data + .get(id) + .expect("Parse Node data not found!") + } + + fn get_parse_node_data_mut<'a>(&'a mut self, id: &'a ParseNodeId) -> &'a mut ParseNodeData { + self.parse_node_data + .get_mut(id) + .expect("Parse Node data not found!") + } +} + +#[allow(unrooted_must_root)] +impl TreeSink for Sink { + type Output = Self; + fn finish(self) -> Self { + self + } + + type Handle = ParseNode; + + fn get_document(&mut self) -> Self::Handle { + self.document_node.clone() + } + + fn get_template_contents(&mut self, target: &Self::Handle) -> Self::Handle { + if let Some(ref contents) = self.get_parse_node_data(&target.id).contents { + return contents.clone(); + } + let node = self.new_parse_node(); + { + let data = self.get_parse_node_data_mut(&target.id); + data.contents = Some(node.clone()); + } + self.send_op(ParseOperation::GetTemplateContents { + target: target.id, + contents: node.id, + }); + node + } + + fn same_node(&self, x: &Self::Handle, y: &Self::Handle) -> bool { + x.id == y.id + } + + fn elem_name<'a>(&self, target: &'a Self::Handle) -> ExpandedName<'a> { + target + .qual_name + .as_ref() + .expect("Expected qual name of node!") + .expanded() + } + + fn create_element( + &mut self, + name: QualName, + html_attrs: Vec<HtmlAttribute>, + _flags: ElementFlags, + ) -> Self::Handle { + let mut node = self.new_parse_node(); + node.qual_name = Some(name.clone()); + { + let node_data = self.get_parse_node_data_mut(&node.id); + node_data.is_integration_point = html_attrs.iter().any(|attr| { + let attr_value = &String::from(attr.value.clone()); + (attr.name.local == local_name!("encoding") && attr.name.ns == ns!()) && + (attr_value.eq_ignore_ascii_case("text/html") || + attr_value.eq_ignore_ascii_case("application/xhtml+xml")) + }); + } + let attrs = html_attrs + .into_iter() + .map(|attr| Attribute { + name: attr.name, + value: String::from(attr.value), + }) + .collect(); + + self.send_op(ParseOperation::CreateElement { + node: node.id, + name, + attrs, + current_line: self.current_line, + }); + node + } + + fn create_comment(&mut self, text: StrTendril) -> Self::Handle { + let node = self.new_parse_node(); + self.send_op(ParseOperation::CreateComment { + text: String::from(text), + node: node.id, + }); + node + } + + fn create_pi(&mut self, target: StrTendril, data: StrTendril) -> ParseNode { + let node = self.new_parse_node(); + self.send_op(ParseOperation::CreatePI { + node: node.id, + target: String::from(target), + data: String::from(data), + }); + node + } + + fn associate_with_form( + &mut self, + target: &Self::Handle, + form: &Self::Handle, + nodes: (&Self::Handle, Option<&Self::Handle>), + ) { + let (element, prev_element) = nodes; + self.send_op(ParseOperation::AssociateWithForm { + target: target.id, + form: form.id, + element: element.id, + prev_element: prev_element.map(|p| p.id), + }); + } + + fn append_before_sibling( + &mut self, + sibling: &Self::Handle, + new_node: HtmlNodeOrText<Self::Handle>, + ) { + let new_node = match new_node { + HtmlNodeOrText::AppendNode(node) => NodeOrText::Node(node), + HtmlNodeOrText::AppendText(text) => NodeOrText::Text(String::from(text)), + }; + self.send_op(ParseOperation::AppendBeforeSibling { + sibling: sibling.id, + node: new_node, + }); + } + + fn append_based_on_parent_node( + &mut self, + elem: &Self::Handle, + prev_elem: &Self::Handle, + child: HtmlNodeOrText<Self::Handle>, + ) { + let child = match child { + HtmlNodeOrText::AppendNode(node) => NodeOrText::Node(node), + HtmlNodeOrText::AppendText(text) => NodeOrText::Text(String::from(text)), + }; + self.send_op(ParseOperation::AppendBasedOnParentNode { + element: elem.id, + prev_element: prev_elem.id, + node: child, + }); + } + + fn parse_error(&mut self, msg: Cow<'static, str>) { + debug!("Parse error: {}", msg); + } + + fn set_quirks_mode(&mut self, mode: QuirksMode) { + let mode = match mode { + QuirksMode::Quirks => ServoQuirksMode::Quirks, + QuirksMode::LimitedQuirks => ServoQuirksMode::LimitedQuirks, + QuirksMode::NoQuirks => ServoQuirksMode::NoQuirks, + }; + self.send_op(ParseOperation::SetQuirksMode { mode }); + } + + fn append(&mut self, parent: &Self::Handle, child: HtmlNodeOrText<Self::Handle>) { + let child = match child { + HtmlNodeOrText::AppendNode(node) => NodeOrText::Node(node), + HtmlNodeOrText::AppendText(text) => NodeOrText::Text(String::from(text)), + }; + self.send_op(ParseOperation::Append { + parent: parent.id, + node: child, + }); + } + + fn append_doctype_to_document( + &mut self, + name: StrTendril, + public_id: StrTendril, + system_id: StrTendril, + ) { + self.send_op(ParseOperation::AppendDoctypeToDocument { + name: String::from(name), + public_id: String::from(public_id), + system_id: String::from(system_id), + }); + } + + fn add_attrs_if_missing(&mut self, target: &Self::Handle, html_attrs: Vec<HtmlAttribute>) { + let attrs = html_attrs + .into_iter() + .map(|attr| Attribute { + name: attr.name, + value: String::from(attr.value), + }) + .collect(); + self.send_op(ParseOperation::AddAttrsIfMissing { + target: target.id, + attrs, + }); + } + + fn remove_from_parent(&mut self, target: &Self::Handle) { + self.send_op(ParseOperation::RemoveFromParent { target: target.id }); + } + + fn mark_script_already_started(&mut self, node: &Self::Handle) { + self.send_op(ParseOperation::MarkScriptAlreadyStarted { node: node.id }); + } + + fn complete_script(&mut self, _: &Self::Handle) -> NextParserState { + panic!("complete_script should not be called here!"); + } + + fn reparent_children(&mut self, parent: &Self::Handle, new_parent: &Self::Handle) { + self.send_op(ParseOperation::ReparentChildren { + parent: parent.id, + new_parent: new_parent.id, + }); + } + + /// <https://html.spec.whatwg.org/multipage/#html-integration-point> + /// Specifically, the <annotation-xml> cases. + fn is_mathml_annotation_xml_integration_point(&self, handle: &Self::Handle) -> bool { + let node_data = self.get_parse_node_data(&handle.id); + node_data.is_integration_point + } + + fn set_current_line(&mut self, line_number: u64) { + self.current_line = line_number; + } + + fn pop(&mut self, node: &Self::Handle) { + self.send_op(ParseOperation::Pop { node: node.id }); + } +} diff --git a/components/script/dom/servoparser/html.rs b/components/script/dom/servoparser/html.rs index 6142dba20f6..1bbc0aeeae8 100644 --- a/components/script/dom/servoparser/html.rs +++ b/components/script/dom/servoparser/html.rs @@ -1,77 +1,72 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #![allow(unrooted_must_root)] -use dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::{Castable, CharacterDataTypeId, NodeTypeId}; -use dom::bindings::js::{JS, Root}; -use dom::bindings::str::DOMString; -use dom::bindings::trace::JSTraceable; -use dom::characterdata::CharacterData; -use dom::comment::Comment; -use dom::document::Document; -use dom::documenttype::DocumentType; -use dom::element::{Element, ElementCreator}; -use dom::htmlformelement::{FormControlElementHelpers, HTMLFormElement}; -use dom::htmlscriptelement::HTMLScriptElement; -use dom::htmltemplateelement::HTMLTemplateElement; -use dom::node::Node; -use dom::processinginstruction::ProcessingInstruction; -use dom::virtualmethods::vtable_for; -use html5ever::Attribute; -use html5ever::QualName; -use html5ever::serialize::{AttrRef, Serializable, Serializer}; +use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; +use crate::dom::bindings::inheritance::{Castable, CharacterDataTypeId, NodeTypeId}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::characterdata::CharacterData; +use crate::dom::document::Document; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::documenttype::DocumentType; +use crate::dom::element::Element; +use crate::dom::htmlscriptelement::HTMLScriptElement; +use crate::dom::htmltemplateelement::HTMLTemplateElement; +use crate::dom::node::Node; +use crate::dom::processinginstruction::ProcessingInstruction; +use crate::dom::servoparser::{ParsingAlgorithm, Sink}; +use html5ever::buffer_queue::BufferQueue; use html5ever::serialize::TraversalScope; -use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode}; -use html5ever::tendril::StrTendril; +use html5ever::serialize::TraversalScope::IncludeNode; +use html5ever::serialize::{AttrRef, Serialize, Serializer}; use html5ever::tokenizer::{Tokenizer as HtmlTokenizer, TokenizerOpts, TokenizerResult}; -use html5ever::tokenizer::buffer_queue::BufferQueue; -use html5ever::tree_builder::{NodeOrText, QuirksMode}; -use html5ever::tree_builder::{Tracer as HtmlTracer, TreeBuilder, TreeBuilderOpts, TreeSink}; +use html5ever::tree_builder::{Tracer as HtmlTracer, TreeBuilder, TreeBuilderOpts}; +use html5ever::QualName; use js::jsapi::JSTracer; use servo_url::ServoUrl; -use std::ascii::AsciiExt; -use std::borrow::Cow; -use std::io::{self, Write}; -use style::context::QuirksMode as ServoQuirksMode; +use std::io; -#[derive(HeapSizeOf, JSTraceable)] -#[must_root] +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] pub struct Tokenizer { - #[ignore_heap_size_of = "Defined in html5ever"] - inner: HtmlTokenizer<TreeBuilder<JS<Node>, Sink>>, + #[ignore_malloc_size_of = "Defined in html5ever"] + inner: HtmlTokenizer<TreeBuilder<Dom<Node>, Sink>>, } impl Tokenizer { pub fn new( - document: &Document, - url: ServoUrl, - fragment_context: Option<super::FragmentContext>) - -> Self { + document: &Document, + url: ServoUrl, + fragment_context: Option<super::FragmentContext>, + parsing_algorithm: ParsingAlgorithm, + ) -> Self { let sink = Sink { base_url: url, - document: JS::from_ref(document), + document: Dom::from_ref(document), current_line: 1, + script: Default::default(), + parsing_algorithm: parsing_algorithm, }; let options = TreeBuilderOpts { ignore_missing_rules: true, - .. Default::default() + ..Default::default() }; let inner = if let Some(fc) = fragment_context { let tb = TreeBuilder::new_for_fragment( sink, - JS::from_ref(fc.context_elem), - fc.form_elem.map(|n| JS::from_ref(n)), - options); + Dom::from_ref(fc.context_elem), + fc.form_elem.map(|n| Dom::from_ref(n)), + options, + ); let tok_options = TokenizerOpts { initial_state: Some(tb.tokenizer_state_for_context_elem()), - .. Default::default() + ..Default::default() }; HtmlTokenizer::new(tb, tok_options) @@ -79,15 +74,13 @@ impl Tokenizer { HtmlTokenizer::new(TreeBuilder::new(sink, options), Default::default()) }; - Tokenizer { - inner: inner, - } + Tokenizer { inner: inner } } - pub fn feed(&mut self, input: &mut BufferQueue) -> Result<(), Root<HTMLScriptElement>> { + pub fn feed(&mut self, input: &mut BufferQueue) -> Result<(), DomRoot<HTMLScriptElement>> { match self.inner.feed(input) { TokenizerResult::Done => Ok(()), - TokenizerResult::Script(script) => Err(Root::from_ref(script.downcast().unwrap())), + TokenizerResult::Script(script) => Err(DomRoot::from_ref(script.downcast().unwrap())), } } @@ -96,7 +89,7 @@ impl Tokenizer { } pub fn url(&self) -> &ServoUrl { - &self.inner.sink().sink().base_url + &self.inner.sink.sink.base_url } pub fn set_plaintext_state(&mut self) { @@ -105,259 +98,166 @@ impl Tokenizer { } #[allow(unsafe_code)] -unsafe impl JSTraceable for HtmlTokenizer<TreeBuilder<JS<Node>, Sink>> { +unsafe impl JSTraceable for HtmlTokenizer<TreeBuilder<Dom<Node>, Sink>> { unsafe fn trace(&self, trc: *mut JSTracer) { struct Tracer(*mut JSTracer); let tracer = Tracer(trc); impl HtmlTracer for Tracer { - type Handle = JS<Node>; + type Handle = Dom<Node>; #[allow(unrooted_must_root)] - fn trace_handle(&self, node: &JS<Node>) { - unsafe { node.trace(self.0); } + fn trace_handle(&self, node: &Dom<Node>) { + unsafe { + node.trace(self.0); + } } } - let tree_builder = self.sink(); + let tree_builder = &self.sink; tree_builder.trace_handles(&tracer); - tree_builder.sink().trace(trc); + tree_builder.sink.trace(trc); } } -#[derive(JSTraceable, HeapSizeOf)] -#[must_root] -struct Sink { - base_url: ServoUrl, - document: JS<Document>, - current_line: u64, +fn start_element<S: Serializer>(node: &Element, serializer: &mut S) -> io::Result<()> { + let name = QualName::new(None, node.namespace().clone(), node.local_name().clone()); + let attrs = node + .attrs() + .iter() + .map(|attr| { + let qname = QualName::new(None, attr.namespace().clone(), attr.local_name().clone()); + let value = attr.value().clone(); + (qname, value) + }) + .collect::<Vec<_>>(); + let attr_refs = attrs.iter().map(|&(ref qname, ref value)| { + let ar: AttrRef = (&qname, &**value); + ar + }); + serializer.start_elem(name, attr_refs)?; + Ok(()) } -impl TreeSink for Sink { - type Output = Self; - fn finish(self) -> Self { self } - - type Handle = JS<Node>; - - fn get_document(&mut self) -> JS<Node> { - JS::from_ref(self.document.upcast()) - } - - fn get_template_contents(&mut self, target: JS<Node>) -> JS<Node> { - let template = target.downcast::<HTMLTemplateElement>() - .expect("tried to get template contents of non-HTMLTemplateElement in HTML parsing"); - JS::from_ref(template.Content().upcast()) - } - - fn same_node(&self, x: JS<Node>, y: JS<Node>) -> bool { - x == y - } - - fn elem_name(&self, target: JS<Node>) -> QualName { - let elem = target.downcast::<Element>() - .expect("tried to get name of non-Element in HTML parsing"); - QualName { - ns: elem.namespace().clone(), - local: elem.local_name().clone(), - } - } - - fn same_tree(&self, x: JS<Node>, y: JS<Node>) -> bool { - let x = x.downcast::<Element>().expect("Element node expected"); - let y = y.downcast::<Element>().expect("Element node expected"); - - x.is_in_same_home_subtree(y) - } - - fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) - -> JS<Node> { - let elem = Element::create(name, None, &*self.document, - ElementCreator::ParserCreated(self.current_line)); +fn end_element<S: Serializer>(node: &Element, serializer: &mut S) -> io::Result<()> { + let name = QualName::new(None, node.namespace().clone(), node.local_name().clone()); + serializer.end_elem(name) +} - for attr in attrs { - elem.set_attribute_from_parser(attr.name, DOMString::from(String::from(attr.value)), None); - } +enum SerializationCommand { + OpenElement(DomRoot<Element>), + CloseElement(DomRoot<Element>), + SerializeNonelement(DomRoot<Node>), +} - JS::from_ref(elem.upcast()) - } +struct SerializationIterator { + stack: Vec<SerializationCommand>, +} - fn create_comment(&mut self, text: StrTendril) -> JS<Node> { - let comment = Comment::new(DOMString::from(String::from(text)), &*self.document); - JS::from_ref(comment.upcast()) +fn rev_children_iter(n: &Node) -> impl Iterator<Item = DomRoot<Node>> { + if n.downcast::<Element>().map_or(false, |e| e.is_void()) { + return Node::new_document_node().rev_children(); } - fn has_parent_node(&self, node: JS<Node>) -> bool { - node.GetParentNode().is_some() + match n.downcast::<HTMLTemplateElement>() { + Some(t) => t.Content().upcast::<Node>().rev_children(), + None => n.rev_children(), } +} - fn associate_with_form(&mut self, target: JS<Node>, form: JS<Node>) { - let node = target; - let form = Root::downcast::<HTMLFormElement>(Root::from_ref(&*form)) - .expect("Owner must be a form element"); - - let elem = node.downcast::<Element>(); - let control = elem.as_ref().and_then(|e| e.as_maybe_form_control()); - - if let Some(control) = control { - control.set_form_owner_from_parser(&form); +impl SerializationIterator { + fn new(node: &Node, skip_first: bool) -> SerializationIterator { + let mut ret = SerializationIterator { stack: vec![] }; + if skip_first || node.is::<DocumentFragment>() || node.is::<Document>() { + for c in rev_children_iter(node) { + ret.push_node(&*c); + } } else { - // TODO remove this code when keygen is implemented. - assert!(node.NodeName() == "KEYGEN", "Unknown form-associatable element"); + ret.push_node(node); } + ret } - fn append_before_sibling(&mut self, - sibling: JS<Node>, - new_node: NodeOrText<JS<Node>>) { - let parent = sibling.GetParentNode() - .expect("append_before_sibling called on node without parent"); - - super::insert(&parent, Some(&*sibling), new_node); - } - - fn parse_error(&mut self, msg: Cow<'static, str>) { - debug!("Parse error: {}", msg); - } - - fn set_quirks_mode(&mut self, mode: QuirksMode) { - let mode = match mode { - QuirksMode::Quirks => ServoQuirksMode::Quirks, - QuirksMode::LimitedQuirks => ServoQuirksMode::LimitedQuirks, - QuirksMode::NoQuirks => ServoQuirksMode::NoQuirks, - }; - self.document.set_quirks_mode(mode); - } - - fn append(&mut self, parent: JS<Node>, child: NodeOrText<JS<Node>>) { - super::insert(&parent, None, child); - } - - fn append_doctype_to_document(&mut self, name: StrTendril, public_id: StrTendril, - system_id: StrTendril) { - let doc = &*self.document; - let doctype = DocumentType::new( - DOMString::from(String::from(name)), Some(DOMString::from(String::from(public_id))), - Some(DOMString::from(String::from(system_id))), doc); - doc.upcast::<Node>().AppendChild(doctype.upcast()).expect("Appending failed"); - } - - fn add_attrs_if_missing(&mut self, target: JS<Node>, attrs: Vec<Attribute>) { - let elem = target.downcast::<Element>() - .expect("tried to set attrs on non-Element in HTML parsing"); - for attr in attrs { - elem.set_attribute_from_parser(attr.name, DOMString::from(String::from(attr.value)), None); + fn push_node(&mut self, n: &Node) { + match n.downcast::<Element>() { + Some(e) => self + .stack + .push(SerializationCommand::OpenElement(DomRoot::from_ref(e))), + None => self.stack.push(SerializationCommand::SerializeNonelement( + DomRoot::from_ref(n), + )), } } +} - fn remove_from_parent(&mut self, target: JS<Node>) { - if let Some(ref parent) = target.GetParentNode() { - parent.RemoveChild(&*target).unwrap(); - } - } +impl Iterator for SerializationIterator { + type Item = SerializationCommand; - fn mark_script_already_started(&mut self, node: JS<Node>) { - let script = node.downcast::<HTMLScriptElement>(); - script.map(|script| script.set_already_started(true)); - } + fn next(&mut self) -> Option<SerializationCommand> { + let res = self.stack.pop(); - fn reparent_children(&mut self, node: JS<Node>, new_parent: JS<Node>) { - while let Some(ref child) = node.GetFirstChild() { - new_parent.AppendChild(&child).unwrap(); + if let Some(SerializationCommand::OpenElement(ref e)) = res { + self.stack + .push(SerializationCommand::CloseElement(e.clone())); + for c in rev_children_iter(&*e.upcast::<Node>()) { + self.push_node(&c); + } } - } - - /// https://html.spec.whatwg.org/multipage/#html-integration-point - /// Specifically, the <annotation-xml> cases. - fn is_mathml_annotation_xml_integration_point(&self, handle: JS<Node>) -> bool { - let elem = handle.downcast::<Element>().unwrap(); - elem.get_attribute(&ns!(), &local_name!("encoding")).map_or(false, |attr| { - attr.value().eq_ignore_ascii_case("text/html") - || attr.value().eq_ignore_ascii_case("application/xhtml+xml") - }) - } - - fn set_current_line(&mut self, line_number: u64) { - self.current_line = line_number; - } - fn pop(&mut self, node: JS<Node>) { - let node = Root::from_ref(&*node); - vtable_for(&node).pop(); + res } } -impl<'a> Serializable for &'a Node { - fn serialize<'wr, Wr: Write>(&self, serializer: &mut Serializer<'wr, Wr>, - traversal_scope: TraversalScope) -> io::Result<()> { +impl<'a> Serialize for &'a Node { + fn serialize<S: Serializer>( + &self, + serializer: &mut S, + traversal_scope: TraversalScope, + ) -> io::Result<()> { let node = *self; - match (traversal_scope, node.type_id()) { - (_, NodeTypeId::Element(..)) => { - let elem = node.downcast::<Element>().unwrap(); - let name = QualName::new(elem.namespace().clone(), - elem.local_name().clone()); - if traversal_scope == IncludeNode { - let attrs = elem.attrs().iter().map(|attr| { - let qname = QualName::new(attr.namespace().clone(), - attr.local_name().clone()); - let value = attr.value().clone(); - (qname, value) - }).collect::<Vec<_>>(); - let attr_refs = attrs.iter().map(|&(ref qname, ref value)| { - let ar: AttrRef = (&qname, &**value); - ar - }); - try!(serializer.start_elem(name.clone(), attr_refs)); - } - - let children = if let Some(tpl) = node.downcast::<HTMLTemplateElement>() { - // https://github.com/w3c/DOM-Parsing/issues/1 - tpl.Content().upcast::<Node>().children() - } else { - node.children() - }; - - for handle in children { - try!((&*handle).serialize(serializer, IncludeNode)); - } - - if traversal_scope == IncludeNode { - try!(serializer.end_elem(name.clone())); - } - Ok(()) - }, - - (ChildrenOnly, NodeTypeId::Document(_)) => { - for handle in node.children() { - try!((&*handle).serialize(serializer, IncludeNode)); - } - Ok(()) - }, - (ChildrenOnly, _) => Ok(()), - - (IncludeNode, NodeTypeId::DocumentType) => { - let doctype = node.downcast::<DocumentType>().unwrap(); - serializer.write_doctype(&doctype.name()) - }, - - (IncludeNode, NodeTypeId::CharacterData(CharacterDataTypeId::Text)) => { - let cdata = node.downcast::<CharacterData>().unwrap(); - serializer.write_text(&cdata.data()) - }, - - (IncludeNode, NodeTypeId::CharacterData(CharacterDataTypeId::Comment)) => { - let cdata = node.downcast::<CharacterData>().unwrap(); - serializer.write_comment(&cdata.data()) - }, - - (IncludeNode, NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction)) => { - let pi = node.downcast::<ProcessingInstruction>().unwrap(); - let data = pi.upcast::<CharacterData>().data(); - serializer.write_processing_instruction(&pi.target(), &data) - }, - - (IncludeNode, NodeTypeId::DocumentFragment) => Ok(()), - - (IncludeNode, NodeTypeId::Document(_)) => panic!("Can't serialize Document node itself"), + let iter = SerializationIterator::new(node, traversal_scope != IncludeNode); + + for cmd in iter { + match cmd { + SerializationCommand::OpenElement(n) => { + start_element(&n, serializer)?; + }, + + SerializationCommand::CloseElement(n) => { + end_element(&&n, serializer)?; + }, + + SerializationCommand::SerializeNonelement(n) => match n.type_id() { + NodeTypeId::DocumentType => { + let doctype = n.downcast::<DocumentType>().unwrap(); + serializer.write_doctype(&doctype.name())?; + }, + + NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => { + let cdata = n.downcast::<CharacterData>().unwrap(); + serializer.write_text(&cdata.data())?; + }, + + NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => { + let cdata = n.downcast::<CharacterData>().unwrap(); + serializer.write_comment(&cdata.data())?; + }, + + NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => { + let pi = n.downcast::<ProcessingInstruction>().unwrap(); + let data = pi.upcast::<CharacterData>().data(); + serializer.write_processing_instruction(&pi.target(), &data)?; + }, + + NodeTypeId::DocumentFragment(_) => {}, + + NodeTypeId::Document(_) => panic!("Can't serialize Document node itself"), + NodeTypeId::Element(_) => panic!("Element shouldn't appear here"), + NodeTypeId::Attr => panic!("Attr shouldn't appear here"), + }, + } } + + Ok(()) } } diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 3904910d3a3..481b39e25d3 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -1,49 +1,69 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use document_loader::{DocumentLoader, LoadType}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; -use dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::Bindings::ServoParserBinding; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root, RootedReference}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::characterdata::CharacterData; -use dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; -use dom::element::Element; -use dom::globalscope::GlobalScope; -use dom::htmlformelement::HTMLFormElement; -use dom::htmlimageelement::HTMLImageElement; -use dom::htmlscriptelement::{HTMLScriptElement, ScriptResult}; -use dom::node::{Node, NodeSiblingIterator}; -use dom::text::Text; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::document_loader::{DocumentLoader, LoadType}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ + DocumentMethods, DocumentReadyState, +}; +use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::settings_stack::is_execution_stack_empty; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::characterdata::CharacterData; +use crate::dom::comment::Comment; +use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; +use crate::dom::documenttype::DocumentType; +use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlformelement::{FormControlElementHelpers, HTMLFormElement}; +use crate::dom::htmlimageelement::HTMLImageElement; +use crate::dom::htmlinputelement::HTMLInputElement; +use crate::dom::htmlscriptelement::{HTMLScriptElement, ScriptResult}; +use crate::dom::htmltemplateelement::HTMLTemplateElement; +use crate::dom::node::{Node, ShadowIncluding}; +use crate::dom::performanceentry::PerformanceEntry; +use crate::dom::performancenavigationtiming::PerformanceNavigationTiming; +use crate::dom::processinginstruction::ProcessingInstruction; +use crate::dom::text::Text; +use crate::dom::virtualmethods::vtable_for; +use crate::network_listener::PreInvoke; +use crate::script_thread::ScriptThread; +use content_security_policy::{self as csp, CspList}; use dom_struct::dom_struct; -use encoding::all::UTF_8; -use encoding::types::{DecoderTrap, Encoding}; -use html5ever::tokenizer::buffer_queue::BufferQueue; -use html5ever::tree_builder::NodeOrText; -use hyper::header::ContentType; -use hyper::mime::{Mime, SubLevel, TopLevel}; +use embedder_traits::resources::{self, Resource}; +use encoding_rs::Encoding; +use html5ever::buffer_queue::BufferQueue; +use html5ever::tendril::fmt::UTF8; +use html5ever::tendril::{ByteTendril, StrTendril, TendrilSink}; +use html5ever::tree_builder::{ElementFlags, NextParserState, NodeOrText, QuirksMode, TreeSink}; +use html5ever::{Attribute, ExpandedName, LocalName, QualName}; use hyper_serde::Serde; +use mime::{self, Mime}; use msg::constellation_msg::PipelineId; use net_traits::{FetchMetadata, FetchResponseListener, Metadata, NetworkError}; -use network_listener::PreInvoke; -use profile_traits::time::{TimerMetadata, TimerMetadataFrameType}; -use profile_traits::time::{TimerMetadataReflowType, ProfilerCategory, profile}; -use script_thread::ScriptThread; +use net_traits::{ResourceFetchTiming, ResourceTimingType}; +use profile_traits::time::{ + profile, ProfilerCategory, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType, +}; use script_traits::DocumentActivity; -use servo_config::resource_files::read_resource_file; +use servo_config::pref; use servo_url::ServoUrl; -use std::ascii::AsciiExt; +use std::borrow::Cow; use std::cell::Cell; use std::mem; +use style::context::QuirksMode as ServoQuirksMode; +use tendril::stream::LossyDecoder; +mod async_html; mod html; +mod prefetch; mod xml; #[dom_struct] @@ -62,25 +82,39 @@ mod xml; pub struct ServoParser { reflector: Reflector, /// The document associated with this parser. - document: JS<Document>, + document: Dom<Document>, + /// The BOM sniffing state. + /// + /// `None` means we've found the BOM, we've found there isn't one, or + /// we're not parsing from a byte stream. `Some` contains the BOM bytes + /// found so far. + bom_sniff: DomRefCell<Option<Vec<u8>>>, + /// The decoder used for the network input. + network_decoder: DomRefCell<Option<NetworkDecoder>>, /// Input received from network. - #[ignore_heap_size_of = "Defined in html5ever"] - network_input: DOMRefCell<BufferQueue>, + #[ignore_malloc_size_of = "Defined in html5ever"] + network_input: DomRefCell<BufferQueue>, /// Input received from script. Used only to support document.write(). - #[ignore_heap_size_of = "Defined in html5ever"] - script_input: DOMRefCell<BufferQueue>, + #[ignore_malloc_size_of = "Defined in html5ever"] + script_input: DomRefCell<BufferQueue>, /// The tokenizer of this parser. - tokenizer: DOMRefCell<Tokenizer>, + tokenizer: DomRefCell<Tokenizer>, /// Whether to expect any further input from the associated network request. last_chunk_received: Cell<bool>, /// Whether this parser should avoid passing any further data to the tokenizer. suspended: Cell<bool>, - /// https://html.spec.whatwg.org/multipage/#script-nesting-level + /// <https://html.spec.whatwg.org/multipage/#script-nesting-level> script_nesting_level: Cell<usize>, - /// https://html.spec.whatwg.org/multipage/#abort-a-parser + /// <https://html.spec.whatwg.org/multipage/#abort-a-parser> aborted: Cell<bool>, - /// https://html.spec.whatwg.org/multipage/#script-created-parser + /// <https://html.spec.whatwg.org/multipage/#script-created-parser> script_created_parser: bool, + /// We do a quick-and-dirty parse of the input looking for resources to prefetch. + // TODO: if we had speculative parsing, we could do this when speculatively + // building the DOM. https://github.com/servo/servo/pull/19203 + prefetch_tokenizer: DomRefCell<prefetch::Tokenizer>, + #[ignore_malloc_size_of = "Defined in html5ever"] + prefetch_input: DomRefCell<BufferQueue>, } #[derive(PartialEq)] @@ -89,56 +123,117 @@ enum LastChunkState { NotReceived, } +pub struct ElementAttribute { + name: QualName, + value: DOMString, +} + +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +pub enum ParsingAlgorithm { + Normal, + Fragment, +} + +impl ElementAttribute { + pub fn new(name: QualName, value: DOMString) -> ElementAttribute { + ElementAttribute { + name: name, + value: value, + } + } +} + impl ServoParser { - pub fn parse_html_document(document: &Document, input: DOMString, url: ServoUrl) { - let parser = ServoParser::new(document, - Tokenizer::Html(self::html::Tokenizer::new(document, url, None)), - LastChunkState::NotReceived, - ParserKind::Normal); - parser.parse_chunk(String::from(input)); + pub fn parser_is_not_active(&self) -> bool { + self.can_write() || self.tokenizer.try_borrow_mut().is_ok() + } + + pub fn parse_html_document(document: &Document, input: Option<DOMString>, url: ServoUrl) { + let parser = if pref!(dom.servoparser.async_html_tokenizer.enabled) { + ServoParser::new( + document, + Tokenizer::AsyncHtml(self::async_html::Tokenizer::new(document, url, None)), + LastChunkState::NotReceived, + ParserKind::Normal, + ) + } else { + ServoParser::new( + document, + Tokenizer::Html(self::html::Tokenizer::new( + document, + url, + None, + ParsingAlgorithm::Normal, + )), + LastChunkState::NotReceived, + ParserKind::Normal, + ) + }; + + // Set as the document's current parser and initialize with `input`, if given. + if let Some(input) = input { + parser.parse_string_chunk(String::from(input)); + } else { + parser.document.set_current_parser(Some(&parser)); + } } // https://html.spec.whatwg.org/multipage/#parsing-html-fragments - pub fn parse_html_fragment(context: &Element, input: DOMString) -> FragmentParsingResult { + pub fn parse_html_fragment( + context: &Element, + input: DOMString, + ) -> impl Iterator<Item = DomRoot<Node>> { let context_node = context.upcast::<Node>(); let context_document = context_node.owner_doc(); let window = context_document.window(); let url = context_document.url(); // Step 1. - let loader = DocumentLoader::new_with_threads(context_document.loader().resource_threads().clone(), - Some(url.clone())); - let document = Document::new(window, - HasBrowsingContext::No, - Some(url.clone()), - context_document.origin().clone(), - IsHTMLDocument::HTMLDocument, - None, - None, - DocumentActivity::Inactive, - DocumentSource::FromParser, - loader, - None, - None); + let loader = DocumentLoader::new_with_threads( + context_document.loader().resource_threads().clone(), + Some(url.clone()), + ); + let document = Document::new( + window, + HasBrowsingContext::No, + Some(url.clone()), + context_document.origin().clone(), + IsHTMLDocument::HTMLDocument, + None, + None, + DocumentActivity::Inactive, + DocumentSource::FromParser, + loader, + None, + None, + Default::default(), + ); // Step 2. document.set_quirks_mode(context_document.quirks_mode()); // Step 11. - let form = context_node.inclusive_ancestors() + let form = context_node + .inclusive_ancestors(ShadowIncluding::No) .find(|element| element.is::<HTMLFormElement>()); + let fragment_context = FragmentContext { context_elem: context_node, - form_elem: form.r(), + form_elem: form.as_deref(), }; - let parser = ServoParser::new(&document, - Tokenizer::Html(self::html::Tokenizer::new(&document, - url.clone(), - Some(fragment_context))), - LastChunkState::Received, - ParserKind::Normal); - parser.parse_chunk(String::from(input)); + let parser = ServoParser::new( + &document, + Tokenizer::Html(self::html::Tokenizer::new( + &document, + url, + Some(fragment_context), + ParsingAlgorithm::Fragment, + )), + LastChunkState::Received, + ParserKind::Normal, + ); + parser.parse_string_chunk(String::from(input)); // Step 14. let root_element = document.GetDocumentElement().expect("no document element"); @@ -147,24 +242,36 @@ impl ServoParser { } } - pub fn parse_html_script_input(document: &Document, url: ServoUrl, type_: &str) { - let parser = ServoParser::new(document, - Tokenizer::Html(self::html::Tokenizer::new(document, url, None)), - LastChunkState::NotReceived, - ParserKind::ScriptCreated); + pub fn parse_html_script_input(document: &Document, url: ServoUrl) { + let parser = ServoParser::new( + document, + Tokenizer::Html(self::html::Tokenizer::new( + document, + url, + None, + ParsingAlgorithm::Normal, + )), + LastChunkState::NotReceived, + ParserKind::ScriptCreated, + ); + *parser.bom_sniff.borrow_mut() = None; document.set_current_parser(Some(&parser)); - if !type_.eq_ignore_ascii_case("text/html") { - parser.parse_chunk("<pre>\n".to_owned()); - parser.tokenizer.borrow_mut().set_plaintext_state(); - } } - pub fn parse_xml_document(document: &Document, input: DOMString, url: ServoUrl) { - let parser = ServoParser::new(document, - Tokenizer::Xml(self::xml::Tokenizer::new(document, url)), - LastChunkState::NotReceived, - ParserKind::Normal); - parser.parse_chunk(String::from(input)); + pub fn parse_xml_document(document: &Document, input: Option<DOMString>, url: ServoUrl) { + let parser = ServoParser::new( + document, + Tokenizer::Xml(self::xml::Tokenizer::new(document, url)), + LastChunkState::NotReceived, + ParserKind::Normal, + ); + + // Set as the document's current parser and initialize with `input`, if given. + if let Some(input) = input { + parser.parse_string_chunk(String::from(input)); + } else { + parser.document.set_current_parser(Some(&parser)); + } } pub fn script_nesting_level(&self) -> usize { @@ -177,7 +284,7 @@ impl ServoParser { /// Corresponds to the latter part of the "Otherwise" branch of the 'An end /// tag whose tag name is "script"' of - /// https://html.spec.whatwg.org/multipage/#parsing-main-incdata + /// <https://html.spec.whatwg.org/multipage/#parsing-main-incdata> /// /// This first moves everything from the script input to the beginning of /// the network input, effectively resetting the insertion point to just @@ -189,12 +296,18 @@ impl ServoParser { /// ^ /// insertion point /// ``` - pub fn resume_with_pending_parsing_blocking_script(&self, script: &HTMLScriptElement, result: ScriptResult) { + pub fn resume_with_pending_parsing_blocking_script( + &self, + script: &HTMLScriptElement, + result: ScriptResult, + ) { assert!(self.suspended.get()); self.suspended.set(false); - mem::swap(&mut *self.script_input.borrow_mut(), - &mut *self.network_input.borrow_mut()); + mem::swap( + &mut *self.script_input.borrow_mut(), + &mut *self.network_input.borrow_mut(), + ); while let Some(chunk) = self.script_input.borrow_mut().pop_front() { self.network_input.borrow_mut().push_back(chunk); } @@ -224,7 +337,9 @@ impl ServoParser { // parser is suspended, we just append everything to the // script input and abort these steps. for chunk in text { - self.script_input.borrow_mut().push_back(String::from(chunk).into()); + self.script_input + .borrow_mut() + .push_back(String::from(chunk).into()); } return; } @@ -280,49 +395,133 @@ impl ServoParser { *self.network_input.borrow_mut() = BufferQueue::new(); // Step 2. - self.document.set_ready_state(DocumentReadyState::Interactive); + self.document + .set_ready_state(DocumentReadyState::Interactive); // Step 3. self.tokenizer.borrow_mut().end(); self.document.set_current_parser(None); // Step 4. - self.document.set_ready_state(DocumentReadyState::Interactive); + self.document.set_ready_state(DocumentReadyState::Complete); + } + + // https://html.spec.whatwg.org/multipage/#active-parser + pub fn is_active(&self) -> bool { + self.script_nesting_level() > 0 && !self.aborted.get() } #[allow(unrooted_must_root)] - fn new_inherited(document: &Document, - tokenizer: Tokenizer, - last_chunk_state: LastChunkState, - kind: ParserKind) - -> Self { + fn new_inherited( + document: &Document, + tokenizer: Tokenizer, + last_chunk_state: LastChunkState, + kind: ParserKind, + ) -> Self { ServoParser { reflector: Reflector::new(), - document: JS::from_ref(document), - network_input: DOMRefCell::new(BufferQueue::new()), - script_input: DOMRefCell::new(BufferQueue::new()), - tokenizer: DOMRefCell::new(tokenizer), + document: Dom::from_ref(document), + bom_sniff: DomRefCell::new(Some(Vec::with_capacity(3))), + network_decoder: DomRefCell::new(Some(NetworkDecoder::new(document.encoding()))), + network_input: DomRefCell::new(BufferQueue::new()), + script_input: DomRefCell::new(BufferQueue::new()), + tokenizer: DomRefCell::new(tokenizer), last_chunk_received: Cell::new(last_chunk_state == LastChunkState::Received), suspended: Default::default(), script_nesting_level: Default::default(), aborted: Default::default(), script_created_parser: kind == ParserKind::ScriptCreated, + prefetch_tokenizer: DomRefCell::new(prefetch::Tokenizer::new(document)), + prefetch_input: DomRefCell::new(BufferQueue::new()), } } #[allow(unrooted_must_root)] - fn new(document: &Document, - tokenizer: Tokenizer, - last_chunk_state: LastChunkState, - kind: ParserKind) - -> Root<Self> { - reflect_dom_object(box ServoParser::new_inherited(document, tokenizer, last_chunk_state, kind), - document.window(), - ServoParserBinding::Wrap) + fn new( + document: &Document, + tokenizer: Tokenizer, + last_chunk_state: LastChunkState, + kind: ParserKind, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(ServoParser::new_inherited( + document, + tokenizer, + last_chunk_state, + kind, + )), + document.window(), + ) + } + + fn push_tendril_input_chunk(&self, chunk: StrTendril) { + if chunk.is_empty() { + return; + } + // Per https://github.com/whatwg/html/issues/1495 + // stylesheets should not be loaded for documents + // without browsing contexts. + // https://github.com/whatwg/html/issues/1495#issuecomment-230334047 + // suggests that no content should be preloaded in such a case. + // We're conservative, and only prefetch for documents + // with browsing contexts. + if self.document.browsing_context().is_some() { + // Push the chunk into the prefetch input stream, + // which is tokenized eagerly, to scan for resources + // to prefetch. If the user script uses `document.write()` + // to overwrite the network input, this prefetching may + // have been wasted, but in most cases it won't. + let mut prefetch_input = self.prefetch_input.borrow_mut(); + prefetch_input.push_back(chunk.clone()); + self.prefetch_tokenizer + .borrow_mut() + .feed(&mut *prefetch_input); + } + // Push the chunk into the network input stream, + // which is tokenized lazily. + self.network_input.borrow_mut().push_back(chunk); + } + + fn push_bytes_input_chunk(&self, chunk: Vec<u8>) { + // BOM sniff. This is needed because NetworkDecoder will switch the + // encoding based on the BOM, but it won't change + // `self.document.encoding` in the process. + { + let mut bom_sniff = self.bom_sniff.borrow_mut(); + if let Some(partial_bom) = bom_sniff.as_mut() { + if partial_bom.len() + chunk.len() >= 3 { + partial_bom.extend(chunk.iter().take(3 - partial_bom.len()).copied()); + if let Some((encoding, _)) = Encoding::for_bom(&partial_bom) { + self.document.set_encoding(encoding); + } + drop(bom_sniff); + *self.bom_sniff.borrow_mut() = None; + } else { + partial_bom.extend(chunk.iter().copied()); + } + } + } + + // For byte input, we convert it to text using the network decoder. + let chunk = self + .network_decoder + .borrow_mut() + .as_mut() + .unwrap() + .decode(chunk); + self.push_tendril_input_chunk(chunk); } - fn push_input_chunk(&self, chunk: String) { - self.network_input.borrow_mut().push_back(chunk.into()); + fn push_string_input_chunk(&self, chunk: String) { + // If the input is a string, we don't have a BOM. + if self.bom_sniff.borrow().is_some() { + *self.bom_sniff.borrow_mut() = None; + } + + // The input has already been decoded as a string, so doesn't need + // to be decoded by the network decoder again. + let chunk = StrTendril::from(chunk); + self.push_tendril_input_chunk(chunk); } fn parse_sync(&self) { @@ -332,10 +531,16 @@ impl ServoParser { incremental: TimerMetadataReflowType::FirstReflow, }; let profiler_category = self.tokenizer.borrow().profiler_category(); - profile(profiler_category, - Some(metadata), - self.document.window().upcast::<GlobalScope>().time_profiler_chan().clone(), - || self.do_parse_sync()) + profile( + profiler_category, + Some(metadata), + self.document + .window() + .upcast::<GlobalScope>() + .time_profiler_chan() + .clone(), + || self.do_parse_sync(), + ) } fn do_parse_sync(&self) { @@ -344,6 +549,14 @@ impl ServoParser { // This parser will continue to parse while there is either pending input or // the parser remains unsuspended. + if self.last_chunk_received.get() { + if let Some(decoder) = self.network_decoder.borrow_mut().take() { + let chunk = decoder.finish(); + if !chunk.is_empty() { + self.network_input.borrow_mut().push_back(chunk); + } + } + } self.tokenize(|tokenizer| tokenizer.feed(&mut *self.network_input.borrow_mut())); if self.suspended.get() { @@ -357,16 +570,25 @@ impl ServoParser { } } - fn parse_chunk(&self, input: String) { + fn parse_string_chunk(&self, input: String) { self.document.set_current_parser(Some(self)); - self.push_input_chunk(input); + self.push_string_input_chunk(input); + if !self.suspended.get() { + self.parse_sync(); + } + } + + fn parse_bytes_chunk(&self, input: Vec<u8>) { + self.document.set_current_parser(Some(self)); + self.push_bytes_input_chunk(input); if !self.suspended.get() { self.parse_sync(); } } fn tokenize<F>(&self, mut feed: F) - where F: FnMut(&mut Tokenizer) -> Result<(), Root<HTMLScriptElement>>, + where + F: FnMut(&mut Tokenizer) -> Result<(), DomRoot<HTMLScriptElement>>, { loop { assert!(!self.suspended.get()); @@ -378,6 +600,19 @@ impl ServoParser { Err(script) => script, }; + // https://html.spec.whatwg.org/multipage/#parsing-main-incdata + // branch "An end tag whose tag name is "script" + // The spec says to perform the microtask checkpoint before + // setting the insertion mode back from Text, but this is not + // possible with the way servo and html5ever currently + // relate to each other, and hopefully it is not observable. + if is_execution_stack_empty() { + self.document + .window() + .upcast::<GlobalScope>() + .perform_a_microtask_checkpoint(); + } + let script_nesting_level = self.script_nesting_level.get(); self.script_nesting_level.set(script_nesting_level + 1); @@ -388,6 +623,9 @@ impl ServoParser { self.suspended.set(true); return; } + if self.aborted.get() { + return; + } } } @@ -397,9 +635,11 @@ impl ServoParser { assert!(self.last_chunk_received.get()); assert!(self.script_input.borrow().is_empty()); assert!(self.network_input.borrow().is_empty()); + assert!(self.network_decoder.borrow().is_none()); // Step 1. - self.document.set_ready_state(DocumentReadyState::Interactive); + self.document + .set_ready_state(DocumentReadyState::Interactive); // Step 2. self.tokenizer.borrow_mut().end(); @@ -411,40 +651,49 @@ impl ServoParser { } } -pub struct FragmentParsingResult { - inner: NodeSiblingIterator, +struct FragmentParsingResult<I> +where + I: Iterator<Item = DomRoot<Node>>, +{ + inner: I, } -impl Iterator for FragmentParsingResult { - type Item = Root<Node>; +impl<I> Iterator for FragmentParsingResult<I> +where + I: Iterator<Item = DomRoot<Node>>, +{ + type Item = DomRoot<Node>; - fn next(&mut self) -> Option<Root<Node>> { - let next = match self.inner.next() { - Some(next) => next, - None => return None, - }; + fn next(&mut self) -> Option<DomRoot<Node>> { + let next = self.inner.next()?; next.remove_self(); Some(next) } + + fn size_hint(&self) -> (usize, Option<usize>) { + self.inner.size_hint() + } } -#[derive(HeapSizeOf, JSTraceable, PartialEq)] +#[derive(JSTraceable, MallocSizeOf, PartialEq)] enum ParserKind { Normal, ScriptCreated, } -#[derive(HeapSizeOf, JSTraceable)] -#[must_root] +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] enum Tokenizer { Html(self::html::Tokenizer), + AsyncHtml(self::async_html::Tokenizer), Xml(self::xml::Tokenizer), } impl Tokenizer { - fn feed(&mut self, input: &mut BufferQueue) -> Result<(), Root<HTMLScriptElement>> { + fn feed(&mut self, input: &mut BufferQueue) -> Result<(), DomRoot<HTMLScriptElement>> { match *self { Tokenizer::Html(ref mut tokenizer) => tokenizer.feed(input), + Tokenizer::AsyncHtml(ref mut tokenizer) => tokenizer.feed(input), Tokenizer::Xml(ref mut tokenizer) => tokenizer.feed(input), } } @@ -452,6 +701,7 @@ impl Tokenizer { fn end(&mut self) { match *self { Tokenizer::Html(ref mut tokenizer) => tokenizer.end(), + Tokenizer::AsyncHtml(ref mut tokenizer) => tokenizer.end(), Tokenizer::Xml(ref mut tokenizer) => tokenizer.end(), } } @@ -459,6 +709,7 @@ impl Tokenizer { fn url(&self) -> &ServoUrl { match *self { Tokenizer::Html(ref tokenizer) => tokenizer.url(), + Tokenizer::AsyncHtml(ref tokenizer) => tokenizer.url(), Tokenizer::Xml(ref tokenizer) => tokenizer.url(), } } @@ -466,6 +717,7 @@ impl Tokenizer { fn set_plaintext_state(&mut self) { match *self { Tokenizer::Html(ref mut tokenizer) => tokenizer.set_plaintext_state(), + Tokenizer::AsyncHtml(ref mut tokenizer) => tokenizer.set_plaintext_state(), Tokenizer::Xml(_) => unimplemented!(), } } @@ -473,6 +725,7 @@ impl Tokenizer { fn profiler_category(&self) -> ProfilerCategory { match *self { Tokenizer::Html(_) => ProfilerCategory::ScriptParseHTML, + Tokenizer::AsyncHtml(_) => ProfilerCategory::ScriptParseHTML, Tokenizer::Xml(_) => ProfilerCategory::ScriptParseXML, } } @@ -489,6 +742,10 @@ pub struct ParserContext { id: PipelineId, /// The URL for this document. url: ServoUrl, + /// timing data for this resource + resource_timing: ResourceFetchTiming, + /// pushed entry index + pushed_entry_index: Option<usize>, } impl ParserContext { @@ -498,6 +755,8 @@ impl ParserContext { is_synthesized_document: false, id: id, url: url, + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Navigation), + pushed_entry_index: None, } } } @@ -511,15 +770,13 @@ impl FetchResponseListener for ParserContext { let mut ssl_error = None; let mut network_error = None; let metadata = match meta_result { - Ok(meta) => { - Some(match meta { - FetchMetadata::Unfiltered(m) => m, - FetchMetadata::Filtered { unsafe_, .. } => unsafe_, - }) - }, - Err(NetworkError::SslValidation(url, reason)) => { - ssl_error = Some(reason); - let mut meta = Metadata::default(url); + Ok(meta) => Some(match meta { + FetchMetadata::Unfiltered(m) => m, + FetchMetadata::Filtered { unsafe_, .. } => unsafe_, + }), + Err(NetworkError::SslValidation(reason, cert_bytes)) => { + ssl_error = Some((reason, cert_bytes)); + let mut meta = Metadata::default(self.url.clone()); let mime: Option<Mime> = "text/html".parse().ok(); meta.set_content_type(mime.as_ref()); Some(meta) @@ -533,7 +790,36 @@ impl FetchResponseListener for ParserContext { }, Err(_) => None, }; - let content_type = metadata.clone().and_then(|meta| meta.content_type).map(Serde::into_inner); + let content_type: Option<Mime> = metadata + .clone() + .and_then(|meta| meta.content_type) + .map(Serde::into_inner) + .map(Into::into); + + // https://www.w3.org/TR/CSP/#initialize-document-csp + // TODO: Implement step 1 (local scheme special case) + let csp_list = metadata.as_ref().and_then(|m| { + let h = m.headers.as_ref()?; + let mut csp = h.get_all("content-security-policy").iter(); + // This silently ignores the CSP if it contains invalid Unicode. + // We should probably report an error somewhere. + let c = csp.next().and_then(|c| c.to_str().ok())?; + let mut csp_list = CspList::parse( + c, + csp::PolicySource::Header, + csp::PolicyDisposition::Enforce, + ); + for c in csp { + let c = c.to_str().ok()?; + csp_list.append(CspList::parse( + c, + csp::PolicySource::Header, + csp::PolicyDisposition::Enforce, + )); + } + Some(csp_list) + }); + let parser = match ScriptThread::page_headers_available(&self.id, metadata) { Some(parser) => parser, None => return, @@ -542,61 +828,72 @@ impl FetchResponseListener for ParserContext { return; } + parser.document.set_csp_list(csp_list); + self.parser = Some(Trusted::new(&*parser)); + self.submit_resource_timing(); + match content_type { - Some(ContentType(Mime(TopLevel::Image, _, _))) => { + Some(ref mime) if mime.type_() == mime::IMAGE => { self.is_synthesized_document = true; let page = "<html><body></body></html>".into(); - parser.push_input_chunk(page); + parser.push_string_input_chunk(page); parser.parse_sync(); let doc = &parser.document; - let doc_body = Root::upcast::<Node>(doc.GetBody().unwrap()); + let doc_body = DomRoot::upcast::<Node>(doc.GetBody().unwrap()); let img = HTMLImageElement::new(local_name!("img"), None, doc); - img.SetSrc(DOMString::from(self.url.to_string())); - doc_body.AppendChild(&Root::upcast::<Node>(img)).expect("Appending failed"); - + img.SetSrc(USVString(self.url.to_string())); + doc_body + .AppendChild(&DomRoot::upcast::<Node>(img)) + .expect("Appending failed"); }, - Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => { + Some(ref mime) if mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN => { // https://html.spec.whatwg.org/multipage/#read-text let page = "<pre>\n".into(); - parser.push_input_chunk(page); + parser.push_string_input_chunk(page); parser.parse_sync(); parser.tokenizer.borrow_mut().set_plaintext_state(); }, - Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, _))) => { + Some(ref mime) if mime.type_() == mime::TEXT && mime.subtype() == mime::HTML => { // Handle text/html - if let Some(reason) = ssl_error { + if let Some((reason, bytes)) = ssl_error { self.is_synthesized_document = true; - let page_bytes = read_resource_file("badcert.html").unwrap(); - let page = String::from_utf8(page_bytes).unwrap(); + let page = resources::read_string(Resource::BadCertHTML); let page = page.replace("${reason}", &reason); - parser.push_input_chunk(page); + let page = + page.replace("${bytes}", std::str::from_utf8(&bytes).unwrap_or_default()); + let page = + page.replace("${secret}", &net_traits::PRIVILEGED_SECRET.to_string()); + parser.push_string_input_chunk(page); parser.parse_sync(); } if let Some(reason) = network_error { self.is_synthesized_document = true; - let page_bytes = read_resource_file("neterror.html").unwrap(); - let page = String::from_utf8(page_bytes).unwrap(); + let page = resources::read_string(Resource::NetErrorHTML); let page = page.replace("${reason}", &reason); - parser.push_input_chunk(page); + parser.push_string_input_chunk(page); parser.parse_sync(); } }, - Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) => {}, // Handle text/xml - Some(ContentType(Mime(toplevel, sublevel, _))) => { - if toplevel.as_str() == "application" && sublevel.as_str() == "xhtml+xml" { - // Handle xhtml (application/xhtml+xml). - return; - } - + // Handle text/xml, application/xml + Some(ref mime) + if (mime.type_() == mime::TEXT && mime.subtype() == mime::XML) || + (mime.type_() == mime::APPLICATION && mime.subtype() == mime::XML) => {}, + Some(ref mime) + if mime.type_() == mime::APPLICATION && + mime.subtype().as_str() == "xhtml" && + mime.suffix() == Some(mime::XML) => {}, // Handle xhtml (application/xhtml+xml) + Some(ref mime) => { // Show warning page for unknown mime types. - let page = format!("<html><body><p>Unknown content type ({}/{}).</p></body></html>", - toplevel.as_str(), - sublevel.as_str()); + let page = format!( + "<html><body><p>Unknown content type ({}/{}).</p></body></html>", + mime.type_().as_str(), + mime.subtype().as_str() + ); self.is_synthesized_document = true; - parser.push_input_chunk(page); + parser.push_string_input_chunk(page); parser.parse_sync(); }, None => { @@ -610,8 +907,6 @@ impl FetchResponseListener for ParserContext { if self.is_synthesized_document { return; } - // FIXME: use Vec<u8> (html5ever #34) - let data = UTF_8.decode(&payload, DecoderTrap::Replace).unwrap(); let parser = match self.parser.as_ref() { Some(parser) => parser.root(), None => return, @@ -619,10 +914,13 @@ impl FetchResponseListener for ParserContext { if parser.aborted.get() { return; } - parser.parse_chunk(data); + parser.parse_bytes_chunk(payload); } - fn process_response_eof(&mut self, status: Result<(), NetworkError>) { + // This method is called via script_thread::handle_fetch_eof, so we must call + // submit_resource_timing in this function + // Resource listeners are called via net_traits::Action::process, which handles submission for them + fn process_response_eof(&mut self, status: Result<ResourceFetchTiming, NetworkError>) { let parser = match self.parser.as_ref() { Some(parser) => parser.root(), None => return, @@ -631,15 +929,61 @@ impl FetchResponseListener for ParserContext { return; } - if let Err(err) = status { + match status { + // are we throwing this away or can we use it? + Ok(_) => (), // TODO(Savago): we should send a notification to callers #5463. - debug!("Failed to load page URL {}, error: {:?}", self.url, err); + Err(err) => debug!("Failed to load page URL {}, error: {:?}", self.url, err), } + parser + .document + .set_redirect_count(self.resource_timing.redirect_count); + parser.last_chunk_received.set(true); if !parser.suspended.get() { parser.parse_sync(); } + + //TODO only update if this is the current document resource + if let Some(pushed_index) = self.pushed_entry_index { + let document = &parser.document; + let performance_entry = + PerformanceNavigationTiming::new(&document.global(), 0, 0, &document); + document + .global() + .performance() + .update_entry(pushed_index, performance_entry.upcast::<PerformanceEntry>()); + } + } + + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } + + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } + + // store a PerformanceNavigationTiming entry in the globalscope's Performance buffer + fn submit_resource_timing(&mut self) { + let parser = match self.parser.as_ref() { + Some(parser) => parser.root(), + None => return, + }; + if parser.aborted.get() { + return; + } + + let document = &parser.document; + + //TODO nav_start and nav_start_precise + let performance_entry = + PerformanceNavigationTiming::new(&document.global(), 0, 0, &document); + self.pushed_entry_index = document + .global() + .performance() + .queue_entry(performance_entry.upcast::<PerformanceEntry>()); } } @@ -651,16 +995,33 @@ pub struct FragmentContext<'a> { } #[allow(unrooted_must_root)] -fn insert(parent: &Node, reference_child: Option<&Node>, child: NodeOrText<JS<Node>>) { +fn insert( + parent: &Node, + reference_child: Option<&Node>, + child: NodeOrText<Dom<Node>>, + parsing_algorithm: ParsingAlgorithm, +) { match child { NodeOrText::AppendNode(n) => { + // https://html.spec.whatwg.org/multipage/#insert-a-foreign-element + // applies if this is an element; if not, it may be + // https://html.spec.whatwg.org/multipage/#insert-a-comment + let element_in_non_fragment = + parsing_algorithm != ParsingAlgorithm::Fragment && n.is::<Element>(); + if element_in_non_fragment { + ScriptThread::push_new_element_queue(); + } parent.InsertBefore(&n, reference_child).unwrap(); + if element_in_non_fragment { + ScriptThread::pop_current_element_queue(); + } }, NodeOrText::AppendText(t) => { + // https://html.spec.whatwg.org/multipage/#insert-a-character let text = reference_child .and_then(Node::GetPreviousSibling) .or_else(|| parent.GetLastChild()) - .and_then(Root::downcast::<Text>); + .and_then(DomRoot::downcast::<Text>); if let Some(text) = text { text.upcast::<CharacterData>().append_data(&t); @@ -671,3 +1032,382 @@ fn insert(parent: &Node, reference_child: Option<&Node>, child: NodeOrText<JS<No }, } } + +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct Sink { + base_url: ServoUrl, + document: Dom<Document>, + current_line: u64, + script: MutNullableDom<HTMLScriptElement>, + parsing_algorithm: ParsingAlgorithm, +} + +impl Sink { + fn same_tree(&self, x: &Dom<Node>, y: &Dom<Node>) -> bool { + let x = x.downcast::<Element>().expect("Element node expected"); + let y = y.downcast::<Element>().expect("Element node expected"); + + x.is_in_same_home_subtree(y) + } + + fn has_parent_node(&self, node: &Dom<Node>) -> bool { + node.GetParentNode().is_some() + } +} + +#[allow(unrooted_must_root)] // FIXME: really? +impl TreeSink for Sink { + type Output = Self; + fn finish(self) -> Self { + self + } + + type Handle = Dom<Node>; + + fn get_document(&mut self) -> Dom<Node> { + Dom::from_ref(self.document.upcast()) + } + + fn get_template_contents(&mut self, target: &Dom<Node>) -> Dom<Node> { + let template = target + .downcast::<HTMLTemplateElement>() + .expect("tried to get template contents of non-HTMLTemplateElement in HTML parsing"); + Dom::from_ref(template.Content().upcast()) + } + + fn same_node(&self, x: &Dom<Node>, y: &Dom<Node>) -> bool { + x == y + } + + fn elem_name<'a>(&self, target: &'a Dom<Node>) -> ExpandedName<'a> { + let elem = target + .downcast::<Element>() + .expect("tried to get name of non-Element in HTML parsing"); + ExpandedName { + ns: elem.namespace(), + local: elem.local_name(), + } + } + + fn create_element( + &mut self, + name: QualName, + attrs: Vec<Attribute>, + _flags: ElementFlags, + ) -> Dom<Node> { + let attrs = attrs + .into_iter() + .map(|attr| ElementAttribute::new(attr.name, DOMString::from(String::from(attr.value)))) + .collect(); + let element = create_element_for_token( + name, + attrs, + &*self.document, + ElementCreator::ParserCreated(self.current_line), + self.parsing_algorithm, + ); + Dom::from_ref(element.upcast()) + } + + fn create_comment(&mut self, text: StrTendril) -> Dom<Node> { + let comment = Comment::new(DOMString::from(String::from(text)), &*self.document); + Dom::from_ref(comment.upcast()) + } + + fn create_pi(&mut self, target: StrTendril, data: StrTendril) -> Dom<Node> { + let doc = &*self.document; + let pi = ProcessingInstruction::new( + DOMString::from(String::from(target)), + DOMString::from(String::from(data)), + doc, + ); + Dom::from_ref(pi.upcast()) + } + + fn associate_with_form( + &mut self, + target: &Dom<Node>, + form: &Dom<Node>, + nodes: (&Dom<Node>, Option<&Dom<Node>>), + ) { + let (element, prev_element) = nodes; + let tree_node = prev_element.map_or(element, |prev| { + if self.has_parent_node(element) { + element + } else { + prev + } + }); + if !self.same_tree(tree_node, form) { + return; + } + + let node = target; + let form = DomRoot::downcast::<HTMLFormElement>(DomRoot::from_ref(&**form)) + .expect("Owner must be a form element"); + + let elem = node.downcast::<Element>(); + let control = elem.and_then(|e| e.as_maybe_form_control()); + + if let Some(control) = control { + control.set_form_owner_from_parser(&form); + } else { + // TODO remove this code when keygen is implemented. + assert_eq!( + node.NodeName(), + "KEYGEN", + "Unknown form-associatable element" + ); + } + } + + fn append_before_sibling(&mut self, sibling: &Dom<Node>, new_node: NodeOrText<Dom<Node>>) { + let parent = sibling + .GetParentNode() + .expect("append_before_sibling called on node without parent"); + + insert(&parent, Some(&*sibling), new_node, self.parsing_algorithm); + } + + fn parse_error(&mut self, msg: Cow<'static, str>) { + debug!("Parse error: {}", msg); + } + + fn set_quirks_mode(&mut self, mode: QuirksMode) { + let mode = match mode { + QuirksMode::Quirks => ServoQuirksMode::Quirks, + QuirksMode::LimitedQuirks => ServoQuirksMode::LimitedQuirks, + QuirksMode::NoQuirks => ServoQuirksMode::NoQuirks, + }; + self.document.set_quirks_mode(mode); + } + + fn append(&mut self, parent: &Dom<Node>, child: NodeOrText<Dom<Node>>) { + insert(&parent, None, child, self.parsing_algorithm); + } + + fn append_based_on_parent_node( + &mut self, + elem: &Dom<Node>, + prev_elem: &Dom<Node>, + child: NodeOrText<Dom<Node>>, + ) { + if self.has_parent_node(elem) { + self.append_before_sibling(elem, child); + } else { + self.append(prev_elem, child); + } + } + + fn append_doctype_to_document( + &mut self, + name: StrTendril, + public_id: StrTendril, + system_id: StrTendril, + ) { + let doc = &*self.document; + let doctype = DocumentType::new( + DOMString::from(String::from(name)), + Some(DOMString::from(String::from(public_id))), + Some(DOMString::from(String::from(system_id))), + doc, + ); + doc.upcast::<Node>() + .AppendChild(doctype.upcast()) + .expect("Appending failed"); + } + + fn add_attrs_if_missing(&mut self, target: &Dom<Node>, attrs: Vec<Attribute>) { + let elem = target + .downcast::<Element>() + .expect("tried to set attrs on non-Element in HTML parsing"); + for attr in attrs { + elem.set_attribute_from_parser( + attr.name, + DOMString::from(String::from(attr.value)), + None, + ); + } + } + + fn remove_from_parent(&mut self, target: &Dom<Node>) { + if let Some(ref parent) = target.GetParentNode() { + parent.RemoveChild(&*target).unwrap(); + } + } + + fn mark_script_already_started(&mut self, node: &Dom<Node>) { + let script = node.downcast::<HTMLScriptElement>(); + script.map(|script| script.set_already_started(true)); + } + + fn complete_script(&mut self, node: &Dom<Node>) -> NextParserState { + if let Some(script) = node.downcast() { + self.script.set(Some(script)); + NextParserState::Suspend + } else { + NextParserState::Continue + } + } + + fn reparent_children(&mut self, node: &Dom<Node>, new_parent: &Dom<Node>) { + while let Some(ref child) = node.GetFirstChild() { + new_parent.AppendChild(&child).unwrap(); + } + } + + /// <https://html.spec.whatwg.org/multipage/#html-integration-point> + /// Specifically, the <annotation-xml> cases. + fn is_mathml_annotation_xml_integration_point(&self, handle: &Dom<Node>) -> bool { + let elem = handle.downcast::<Element>().unwrap(); + elem.get_attribute(&ns!(), &local_name!("encoding")) + .map_or(false, |attr| { + attr.value().eq_ignore_ascii_case("text/html") || + attr.value().eq_ignore_ascii_case("application/xhtml+xml") + }) + } + + fn set_current_line(&mut self, line_number: u64) { + self.current_line = line_number; + } + + fn pop(&mut self, node: &Dom<Node>) { + let node = DomRoot::from_ref(&**node); + vtable_for(&node).pop(); + } +} + +/// https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token +fn create_element_for_token( + name: QualName, + attrs: Vec<ElementAttribute>, + document: &Document, + creator: ElementCreator, + parsing_algorithm: ParsingAlgorithm, +) -> DomRoot<Element> { + // Step 3. + let is = attrs + .iter() + .find(|attr| attr.name.local.eq_str_ignore_ascii_case("is")) + .map(|attr| LocalName::from(&*attr.value)); + + // Step 4. + let definition = document.lookup_custom_element_definition(&name.ns, &name.local, is.as_ref()); + + // Step 5. + let will_execute_script = + definition.is_some() && parsing_algorithm != ParsingAlgorithm::Fragment; + + // Step 6. + if will_execute_script { + // Step 6.1. + document.increment_throw_on_dynamic_markup_insertion_counter(); + // Step 6.2 + if is_execution_stack_empty() { + document + .window() + .upcast::<GlobalScope>() + .perform_a_microtask_checkpoint(); + } + // Step 6.3 + ScriptThread::push_new_element_queue() + } + + // Step 7. + let creation_mode = if will_execute_script { + CustomElementCreationMode::Synchronous + } else { + CustomElementCreationMode::Asynchronous + }; + + let element = Element::create(name, is, document, creator, creation_mode); + + // https://html.spec.whatwg.org/multipage#the-input-element:value-sanitization-algorithm-3 + // says to invoke sanitization "when an input element is first created"; + // however, since sanitization requires content attributes to function, + // it can't mean that literally. + // Indeed, to make sanitization work correctly, we need to _not_ sanitize + // until after all content attributes have been added + + let maybe_input = element.downcast::<HTMLInputElement>(); + if let Some(input) = maybe_input { + input.disable_sanitization(); + } + + // Step 8 + for attr in attrs { + element.set_attribute_from_parser(attr.name, attr.value, None); + } + + // _now_ we can sanitize (and we sanitize now even if the "value" + // attribute isn't present!) + if let Some(input) = maybe_input { + input.enable_sanitization(); + } + + // Step 9. + if will_execute_script { + // Steps 9.1 - 9.2. + ScriptThread::pop_current_element_queue(); + // Step 9.3. + document.decrement_throw_on_dynamic_markup_insertion_counter(); + } + + // TODO: Step 10. + // TODO: Step 11. + + // Step 12 is handled in `associate_with_form`. + + // Step 13. + element +} + +#[derive(JSTraceable, MallocSizeOf)] +struct NetworkDecoder { + #[ignore_malloc_size_of = "Defined in tendril"] + decoder: LossyDecoder<NetworkSink>, +} + +impl NetworkDecoder { + fn new(encoding: &'static Encoding) -> Self { + Self { + decoder: LossyDecoder::new_encoding_rs(encoding, Default::default()), + } + } + + fn decode(&mut self, chunk: Vec<u8>) -> StrTendril { + self.decoder.process(ByteTendril::from(&*chunk)); + mem::replace( + &mut self.decoder.inner_sink_mut().output, + Default::default(), + ) + } + + fn finish(self) -> StrTendril { + self.decoder.finish() + } +} + +#[derive(Default, JSTraceable)] +struct NetworkSink { + output: StrTendril, +} + +impl TendrilSink<UTF8> for NetworkSink { + type Output = StrTendril; + + fn process(&mut self, t: StrTendril) { + if self.output.is_empty() { + self.output = t; + } else { + self.output.push_tendril(&t); + } + } + + fn error(&mut self, _desc: Cow<'static, str>) {} + + fn finish(self) -> Self::Output { + self.output + } +} diff --git a/components/script/dom/servoparser/prefetch.rs b/components/script/dom/servoparser/prefetch.rs new file mode 100644 index 00000000000..4de7e765b0c --- /dev/null +++ b/components/script/dom/servoparser/prefetch.rs @@ -0,0 +1,228 @@ +/* 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 crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::document::{determine_policy_for_token, Document}; +use crate::dom::htmlimageelement::{image_fetch_request, FromPictureOrSrcSet}; +use crate::dom::htmlscriptelement::script_fetch_request; +use crate::script_module::ScriptFetchOptions; +use crate::stylesheet_loader::stylesheet_fetch_request; +use html5ever::buffer_queue::BufferQueue; +use html5ever::tokenizer::states::RawKind; +use html5ever::tokenizer::Tag; +use html5ever::tokenizer::TagKind; +use html5ever::tokenizer::Token; +use html5ever::tokenizer::TokenSink; +use html5ever::tokenizer::TokenSinkResult; +use html5ever::tokenizer::Tokenizer as HtmlTokenizer; +use html5ever::tokenizer::TokenizerResult; +use html5ever::Attribute; +use html5ever::LocalName; +use js::jsapi::JSTracer; +use msg::constellation_msg::PipelineId; +use net_traits::request::CorsSettings; +use net_traits::request::CredentialsMode; +use net_traits::request::ParserMetadata; +use net_traits::request::Referrer; +use net_traits::CoreResourceMsg; +use net_traits::FetchChannels; +use net_traits::IpcSend; +use net_traits::ReferrerPolicy; +use net_traits::ResourceThreads; +use servo_url::ImmutableOrigin; +use servo_url::ServoUrl; + +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct Tokenizer { + #[ignore_malloc_size_of = "Defined in html5ever"] + inner: HtmlTokenizer<PrefetchSink>, +} + +#[allow(unsafe_code)] +unsafe impl JSTraceable for HtmlTokenizer<PrefetchSink> { + unsafe fn trace(&self, trc: *mut JSTracer) { + self.sink.trace(trc) + } +} + +impl Tokenizer { + pub fn new(document: &Document) -> Self { + let sink = PrefetchSink { + origin: document.origin().immutable().clone(), + pipeline_id: document.global().pipeline_id(), + base_url: None, + document_url: document.url(), + referrer: document.global().get_referrer(), + referrer_policy: document.get_referrer_policy(), + resource_threads: document.loader().resource_threads().clone(), + // Initially we set prefetching to false, and only set it + // true after the first script tag, since that is what will + // block the main parser. + prefetching: false, + }; + let options = Default::default(); + let inner = HtmlTokenizer::new(sink, options); + Tokenizer { inner } + } + + pub fn feed(&mut self, input: &mut BufferQueue) { + while let TokenizerResult::Script(PrefetchHandle) = self.inner.feed(input) {} + } +} + +#[derive(JSTraceable)] +struct PrefetchSink { + origin: ImmutableOrigin, + pipeline_id: PipelineId, + document_url: ServoUrl, + base_url: Option<ServoUrl>, + referrer: Referrer, + referrer_policy: Option<ReferrerPolicy>, + resource_threads: ResourceThreads, + prefetching: bool, +} + +/// The prefetch tokenizer produces trivial results +struct PrefetchHandle; + +impl TokenSink for PrefetchSink { + type Handle = PrefetchHandle; + fn process_token( + &mut self, + token: Token, + _line_number: u64, + ) -> TokenSinkResult<PrefetchHandle> { + let tag = match token { + Token::TagToken(ref tag) => tag, + _ => return TokenSinkResult::Continue, + }; + match (tag.kind, &tag.name) { + (TagKind::StartTag, &local_name!("script")) if self.prefetching => { + if let Some(url) = self.get_url(tag, local_name!("src")) { + debug!("Prefetch script {}", url); + let cors_setting = self.get_cors_settings(tag, local_name!("crossorigin")); + let integrity_metadata = self + .get_attr(tag, local_name!("integrity")) + .map(|attr| String::from(&attr.value)) + .unwrap_or_default(); + let request = script_fetch_request( + url, + cors_setting, + self.origin.clone(), + self.pipeline_id, + ScriptFetchOptions { + referrer: self.referrer.clone(), + referrer_policy: self.referrer_policy, + integrity_metadata, + cryptographic_nonce: String::new(), + credentials_mode: CredentialsMode::CredentialsSameOrigin, + parser_metadata: ParserMetadata::ParserInserted, + }, + ); + let _ = self + .resource_threads + .send(CoreResourceMsg::Fetch(request, FetchChannels::Prefetch)); + } + TokenSinkResult::RawData(RawKind::ScriptData) + }, + (TagKind::StartTag, &local_name!("img")) if self.prefetching => { + if let Some(url) = self.get_url(tag, local_name!("src")) { + debug!("Prefetch {} {}", tag.name, url); + let request = image_fetch_request( + url, + self.origin.clone(), + self.referrer.clone(), + self.pipeline_id, + self.get_cors_settings(tag, local_name!("crossorigin")), + self.get_referrer_policy(tag, local_name!("referrerpolicy")), + FromPictureOrSrcSet::No, + ); + let _ = self + .resource_threads + .send(CoreResourceMsg::Fetch(request, FetchChannels::Prefetch)); + } + TokenSinkResult::Continue + }, + (TagKind::StartTag, &local_name!("link")) if self.prefetching => { + if let Some(rel) = self.get_attr(tag, local_name!("rel")) { + if rel.value.eq_ignore_ascii_case("stylesheet") { + if let Some(url) = self.get_url(tag, local_name!("href")) { + debug!("Prefetch {} {}", tag.name, url); + let cors_setting = + self.get_cors_settings(tag, local_name!("crossorigin")); + let referrer_policy = + self.get_referrer_policy(tag, local_name!("referrerpolicy")); + let integrity_metadata = self + .get_attr(tag, local_name!("integrity")) + .map(|attr| String::from(&attr.value)) + .unwrap_or_default(); + let request = stylesheet_fetch_request( + url, + cors_setting, + self.origin.clone(), + self.pipeline_id, + self.referrer.clone(), + referrer_policy, + integrity_metadata, + ); + let _ = self + .resource_threads + .send(CoreResourceMsg::Fetch(request, FetchChannels::Prefetch)); + } + } + } + TokenSinkResult::Continue + }, + (TagKind::StartTag, &local_name!("script")) => { + TokenSinkResult::RawData(RawKind::ScriptData) + }, + (TagKind::EndTag, &local_name!("script")) => { + // After the first script tag, the main parser is blocked, so it's worth prefetching. + self.prefetching = true; + TokenSinkResult::Script(PrefetchHandle) + }, + (TagKind::StartTag, &local_name!("base")) => { + if let Some(url) = self.get_url(tag, local_name!("href")) { + if self.base_url.is_none() { + debug!("Setting base {}", url); + self.base_url = Some(url); + } + } + TokenSinkResult::Continue + }, + _ => TokenSinkResult::Continue, + } + } +} + +impl PrefetchSink { + fn get_attr<'a>(&'a self, tag: &'a Tag, name: LocalName) -> Option<&'a Attribute> { + tag.attrs.iter().find(|attr| attr.name.local == name) + } + + fn get_url(&self, tag: &Tag, name: LocalName) -> Option<ServoUrl> { + let attr = self.get_attr(tag, name)?; + let base = self.base_url.as_ref().unwrap_or(&self.document_url); + ServoUrl::parse_with_base(Some(base), &attr.value).ok() + } + + fn get_referrer_policy(&self, tag: &Tag, name: LocalName) -> Option<ReferrerPolicy> { + self.get_attr(tag, name) + .and_then(|attr| determine_policy_for_token(&*attr.value)) + .or(self.referrer_policy) + } + + fn get_cors_settings(&self, tag: &Tag, name: LocalName) -> Option<CorsSettings> { + let crossorigin = self.get_attr(tag, name)?; + if crossorigin.value.eq_ignore_ascii_case("anonymous") { + Some(CorsSettings::Anonymous) + } else if crossorigin.value.eq_ignore_ascii_case("use-credentials") { + Some(CorsSettings::UseCredentials) + } else { + None + } + } +} diff --git a/components/script/dom/servoparser/xml.rs b/components/script/dom/servoparser/xml.rs index 1ef5f882db0..ac900d61ce0 100644 --- a/components/script/dom/servoparser/xml.rs +++ b/components/script/dom/servoparser/xml.rs @@ -1,69 +1,48 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #![allow(unrooted_must_root)] -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::bindings::trace::JSTraceable; -use dom::comment::Comment; -use dom::document::Document; -use dom::documenttype::DocumentType; -use dom::element::{Element, ElementCreator}; -use dom::htmlscriptelement::HTMLScriptElement; -use dom::node::Node; -use dom::processinginstruction::ProcessingInstruction; -use dom::virtualmethods::vtable_for; -use html5ever::tokenizer::buffer_queue::BufferQueue; -use html5ever::tree_builder::{NodeOrText as H5eNodeOrText}; -use html5ever_atoms::{Prefix, QualName}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::document::Document; +use crate::dom::htmlscriptelement::HTMLScriptElement; +use crate::dom::node::Node; +use crate::dom::servoparser::{ParsingAlgorithm, Sink}; use js::jsapi::JSTracer; use servo_url::ServoUrl; -use std::borrow::Cow; -use xml5ever::tendril::StrTendril; -use xml5ever::tokenizer::{Attribute, QName, XmlTokenizer}; -use xml5ever::tree_builder::{NextParserState, NodeOrText}; -use xml5ever::tree_builder::{Tracer as XmlTracer, TreeSink, XmlTreeBuilder}; +use xml5ever::buffer_queue::BufferQueue; +use xml5ever::tokenizer::XmlTokenizer; +use xml5ever::tree_builder::{Tracer as XmlTracer, XmlTreeBuilder}; -#[derive(HeapSizeOf, JSTraceable)] -#[must_root] +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] pub struct Tokenizer { - #[ignore_heap_size_of = "Defined in xml5ever"] - inner: XmlTokenizer<XmlTreeBuilder<JS<Node>, Sink>>, + #[ignore_malloc_size_of = "Defined in xml5ever"] + inner: XmlTokenizer<XmlTreeBuilder<Dom<Node>, Sink>>, } impl Tokenizer { pub fn new(document: &Document, url: ServoUrl) -> Self { let sink = Sink { base_url: url, - document: JS::from_ref(document), + document: Dom::from_ref(document), + current_line: 1, script: Default::default(), + parsing_algorithm: ParsingAlgorithm::Normal, }; let tb = XmlTreeBuilder::new(sink, Default::default()); let tok = XmlTokenizer::new(tb, Default::default()); - Tokenizer { - inner: tok, - } + Tokenizer { inner: tok } } - pub fn feed(&mut self, input: &mut BufferQueue) -> Result<(), Root<HTMLScriptElement>> { - if !input.is_empty() { - while let Some(chunk) = input.pop_front() { - self.inner.feed(chunk); - if let Some(script) = self.inner.sink().sink().script.take() { - return Err(script); - } - } - } else { - self.inner.run(); - if let Some(script) = self.inner.sink().sink().script.take() { - return Err(script); - } + pub fn feed(&mut self, input: &mut BufferQueue) -> Result<(), DomRoot<HTMLScriptElement>> { + self.inner.run(input); + if let Some(script) = self.inner.sink.sink.script.take() { + return Err(script); } Ok(()) } @@ -73,134 +52,28 @@ impl Tokenizer { } pub fn url(&self) -> &ServoUrl { - &self.inner.sink().sink().base_url + &self.inner.sink.sink.base_url } } #[allow(unsafe_code)] -unsafe impl JSTraceable for XmlTokenizer<XmlTreeBuilder<JS<Node>, Sink>> { +unsafe impl JSTraceable for XmlTokenizer<XmlTreeBuilder<Dom<Node>, Sink>> { unsafe fn trace(&self, trc: *mut JSTracer) { struct Tracer(*mut JSTracer); let tracer = Tracer(trc); impl XmlTracer for Tracer { - type Handle = JS<Node>; + type Handle = Dom<Node>; #[allow(unrooted_must_root)] - fn trace_handle(&self, node: JS<Node>) { - unsafe { node.trace(self.0); } + fn trace_handle(&self, node: &Dom<Node>) { + unsafe { + node.trace(self.0); + } } } - let tree_builder = self.sink(); + let tree_builder = &self.sink; tree_builder.trace_handles(&tracer); - tree_builder.sink().trace(trc); - } -} - -#[derive(JSTraceable, HeapSizeOf)] -#[must_root] -struct Sink { - base_url: ServoUrl, - document: JS<Document>, - script: MutNullableJS<HTMLScriptElement>, -} - -impl TreeSink for Sink { - type Output = Self; - type Handle = JS<Node>; - - fn finish(self) -> Self { - self - } - - fn parse_error(&mut self, msg: Cow<'static, str>) { - debug!("Parse error: {}", msg); - } - - fn get_document(&mut self) -> JS<Node> { - JS::from_ref(self.document.upcast()) - } - - fn elem_name(&self, target: &JS<Node>) -> QName { - let elem = target.downcast::<Element>() - .expect("tried to get name of non-Element in XML parsing"); - QName { - prefix: elem.prefix().map_or(namespace_prefix!(""), |p| Prefix::from(&**p)), - namespace_url: elem.namespace().clone(), - local: elem.local_name().clone(), - } - } - - fn create_element(&mut self, name: QName, attrs: Vec<Attribute>) - -> JS<Node> { - let prefix = if name.prefix == namespace_prefix!("") { None } else { Some(name.prefix) }; - let name = QualName { - ns: name.namespace_url, - local: name.local, - }; - //TODO: Add ability to track lines to API of xml5ever - let elem = Element::create(name, prefix, &*self.document, - ElementCreator::ParserCreated(1)); - - for attr in attrs { - let name = QualName { - ns: attr.name.namespace_url, - local: attr.name.local, - }; - elem.set_attribute_from_parser(name, DOMString::from(String::from(attr.value)), None); - } - - JS::from_ref(elem.upcast()) - } - - fn create_comment(&mut self, text: StrTendril) -> JS<Node> { - let comment = Comment::new(DOMString::from(String::from(text)), &*self.document); - JS::from_ref(comment.upcast()) - } - - fn append(&mut self, parent: JS<Node>, child: NodeOrText<JS<Node>>) { - let child = match child { - NodeOrText::AppendNode(n) => H5eNodeOrText::AppendNode(n), - NodeOrText::AppendText(s) => H5eNodeOrText::AppendText(s), - }; - super::insert(&*parent, None, child); - } - - fn append_doctype_to_document(&mut self, name: StrTendril, public_id: StrTendril, - system_id: StrTendril) { - let doc = &*self.document; - let doctype = DocumentType::new( - DOMString::from(String::from(name)), Some(DOMString::from(String::from(public_id))), - Some(DOMString::from(String::from(system_id))), doc); - doc.upcast::<Node>().AppendChild(doctype.upcast()).expect("Appending failed"); - } - - fn create_pi(&mut self, target: StrTendril, data: StrTendril) -> JS<Node> { - let doc = &*self.document; - let pi = ProcessingInstruction::new( - DOMString::from(String::from(target)), DOMString::from(String::from(data)), - doc); - JS::from_ref(pi.upcast()) - } - - fn mark_script_already_started(&mut self, node: Self::Handle) { - let script = node.downcast::<HTMLScriptElement>(); - if let Some(script) = script { - script.set_already_started(true); - } - } - - fn complete_script(&mut self, node: Self::Handle) -> NextParserState { - if let Some(script) = node.downcast() { - self.script.set(Some(script)); - NextParserState::Suspend - } else { - NextParserState::Continue - } - } - - fn pop(&mut self, node: Self::Handle) { - let node = Root::from_ref(&*node); - vtable_for(&node).pop(); + tree_builder.sink.trace(trc); } } diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs new file mode 100644 index 00000000000..c70c13d8cef --- /dev/null +++ b/components/script/dom/shadowroot.rs @@ -0,0 +1,289 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootBinding::ShadowRootMethods; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMode; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::document::Document; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::documentorshadowroot::{DocumentOrShadowRoot, StyleSheetInDocument}; +use crate::dom::element::Element; +use crate::dom::node::{Node, NodeDamage, NodeFlags, ShadowIncluding, UnbindContext}; +use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner}; +use crate::dom::window::Window; +use crate::stylesheet_set::StylesheetSetRef; +use dom_struct::dom_struct; +use selectors::context::QuirksMode; +use servo_arc::Arc; +use servo_atoms::Atom; +use style::author_styles::AuthorStyles; +use style::dom::TElement; +use style::media_queries::Device; +use style::shared_lock::SharedRwLockReadGuard; +use style::stylesheets::Stylesheet; +use style::stylist::CascadeData; + +/// Whether a shadow root hosts an User Agent widget. +#[derive(JSTraceable, MallocSizeOf, PartialEq)] +pub enum IsUserAgentWidget { + No, + Yes, +} + +// https://dom.spec.whatwg.org/#interface-shadowroot +#[dom_struct] +pub struct ShadowRoot { + document_fragment: DocumentFragment, + document_or_shadow_root: DocumentOrShadowRoot, + document: Dom<Document>, + host: MutNullableDom<Element>, + /// List of author styles associated with nodes in this shadow tree. + author_styles: DomRefCell<AuthorStyles<StyleSheetInDocument>>, + stylesheet_list: MutNullableDom<StyleSheetList>, + window: Dom<Window>, +} + +impl ShadowRoot { + #[allow(unrooted_must_root)] + fn new_inherited(host: &Element, document: &Document) -> ShadowRoot { + let document_fragment = DocumentFragment::new_inherited(document); + let node = document_fragment.upcast::<Node>(); + node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, true); + node.set_flag( + NodeFlags::IS_CONNECTED, + host.upcast::<Node>().is_connected(), + ); + ShadowRoot { + document_fragment, + document_or_shadow_root: DocumentOrShadowRoot::new(document.window()), + document: Dom::from_ref(document), + host: MutNullableDom::new(Some(host)), + author_styles: DomRefCell::new(AuthorStyles::new()), + stylesheet_list: MutNullableDom::new(None), + window: Dom::from_ref(document.window()), + } + } + + pub fn new(host: &Element, document: &Document) -> DomRoot<ShadowRoot> { + reflect_dom_object( + Box::new(ShadowRoot::new_inherited(host, document)), + document.window(), + ) + } + + pub fn detach(&self) { + self.document.unregister_shadow_root(&self); + let node = self.upcast::<Node>(); + node.set_containing_shadow_root(None); + Node::complete_remove_subtree(&node, &UnbindContext::new(node, None, None, None)); + self.host.set(None); + } + + pub fn get_focused_element(&self) -> Option<DomRoot<Element>> { + //XXX get retargeted focused element + None + } + + pub fn stylesheet_count(&self) -> usize { + self.author_styles.borrow().stylesheets.len() + } + + pub fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> { + let stylesheets = &self.author_styles.borrow().stylesheets; + + stylesheets + .get(index) + .and_then(|s| s.owner.upcast::<Node>().get_cssom_stylesheet()) + } + + /// Add a stylesheet owned by `owner` to the list of shadow root sheets, in the + /// correct tree position. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn add_stylesheet(&self, owner: &Element, sheet: Arc<Stylesheet>) { + let stylesheets = &mut self.author_styles.borrow_mut().stylesheets; + let insertion_point = stylesheets + .iter() + .find(|sheet_in_shadow| { + owner + .upcast::<Node>() + .is_before(sheet_in_shadow.owner.upcast()) + }) + .cloned(); + DocumentOrShadowRoot::add_stylesheet( + owner, + StylesheetSetRef::Author(stylesheets), + sheet, + insertion_point, + self.document.style_shared_lock(), + ); + } + + /// Remove a stylesheet owned by `owner` from the list of shadow root sheets. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn remove_stylesheet(&self, owner: &Element, s: &Arc<Stylesheet>) { + DocumentOrShadowRoot::remove_stylesheet( + owner, + s, + StylesheetSetRef::Author(&mut self.author_styles.borrow_mut().stylesheets), + ) + } + + pub fn invalidate_stylesheets(&self) { + self.document.invalidate_shadow_roots_stylesheets(); + self.author_styles.borrow_mut().stylesheets.force_dirty(); + // Mark the host element dirty so a reflow will be performed. + if let Some(host) = self.host.get() { + host.upcast::<Node>().dirty(NodeDamage::NodeStyleDamaged); + } + } + + /// Remove any existing association between the provided id and any elements + /// in this shadow tree. + pub fn unregister_element_id(&self, to_unregister: &Element, id: Atom) { + self.document_or_shadow_root.unregister_named_element( + self.document_fragment.id_map(), + to_unregister, + &id, + ); + } + + /// Associate an element present in this shadow tree with the provided id. + pub fn register_element_id(&self, element: &Element, id: Atom) { + let root = self + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); + self.document_or_shadow_root.register_named_element( + self.document_fragment.id_map(), + element, + &id, + root, + ); + } +} + +impl ShadowRootMethods for ShadowRoot { + // https://html.spec.whatwg.org/multipage/#dom-document-activeelement + fn GetActiveElement(&self) -> Option<DomRoot<Element>> { + self.document_or_shadow_root + .get_active_element(self.get_focused_element(), None, None) + } + + // https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint + fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> { + // Return the result of running the retargeting algorithm with context object + // and the original result as input. + match self.document_or_shadow_root.element_from_point( + x, + y, + None, + self.document.has_browsing_context(), + ) { + Some(e) => { + let retargeted_node = self.upcast::<Node>().retarget(e.upcast::<Node>()); + retargeted_node + .downcast::<Element>() + .map(|n| DomRoot::from_ref(n)) + }, + None => None, + } + } + + // https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint + fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> { + // Return the result of running the retargeting algorithm with context object + // and the original result as input + let mut elements = Vec::new(); + for e in self + .document_or_shadow_root + .elements_from_point(x, y, None, self.document.has_browsing_context()) + .iter() + { + let retargeted_node = self.upcast::<Node>().retarget(e.upcast::<Node>()); + if let Some(element) = retargeted_node + .downcast::<Element>() + .map(|n| DomRoot::from_ref(n)) + { + elements.push(element); + } + } + elements + } + + /// https://dom.spec.whatwg.org/#dom-shadowroot-mode + fn Mode(&self) -> ShadowRootMode { + ShadowRootMode::Closed + } + + /// https://dom.spec.whatwg.org/#dom-shadowroot-host + fn Host(&self) -> DomRoot<Element> { + let host = self.host.get(); + host.expect("Trying to get host from a detached shadow root") + } + + // https://drafts.csswg.org/cssom/#dom-document-stylesheets + fn StyleSheets(&self) -> DomRoot<StyleSheetList> { + self.stylesheet_list.or_init(|| { + StyleSheetList::new( + &self.window, + StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)), + ) + }) + } +} + +#[allow(unsafe_code)] +pub trait LayoutShadowRootHelpers<'dom> { + fn get_host_for_layout(self) -> LayoutDom<'dom, Element>; + fn get_style_data_for_layout(self) -> &'dom CascadeData; + unsafe fn flush_stylesheets<E: TElement>( + self, + device: &Device, + quirks_mode: QuirksMode, + guard: &SharedRwLockReadGuard, + ); +} + +impl<'dom> LayoutShadowRootHelpers<'dom> for LayoutDom<'dom, ShadowRoot> { + #[inline] + #[allow(unsafe_code)] + fn get_host_for_layout(self) -> LayoutDom<'dom, Element> { + unsafe { + self.unsafe_get() + .host + .get_inner_as_layout() + .expect("We should never do layout on a detached shadow root") + } + } + + #[inline] + #[allow(unsafe_code)] + fn get_style_data_for_layout(self) -> &'dom CascadeData { + fn is_sync<T: Sync>() {} + let _ = is_sync::<CascadeData>; + unsafe { &self.unsafe_get().author_styles.borrow_for_layout().data } + } + + // FIXME(nox): This uses the dreaded borrow_mut_for_layout so this should + // probably be revisited. + #[inline] + #[allow(unsafe_code)] + unsafe fn flush_stylesheets<E: TElement>( + self, + device: &Device, + quirks_mode: QuirksMode, + guard: &SharedRwLockReadGuard, + ) { + let author_styles = self.unsafe_get().author_styles.borrow_mut_for_layout(); + if author_styles.stylesheets.dirty() { + author_styles.flush::<E>(device, quirks_mode, guard); + } + } +} diff --git a/components/script/dom/stereopannernode.rs b/components/script/dom/stereopannernode.rs new file mode 100644 index 00000000000..cc71c69593b --- /dev/null +++ b/components/script/dom/stereopannernode.rs @@ -0,0 +1,103 @@ +/* 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 crate::dom::audioparam::AudioParam; +use crate::dom::audioscheduledsourcenode::AudioScheduledSourceNode; +use crate::dom::baseaudiocontext::BaseAudioContext; +use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{ + ChannelCountMode, ChannelInterpretation, +}; +use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate; +use crate::dom::bindings::codegen::Bindings::StereoPannerNodeBinding::StereoPannerNodeMethods; +use crate::dom::bindings::codegen::Bindings::StereoPannerNodeBinding::StereoPannerOptions; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::node::AudioNodeInit; +use servo_media::audio::param::ParamType; +use servo_media::audio::stereo_panner::StereoPannerOptions as ServoMediaStereoPannerOptions; + +#[dom_struct] +pub struct StereoPannerNode { + source_node: AudioScheduledSourceNode, + pan: Dom<AudioParam>, +} + +impl StereoPannerNode { + #[allow(unrooted_must_root)] + pub fn new_inherited( + window: &Window, + context: &BaseAudioContext, + options: &StereoPannerOptions, + ) -> Fallible<StereoPannerNode> { + let node_options = options.parent.unwrap_or( + 2, + ChannelCountMode::Clamped_max, + ChannelInterpretation::Speakers, + ); + if node_options.mode == ChannelCountMode::Max { + return Err(Error::NotSupported); + } + if node_options.count > 2 || node_options.count == 0 { + return Err(Error::NotSupported); + } + let source_node = AudioScheduledSourceNode::new_inherited( + AudioNodeInit::StereoPannerNode(options.into()), + context, + node_options, + 1, /* inputs */ + 1, /* outputs */ + )?; + let node_id = source_node.node().node_id(); + let pan = AudioParam::new( + window, + context, + node_id, + ParamType::Pan, + AutomationRate::A_rate, + *options.pan, + -1., + 1., + ); + + Ok(StereoPannerNode { + source_node, + pan: Dom::from_ref(&pan), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &BaseAudioContext, + options: &StereoPannerOptions, + ) -> Fallible<DomRoot<StereoPannerNode>> { + let node = StereoPannerNode::new_inherited(window, context, options)?; + Ok(reflect_dom_object(Box::new(node), window)) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + context: &BaseAudioContext, + options: &StereoPannerOptions, + ) -> Fallible<DomRoot<StereoPannerNode>> { + StereoPannerNode::new(window, context, options) + } +} + +impl StereoPannerNodeMethods for StereoPannerNode { + // https://webaudio.github.io/web-audio-api/#dom-stereopannernode-pan + fn Pan(&self) -> DomRoot<AudioParam> { + DomRoot::from_ref(&self.pan) + } +} + +impl<'a> From<&'a StereoPannerOptions> for ServoMediaStereoPannerOptions { + fn from(options: &'a StereoPannerOptions) -> Self { + Self { pan: *options.pan } + } +} diff --git a/components/script/dom/storage.rs b/components/script/dom/storage.rs index ea4662dda20..8d8ad0dc7ba 100644 --- a/components/script/dom/storage.rs +++ b/components/script/dom/storage.rs @@ -1,43 +1,42 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::StorageBinding; -use dom::bindings::codegen::Bindings::StorageBinding::StorageMethods; -use dom::bindings::error::{Error, ErrorResult}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::storageevent::StorageEvent; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::StorageBinding::StorageMethods; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::storageevent::StorageEvent; +use crate::dom::window::Window; +use crate::task_source::TaskSource; use dom_struct::dom_struct; -use ipc_channel::ipc::{self, IpcSender}; -use net_traits::IpcSend; +use ipc_channel::ipc::IpcSender; use net_traits::storage_thread::{StorageThreadMsg, StorageType}; -use script_thread::{Runnable, ScriptThread}; +use net_traits::IpcSend; +use profile_traits::ipc; use script_traits::ScriptMsg; use servo_url::ServoUrl; -use task_source::TaskSource; #[dom_struct] pub struct Storage { reflector_: Reflector, - storage_type: StorageType + storage_type: StorageType, } impl Storage { fn new_inherited(storage_type: StorageType) -> Storage { Storage { reflector_: Reflector::new(), - storage_type: storage_type + storage_type: storage_type, } } - pub fn new(global: &Window, storage_type: StorageType) -> Root<Storage> { - reflect_dom_object(box Storage::new_inherited(storage_type), global, StorageBinding::Wrap) + pub fn new(global: &Window, storage_type: StorageType) -> DomRoot<Storage> { + reflect_dom_object(Box::new(Storage::new_inherited(storage_type)), global) } fn get_url(&self) -> ServoUrl { @@ -47,31 +46,41 @@ impl Storage { fn get_storage_thread(&self) -> IpcSender<StorageThreadMsg> { self.global().resource_threads().sender() } - } impl StorageMethods for Storage { // https://html.spec.whatwg.org/multipage/#dom-storage-length fn Length(&self) -> u32 { - let (sender, receiver) = ipc::channel().unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); - self.get_storage_thread().send(StorageThreadMsg::Length(sender, self.get_url(), self.storage_type)).unwrap(); + self.get_storage_thread() + .send(StorageThreadMsg::Length( + sender, + self.get_url(), + self.storage_type, + )) + .unwrap(); receiver.recv().unwrap() as u32 } // https://html.spec.whatwg.org/multipage/#dom-storage-key fn Key(&self, index: u32) -> Option<DOMString> { - let (sender, receiver) = ipc::channel().unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); self.get_storage_thread() - .send(StorageThreadMsg::Key(sender, self.get_url(), self.storage_type, index)) + .send(StorageThreadMsg::Key( + sender, + self.get_url(), + self.storage_type, + index, + )) .unwrap(); receiver.recv().unwrap().map(DOMString::from) } // https://html.spec.whatwg.org/multipage/#dom-storage-getitem fn GetItem(&self, name: DOMString) -> Option<DOMString> { - let (sender, receiver) = ipc::channel().unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); let name = String::from(name); let msg = StorageThreadMsg::GetItem(sender, self.get_url(), self.storage_type, name); @@ -81,29 +90,36 @@ impl StorageMethods for Storage { // https://html.spec.whatwg.org/multipage/#dom-storage-setitem fn SetItem(&self, name: DOMString, value: DOMString) -> ErrorResult { - let (sender, receiver) = ipc::channel().unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); let name = String::from(name); let value = String::from(value); - let msg = StorageThreadMsg::SetItem(sender, self.get_url(), self.storage_type, name.clone(), value.clone()); + let msg = StorageThreadMsg::SetItem( + sender, + self.get_url(), + self.storage_type, + name.clone(), + value.clone(), + ); self.get_storage_thread().send(msg).unwrap(); match receiver.recv().unwrap() { Err(_) => Err(Error::QuotaExceeded), Ok((changed, old_value)) => { - if changed { - self.broadcast_change_notification(Some(name), old_value, Some(value)); - } - Ok(()) - } + if changed { + self.broadcast_change_notification(Some(name), old_value, Some(value)); + } + Ok(()) + }, } } // https://html.spec.whatwg.org/multipage/#dom-storage-removeitem fn RemoveItem(&self, name: DOMString) { - let (sender, receiver) = ipc::channel().unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); let name = String::from(name); - let msg = StorageThreadMsg::RemoveItem(sender, self.get_url(), self.storage_type, name.clone()); + let msg = + StorageThreadMsg::RemoveItem(sender, self.get_url(), self.storage_type, name.clone()); self.get_storage_thread().send(msg).unwrap(); if let Some(old_value) = receiver.recv().unwrap() { self.broadcast_change_notification(Some(name), Some(old_value), None); @@ -112,9 +128,15 @@ impl StorageMethods for Storage { // https://html.spec.whatwg.org/multipage/#dom-storage-clear fn Clear(&self) { - let (sender, receiver) = ipc::channel().unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); - self.get_storage_thread().send(StorageThreadMsg::Clear(sender, self.get_url(), self.storage_type)).unwrap(); + self.get_storage_thread() + .send(StorageThreadMsg::Clear( + sender, + self.get_url(), + self.storage_type, + )) + .unwrap(); if receiver.recv().unwrap() { self.broadcast_change_notification(None, None, None); } @@ -122,14 +144,21 @@ impl StorageMethods for Storage { // https://html.spec.whatwg.org/multipage/#the-storage-interface:supported-property-names fn SupportedPropertyNames(&self) -> Vec<DOMString> { - let (sender, receiver) = ipc::channel().unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); - self.get_storage_thread().send(StorageThreadMsg::Keys(sender, self.get_url(), self.storage_type)).unwrap(); - receiver.recv() - .unwrap() - .into_iter() - .map(DOMString::from) - .collect() + self.get_storage_thread() + .send(StorageThreadMsg::Keys( + sender, + self.get_url(), + self.storage_type, + )) + .unwrap(); + receiver + .recv() + .unwrap() + .into_iter() + .map(DOMString::from) + .collect() } // check-tidy: no specs after this line @@ -146,65 +175,56 @@ impl StorageMethods for Storage { } } - impl Storage { - /// https://html.spec.whatwg.org/multipage/#send-a-storage-notification - fn broadcast_change_notification(&self, key: Option<String>, old_value: Option<String>, - new_value: Option<String>) { - let pipeline_id = self.global().pipeline_id(); + /// <https://html.spec.whatwg.org/multipage/#send-a-storage-notification> + fn broadcast_change_notification( + &self, + key: Option<String>, + old_value: Option<String>, + new_value: Option<String>, + ) { let storage = self.storage_type; let url = self.get_url(); - let msg = ScriptMsg::BroadcastStorageEvent(pipeline_id, storage, url, key, old_value, new_value); - self.global().constellation_chan().send(msg).unwrap(); + let msg = ScriptMsg::BroadcastStorageEvent(storage, url, key, old_value, new_value); + self.global() + .script_to_constellation_chan() + .send(msg) + .unwrap(); } - /// https://html.spec.whatwg.org/multipage/#send-a-storage-notification - pub fn queue_storage_event(&self, url: ServoUrl, - key: Option<String>, old_value: Option<String>, new_value: Option<String>) { + /// <https://html.spec.whatwg.org/multipage/#send-a-storage-notification> + pub fn queue_storage_event( + &self, + url: ServoUrl, + key: Option<String>, + old_value: Option<String>, + new_value: Option<String>, + ) { let global = self.global(); - let window = global.as_window(); - let task_source = window.dom_manipulation_task_source(); - let trusted_storage = Trusted::new(self); - task_source + let this = Trusted::new(self); + global + .as_window() + .task_manager() + .dom_manipulation_task_source() .queue( - box StorageEventRunnable::new(trusted_storage, url, key, old_value, new_value), &global) + task!(send_storage_notification: move || { + let this = this.root(); + let global = this.global(); + let event = StorageEvent::new( + global.as_window(), + atom!("storage"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + key.map(DOMString::from), + old_value.map(DOMString::from), + new_value.map(DOMString::from), + DOMString::from(url.into_string()), + Some(&this), + ); + event.upcast::<Event>().fire(global.upcast()); + }), + global.upcast(), + ) .unwrap(); } } - -pub struct StorageEventRunnable { - element: Trusted<Storage>, - url: ServoUrl, - key: Option<String>, - old_value: Option<String>, - new_value: Option<String> -} - -impl StorageEventRunnable { - fn new(storage: Trusted<Storage>, url: ServoUrl, - key: Option<String>, old_value: Option<String>, new_value: Option<String>) -> StorageEventRunnable { - StorageEventRunnable { element: storage, url: url, key: key, old_value: old_value, new_value: new_value } - } -} - -impl Runnable for StorageEventRunnable { - fn name(&self) -> &'static str { "StorageEventRunnable" } - - fn main_thread_handler(self: Box<StorageEventRunnable>, _: &ScriptThread) { - let this = *self; - let storage = this.element.root(); - let global = storage.global(); - let window = global.as_window(); - - let storage_event = StorageEvent::new( - &window, - atom!("storage"), - EventBubbles::DoesNotBubble, EventCancelable::NotCancelable, - this.key.map(DOMString::from), this.old_value.map(DOMString::from), this.new_value.map(DOMString::from), - DOMString::from(this.url.into_string()), - Some(&storage) - ); - - storage_event.upcast::<Event>().fire(window.upcast()); - } -} diff --git a/components/script/dom/storageevent.rs b/components/script/dom/storageevent.rs index b0cab81eb7b..a50c1267585 100644 --- a/components/script/dom/storageevent.rs +++ b/components/script/dom/storageevent.rs @@ -1,68 +1,79 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::StorageEventBinding; -use dom::bindings::codegen::Bindings::StorageEventBinding::StorageEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root, RootedReference}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::storage::Storage; -use dom::window::Window; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::StorageEventBinding; +use crate::dom::bindings::codegen::Bindings::StorageEventBinding::StorageEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::storage::Storage; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; #[dom_struct] pub struct StorageEvent { event: Event, - key: Option<DOMString>, - old_value: Option<DOMString>, - new_value: Option<DOMString>, - url: DOMString, - storage_area: MutNullableJS<Storage> + key: DomRefCell<Option<DOMString>>, + old_value: DomRefCell<Option<DOMString>>, + new_value: DomRefCell<Option<DOMString>>, + url: DomRefCell<DOMString>, + storage_area: MutNullableDom<Storage>, } - +#[allow(non_snake_case)] impl StorageEvent { - pub fn new_inherited(key: Option<DOMString>, - old_value: Option<DOMString>, - new_value: Option<DOMString>, - url: DOMString, - storage_area: Option<&Storage>) -> StorageEvent { + pub fn new_inherited( + key: Option<DOMString>, + old_value: Option<DOMString>, + new_value: Option<DOMString>, + url: DOMString, + storage_area: Option<&Storage>, + ) -> StorageEvent { StorageEvent { event: Event::new_inherited(), - key: key, - old_value: old_value, - new_value: new_value, - url: url, - storage_area: MutNullableJS::new(storage_area) + key: DomRefCell::new(key), + old_value: DomRefCell::new(old_value), + new_value: DomRefCell::new(new_value), + url: DomRefCell::new(url), + storage_area: MutNullableDom::new(storage_area), } } - pub fn new_uninitialized(window: &Window, - url: DOMString) -> Root<StorageEvent> { - reflect_dom_object(box StorageEvent::new_inherited(None, None, None, url, None), - window, - StorageEventBinding::Wrap) + pub fn new_uninitialized(window: &Window, url: DOMString) -> DomRoot<StorageEvent> { + reflect_dom_object( + Box::new(StorageEvent::new_inherited(None, None, None, url, None)), + window, + ) } - pub fn new(global: &Window, - type_: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable, - key: Option<DOMString>, - oldValue: Option<DOMString>, - newValue: Option<DOMString>, - url: DOMString, - storageArea: Option<&Storage>) -> Root<StorageEvent> { - let ev = reflect_dom_object(box StorageEvent::new_inherited(key, oldValue, newValue, - url, storageArea), - global, - StorageEventBinding::Wrap); + pub fn new( + global: &Window, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + key: Option<DOMString>, + oldValue: Option<DOMString>, + newValue: Option<DOMString>, + url: DOMString, + storageArea: Option<&Storage>, + ) -> DomRoot<StorageEvent> { + let ev = reflect_dom_object( + Box::new(StorageEvent::new_inherited( + key, + oldValue, + newValue, + url, + storageArea, + )), + global, + ); { let event = ev.upcast::<Event>(); event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); @@ -70,47 +81,57 @@ impl StorageEvent { ev } - pub fn Constructor(global: &Window, - type_: DOMString, - init: &StorageEventBinding::StorageEventInit) -> Fallible<Root<StorageEvent>> { + pub fn Constructor( + global: &Window, + type_: DOMString, + init: &StorageEventBinding::StorageEventInit, + ) -> Fallible<DomRoot<StorageEvent>> { let key = init.key.clone(); let oldValue = init.oldValue.clone(); let newValue = init.newValue.clone(); let url = init.url.clone(); - let storageArea = init.storageArea.r(); + let storageArea = init.storageArea.as_deref(); let bubbles = EventBubbles::from(init.parent.bubbles); let cancelable = EventCancelable::from(init.parent.cancelable); - let event = StorageEvent::new(global, Atom::from(type_), - bubbles, cancelable, - key, oldValue, newValue, - url, storageArea); + let event = StorageEvent::new( + global, + Atom::from(type_), + bubbles, + cancelable, + key, + oldValue, + newValue, + url, + storageArea, + ); Ok(event) } } +#[allow(non_snake_case)] impl StorageEventMethods for StorageEvent { // https://html.spec.whatwg.org/multipage/#dom-storageevent-key fn GetKey(&self) -> Option<DOMString> { - self.key.clone() + self.key.borrow().clone() } // https://html.spec.whatwg.org/multipage/#dom-storageevent-oldvalue fn GetOldValue(&self) -> Option<DOMString> { - self.old_value.clone() + self.old_value.borrow().clone() } // https://html.spec.whatwg.org/multipage/#dom-storageevent-newvalue fn GetNewValue(&self) -> Option<DOMString> { - self.new_value.clone() + self.new_value.borrow().clone() } // https://html.spec.whatwg.org/multipage/#dom-storageevent-url fn Url(&self) -> DOMString { - self.url.clone() + self.url.borrow().clone() } // https://html.spec.whatwg.org/multipage/#dom-storageevent-storagearea - fn GetStorageArea(&self) -> Option<Root<Storage>> { + fn GetStorageArea(&self) -> Option<DomRoot<Storage>> { self.storage_area.get() } @@ -118,4 +139,28 @@ impl StorageEventMethods for StorageEvent { fn IsTrusted(&self) -> bool { self.event.IsTrusted() } + + // https://html.spec.whatwg.org/multipage/#dom-storageevent-initstorageevent + fn InitStorageEvent( + &self, + type_: DOMString, + bubbles: bool, + cancelable: bool, + key: Option<DOMString>, + oldValue: Option<DOMString>, + newValue: Option<DOMString>, + url: USVString, + storageArea: Option<&Storage>, + ) { + self.event.init_event( + Atom::from(type_), + bool::from(bubbles), + bool::from(cancelable), + ); + *self.key.borrow_mut() = key; + *self.old_value.borrow_mut() = oldValue; + *self.new_value.borrow_mut() = newValue; + *self.url.borrow_mut() = DOMString::from_string(url.0); + self.storage_area.set(storageArea); + } } diff --git a/components/script/dom/stylepropertymapreadonly.rs b/components/script/dom/stylepropertymapreadonly.rs new file mode 100644 index 00000000000..3881bfcdd87 --- /dev/null +++ b/components/script/dom/stylepropertymapreadonly.rs @@ -0,0 +1,103 @@ +/* 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 crate::dom::bindings::codegen::Bindings::StylePropertyMapReadOnlyBinding::StylePropertyMapReadOnlyMethods; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssstylevalue::CSSStyleValue; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use servo_atoms::Atom; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::iter::Iterator; +use style::custom_properties; + +#[dom_struct] +pub struct StylePropertyMapReadOnly { + reflector: Reflector, + entries: HashMap<Atom, Dom<CSSStyleValue>>, +} + +impl StylePropertyMapReadOnly { + fn new_inherited<Entries>(entries: Entries) -> StylePropertyMapReadOnly + where + Entries: IntoIterator<Item = (Atom, Dom<CSSStyleValue>)>, + { + StylePropertyMapReadOnly { + reflector: Reflector::new(), + entries: entries.into_iter().collect(), + } + } + + pub fn from_iter<Entries>( + global: &GlobalScope, + entries: Entries, + ) -> DomRoot<StylePropertyMapReadOnly> + where + Entries: IntoIterator<Item = (Atom, String)>, + { + let mut keys = Vec::new(); + rooted_vec!(let mut values); + let iter = entries.into_iter(); + let (lo, _) = iter.size_hint(); + keys.reserve(lo); + values.reserve(lo); + for (key, value) in iter { + let value = CSSStyleValue::new(global, value); + keys.push(key); + values.push(Dom::from_ref(&*value)); + } + let iter = keys.drain(..).zip(values.iter().cloned()); + reflect_dom_object( + Box::new(StylePropertyMapReadOnly::new_inherited(iter)), + global, + ) + } +} + +impl StylePropertyMapReadOnlyMethods for StylePropertyMapReadOnly { + /// <https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-get> + fn Get(&self, property: DOMString) -> Option<DomRoot<CSSStyleValue>> { + // TODO: avoid constructing an Atom + self.entries + .get(&Atom::from(property)) + .map(|value| DomRoot::from_ref(&**value)) + } + + /// <https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-has> + fn Has(&self, property: DOMString) -> bool { + // TODO: avoid constructing an Atom + self.entries.contains_key(&Atom::from(property)) + } + + /// <https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-getproperties> + fn GetProperties(&self) -> Vec<DOMString> { + let mut result: Vec<DOMString> = self + .entries + .keys() + .map(|key| DOMString::from(&**key)) + .collect(); + // https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-getproperties + // requires this sort order + result.sort_by(|key1, key2| { + if let Ok(key1) = custom_properties::parse_name(key1) { + if let Ok(key2) = custom_properties::parse_name(key2) { + key1.cmp(key2) + } else { + Ordering::Greater + } + } else { + if let Ok(_) = custom_properties::parse_name(key2) { + Ordering::Less + } else { + key1.cmp(key2) + } + } + }); + result + } +} diff --git a/components/script/dom/stylesheet.rs b/components/script/dom/stylesheet.rs index bf4b1c562e3..c03776dc7f6 100644 --- a/components/script/dom/stylesheet.rs +++ b/components/script/dom/stylesheet.rs @@ -1,15 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::StyleSheetBinding; -use dom::bindings::codegen::Bindings::StyleSheetBinding::StyleSheetMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::cssstylesheet::CSSStyleSheet; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::StyleSheetBinding::StyleSheetMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::element::Element; +use crate::dom::medialist::MediaList; use dom_struct::dom_struct; #[dom_struct] @@ -22,9 +22,11 @@ pub struct StyleSheet { impl StyleSheet { #[allow(unrooted_must_root)] - pub fn new_inherited(type_: DOMString, - href: Option<DOMString>, - title: Option<DOMString>) -> StyleSheet { + pub fn new_inherited( + type_: DOMString, + href: Option<DOMString>, + title: Option<DOMString>, + ) -> StyleSheet { StyleSheet { reflector_: Reflector::new(), type_: type_, @@ -32,18 +34,8 @@ impl StyleSheet { title: title, } } - - #[allow(unrooted_must_root)] - pub fn new(window: &Window, type_: DOMString, - href: Option<DOMString>, - title: Option<DOMString>) -> Root<StyleSheet> { - reflect_dom_object(box StyleSheet::new_inherited(type_, href, title), - window, - StyleSheetBinding::Wrap) - } } - impl StyleSheetMethods for StyleSheet { // https://drafts.csswg.org/cssom/#dom-stylesheet-type fn Type_(&self) -> DOMString { @@ -55,6 +47,16 @@ impl StyleSheetMethods for StyleSheet { self.href.clone() } + // https://drafts.csswg.org/cssom/#dom-stylesheet-ownernode + fn GetOwnerNode(&self) -> Option<DomRoot<Element>> { + self.downcast::<CSSStyleSheet>().and_then(|s| s.get_owner()) + } + + // https://drafts.csswg.org/cssom/#dom-stylesheet-media + fn Media(&self) -> DomRoot<MediaList> { + self.downcast::<CSSStyleSheet>().unwrap().medialist() + } + // https://drafts.csswg.org/cssom/#dom-stylesheet-title fn GetTitle(&self) -> Option<DOMString> { self.title.clone() @@ -67,6 +69,8 @@ impl StyleSheetMethods for StyleSheet { // https://drafts.csswg.org/cssom/#dom-stylesheet-disabled fn SetDisabled(&self, disabled: bool) { - self.downcast::<CSSStyleSheet>().unwrap().set_disabled(disabled) + self.downcast::<CSSStyleSheet>() + .unwrap() + .set_disabled(disabled) } } diff --git a/components/script/dom/stylesheetlist.rs b/components/script/dom/stylesheetlist.rs index 25d95ae3986..7bd115f5084 100644 --- a/components/script/dom/stylesheetlist.rs +++ b/components/script/dom/stylesheetlist.rs @@ -1,57 +1,108 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::StyleSheetListBinding; -use dom::bindings::codegen::Bindings::StyleSheetListBinding::StyleSheetListMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::document::Document; -use dom::stylesheet::StyleSheet; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::StyleSheetListBinding::StyleSheetListMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::shadowroot::ShadowRoot; +use crate::dom::stylesheet::StyleSheet; +use crate::dom::window::Window; use dom_struct::dom_struct; +use servo_arc::Arc; +use style::stylesheets::Stylesheet; + +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +pub enum StyleSheetListOwner { + Document(Dom<Document>), + ShadowRoot(Dom<ShadowRoot>), +} + +impl StyleSheetListOwner { + pub fn stylesheet_count(&self) -> usize { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.stylesheet_count(), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => shadow_root.stylesheet_count(), + } + } + + pub fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.stylesheet_at(index), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => shadow_root.stylesheet_at(index), + } + } + + pub fn add_stylesheet(&self, owner: &Element, sheet: Arc<Stylesheet>) { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.add_stylesheet(owner, sheet), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => { + shadow_root.add_stylesheet(owner, sheet) + }, + } + } + + pub fn remove_stylesheet(&self, owner: &Element, s: &Arc<Stylesheet>) { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.remove_stylesheet(owner, s), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => { + shadow_root.remove_stylesheet(owner, s) + }, + } + } + + pub fn invalidate_stylesheets(&self) { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.invalidate_stylesheets(), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => { + shadow_root.invalidate_stylesheets() + }, + } + } +} #[dom_struct] pub struct StyleSheetList { reflector_: Reflector, - document: JS<Document>, + document_or_shadow_root: StyleSheetListOwner, } impl StyleSheetList { #[allow(unrooted_must_root)] - fn new_inherited(doc: JS<Document>) -> StyleSheetList { + fn new_inherited(doc_or_sr: StyleSheetListOwner) -> StyleSheetList { StyleSheetList { reflector_: Reflector::new(), - document: doc + document_or_shadow_root: doc_or_sr, } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, document: JS<Document>) -> Root<StyleSheetList> { - reflect_dom_object(box StyleSheetList::new_inherited(document), - window, StyleSheetListBinding::Wrap) + pub fn new(window: &Window, doc_or_sr: StyleSheetListOwner) -> DomRoot<StyleSheetList> { + reflect_dom_object(Box::new(StyleSheetList::new_inherited(doc_or_sr)), window) } } impl StyleSheetListMethods for StyleSheetList { // https://drafts.csswg.org/cssom/#dom-stylesheetlist-length fn Length(&self) -> u32 { - self.document.with_style_sheets_in_document(|s| s.len() as u32) + self.document_or_shadow_root.stylesheet_count() as u32 } // https://drafts.csswg.org/cssom/#dom-stylesheetlist-item - fn Item(&self, index: u32) -> Option<Root<StyleSheet>> { - // XXXManishearth this doesn't handle the origin clean flag - // and is a cors vulnerability - self.document.with_style_sheets_in_document(|sheets| { - sheets.get(index as usize) - .and_then(|sheet| sheet.node.get_cssom_stylesheet()) - .map(Root::upcast) - }) + fn Item(&self, index: u32) -> Option<DomRoot<StyleSheet>> { + // XXXManishearth this doesn't handle the origin clean flag and is a + // cors vulnerability + self.document_or_shadow_root + .stylesheet_at(index as usize) + .map(DomRoot::upcast) } // check-tidy: no specs after this line - fn IndexedGetter(&self, index: u32) -> Option<Root<StyleSheet>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<StyleSheet>> { self.Item(index) } } diff --git a/components/script/dom/submitevent.rs b/components/script/dom/submitevent.rs new file mode 100644 index 00000000000..c9965b762b1 --- /dev/null +++ b/components/script/dom/submitevent.rs @@ -0,0 +1,75 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::SubmitEventBinding; +use crate::dom::bindings::codegen::Bindings::SubmitEventBinding::SubmitEventMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +#[allow(non_snake_case)] +pub struct SubmitEvent { + event: Event, + submitter: Option<DomRoot<HTMLElement>>, +} + +impl SubmitEvent { + fn new_inherited(submitter: Option<DomRoot<HTMLElement>>) -> SubmitEvent { + SubmitEvent { + event: Event::new_inherited(), + submitter: submitter, + } + } + + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + submitter: Option<DomRoot<HTMLElement>>, + ) -> DomRoot<SubmitEvent> { + let ev = reflect_dom_object(Box::new(SubmitEvent::new_inherited(submitter)), global); + { + let event = ev.upcast::<Event>(); + event.init_event(type_, bubbles, cancelable); + } + ev + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &SubmitEventBinding::SubmitEventInit, + ) -> DomRoot<SubmitEvent> { + SubmitEvent::new( + &window.global(), + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + init.submitter.as_ref().map(|s| DomRoot::from_ref(&**s)), + ) + } +} + +impl SubmitEventMethods for SubmitEvent { + /// <https://dom.spec.whatwg.org/#dom-event-istrusted> + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } + + /// https://html.spec.whatwg.org/multipage/#dom-submitevent-submitter + fn GetSubmitter(&self) -> Option<DomRoot<HTMLElement>> { + self.submitter.as_ref().map(|s| DomRoot::from_ref(&**s)) + } +} diff --git a/components/script/dom/svgelement.rs b/components/script/dom/svgelement.rs index 1a0654ed312..92c56c95c5d 100644 --- a/components/script/dom/svgelement.rs +++ b/components/script/dom/svgelement.rs @@ -1,34 +1,76 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::inheritance::Castable; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::Element; -use dom::virtualmethods::VirtualMethods; +use crate::dom::bindings::codegen::Bindings::SVGElementBinding::SVGElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::node::window_from_node; +use crate::dom::node::Node; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use style::element_state::ElementState; #[dom_struct] pub struct SVGElement { element: Element, + style_decl: MutNullableDom<CSSStyleDeclaration>, } impl SVGElement { - pub fn new_inherited_with_state(state: ElementState, tag_name: LocalName, - prefix: Option<DOMString>, document: &Document) - -> SVGElement { + fn new_inherited( + tag_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> SVGElement { + SVGElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document) + } + + pub fn new_inherited_with_state( + state: ElementState, + tag_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> SVGElement { SVGElement { - element: - Element::new_inherited_with_state(state, tag_name, ns!(svg), prefix, document), + element: Element::new_inherited_with_state(state, tag_name, ns!(svg), prefix, document), + style_decl: Default::default(), } } + + pub fn new( + tag_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<SVGElement> { + Node::reflect_node( + Box::new(SVGElement::new_inherited(tag_name, prefix, document)), + document, + ) + } } impl VirtualMethods for SVGElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<Element>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<Element>() as &dyn VirtualMethods) + } +} + +impl SVGElementMethods for SVGElement { + // https://html.spec.whatwg.org/multipage/#the-style-attribute + fn Style(&self) -> DomRoot<CSSStyleDeclaration> { + self.style_decl.or_init(|| { + let global = window_from_node(self); + CSSStyleDeclaration::new( + &global, + CSSStyleOwner::Element(Dom::from_ref(self.upcast())), + None, + CSSModificationAccess::ReadWrite, + ) + }) } } diff --git a/components/script/dom/svggraphicselement.rs b/components/script/dom/svggraphicselement.rs index f41768f9815..f3322f98888 100644 --- a/components/script/dom/svggraphicselement.rs +++ b/components/script/dom/svggraphicselement.rs @@ -1,14 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::inheritance::Castable; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::svgelement::SVGElement; -use dom::virtualmethods::VirtualMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::document::Document; +use crate::dom::svgelement::SVGElement; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use style::element_state::ElementState; #[dom_struct] @@ -17,23 +16,33 @@ pub struct SVGGraphicsElement { } impl SVGGraphicsElement { - pub fn new_inherited(tag_name: LocalName, prefix: Option<DOMString>, - document: &Document) -> SVGGraphicsElement { - SVGGraphicsElement::new_inherited_with_state(ElementState::empty(), tag_name, prefix, document) + pub fn new_inherited( + tag_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> SVGGraphicsElement { + SVGGraphicsElement::new_inherited_with_state( + ElementState::empty(), + tag_name, + prefix, + document, + ) } - pub fn new_inherited_with_state(state: ElementState, tag_name: LocalName, - prefix: Option<DOMString>, document: &Document) - -> SVGGraphicsElement { + pub fn new_inherited_with_state( + state: ElementState, + tag_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> SVGGraphicsElement { SVGGraphicsElement { - svgelement: - SVGElement::new_inherited_with_state(state, tag_name, prefix, document), + svgelement: SVGElement::new_inherited_with_state(state, tag_name, prefix, document), } } } impl VirtualMethods for SVGGraphicsElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<SVGElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<SVGElement>() as &dyn VirtualMethods) } } diff --git a/components/script/dom/svgsvgelement.rs b/components/script/dom/svgsvgelement.rs index 7c7d85f17b0..583bdb0eb44 100644 --- a/components/script/dom/svgsvgelement.rs +++ b/components/script/dom/svgsvgelement.rs @@ -1,19 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::SVGSVGElementBinding; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; -use dom::node::Node; -use dom::svggraphicselement::SVGGraphicsElement; -use dom::virtualmethods::VirtualMethods; +use crate::dom::attr::Attr; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers}; +use crate::dom::node::Node; +use crate::dom::svggraphicselement::SVGGraphicsElement; +use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use script_layout_interface::SVGSVGData; use style::attr::AttrValue; @@ -22,52 +21,55 @@ const DEFAULT_HEIGHT: u32 = 150; #[dom_struct] pub struct SVGSVGElement { - svggraphicselement: SVGGraphicsElement + svggraphicselement: SVGGraphicsElement, } impl SVGSVGElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> SVGSVGElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> SVGSVGElement { SVGSVGElement { - svggraphicselement: - SVGGraphicsElement::new_inherited(local_name, prefix, document) + svggraphicselement: SVGGraphicsElement::new_inherited(local_name, prefix, document), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<SVGSVGElement> { - Node::reflect_node(box SVGSVGElement::new_inherited(local_name, prefix, document), - document, - SVGSVGElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<SVGSVGElement> { + Node::reflect_node( + Box::new(SVGSVGElement::new_inherited(local_name, prefix, document)), + document, + ) } } pub trait LayoutSVGSVGElementHelpers { - fn data(&self) -> SVGSVGData; + fn data(self) -> SVGSVGData; } -impl LayoutSVGSVGElementHelpers for LayoutJS<SVGSVGElement> { - #[allow(unsafe_code)] - fn data(&self) -> SVGSVGData { - unsafe { - let SVG = &*self.unsafe_get(); - - let width_attr = SVG.upcast::<Element>().get_attr_for_layout(&ns!(), &local_name!("width")); - let height_attr = SVG.upcast::<Element>().get_attr_for_layout(&ns!(), &local_name!("height")); - SVGSVGData { - width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()), - height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()), - } +impl LayoutSVGSVGElementHelpers for LayoutDom<'_, SVGSVGElement> { + fn data(self) -> SVGSVGData { + let width_attr = self + .upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("width")); + let height_attr = self + .upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("height")); + SVGSVGData { + width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()), + height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()), } } } impl VirtualMethods for SVGSVGElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<SVGGraphicsElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<SVGGraphicsElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -78,7 +80,10 @@ impl VirtualMethods for SVGSVGElement { match name { &local_name!("width") => AttrValue::from_u32(value.into(), DEFAULT_WIDTH), &local_name!("height") => AttrValue::from_u32(value.into(), DEFAULT_HEIGHT), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } } diff --git a/components/script/dom/test_mapping.json b/components/script/dom/test_mapping.json new file mode 100644 index 00000000000..3a66bae87ac --- /dev/null +++ b/components/script/dom/test_mapping.json @@ -0,0 +1,8 @@ +{ + "xmlhttprequest.rs": [ + "XMLHttpRequest" + ], + "range.rs": [ + "dom/ranges" + ] +} diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs index f08629c3861..34c25c40449 100644 --- a/components/script/dom/testbinding.rs +++ b/components/script/dom/testbinding.rs @@ -1,49 +1,73 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // check-tidy: no specs after this line -use core::nonzero::NonZero; -use dom::bindings::callback::ExceptionHandling; -use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; -use dom::bindings::codegen::Bindings::FunctionBinding::Function; -use dom::bindings::codegen::Bindings::TestBindingBinding::{self, SimpleCallback}; -use dom::bindings::codegen::Bindings::TestBindingBinding::{TestBindingMethods, TestDictionary}; -use dom::bindings::codegen::Bindings::TestBindingBinding::{TestDictionaryDefaults, TestEnum}; -use dom::bindings::codegen::UnionTypes; -use dom::bindings::codegen::UnionTypes::{BlobOrBoolean, BlobOrBlobSequence, LongOrLongSequenceSequence}; -use dom::bindings::codegen::UnionTypes::{BlobOrString, BlobOrUnsignedLong, EventOrString}; -use dom::bindings::codegen::UnionTypes::{ByteStringOrLong, ByteStringSequenceOrLongOrString}; -use dom::bindings::codegen::UnionTypes::{ByteStringSequenceOrLong, DocumentOrTestTypedef}; -use dom::bindings::codegen::UnionTypes::{EventOrUSVString, HTMLElementOrLong, LongSequenceOrTestTypedef}; -use dom::bindings::codegen::UnionTypes::{HTMLElementOrUnsignedLongOrStringOrBoolean, LongSequenceOrBoolean}; -use dom::bindings::codegen::UnionTypes::{StringOrLongSequence, StringOrStringSequence, StringSequenceOrUnsignedLong}; -use dom::bindings::codegen::UnionTypes::{StringOrUnsignedLong, StringOrBoolean, UnsignedLongOrBoolean}; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::js::Root; -use dom::bindings::mozmap::MozMap; -use dom::bindings::num::Finite; -use dom::bindings::refcounted::TrustedPromise; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::{ByteString, DOMString, USVString}; -use dom::bindings::trace::RootedTraceableBox; -use dom::bindings::weakref::MutableWeakRef; -use dom::blob::{Blob, BlobImpl}; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; -use dom::promisenativehandler::{PromiseNativeHandler, Callback}; -use dom::url::URL; +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener; +use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; +use crate::dom::bindings::codegen::Bindings::TestBindingBinding::SimpleCallback; +use crate::dom::bindings::codegen::Bindings::TestBindingBinding::TestDictionaryParent; +use crate::dom::bindings::codegen::Bindings::TestBindingBinding::TestDictionaryWithParent; +use crate::dom::bindings::codegen::Bindings::TestBindingBinding::{ + TestBindingMethods, TestDictionary, +}; +use crate::dom::bindings::codegen::Bindings::TestBindingBinding::{ + TestDictionaryDefaults, TestEnum, TestURLLike, +}; +use crate::dom::bindings::codegen::UnionTypes; +use crate::dom::bindings::codegen::UnionTypes::{ + BlobOrBlobSequence, BlobOrBoolean, LongOrLongSequenceSequence, +}; +use crate::dom::bindings::codegen::UnionTypes::{BlobOrString, BlobOrUnsignedLong, EventOrString}; +use crate::dom::bindings::codegen::UnionTypes::{ + ByteStringOrLong, ByteStringSequenceOrLongOrString, +}; +use crate::dom::bindings::codegen::UnionTypes::{ByteStringSequenceOrLong, DocumentOrTestTypedef}; +use crate::dom::bindings::codegen::UnionTypes::{ + EventOrUSVString, HTMLElementOrLong, LongSequenceOrTestTypedef, +}; +use crate::dom::bindings::codegen::UnionTypes::{ + HTMLElementOrUnsignedLongOrStringOrBoolean, LongSequenceOrBoolean, +}; +use crate::dom::bindings::codegen::UnionTypes::{StringOrBoolean, UnsignedLongOrBoolean}; +use crate::dom::bindings::codegen::UnionTypes::{StringOrLongSequence, StringOrStringSequence}; +use crate::dom::bindings::codegen::UnionTypes::{ + StringOrUnsignedLong, StringSequenceOrUnsignedLong, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::record::Record; +use crate::dom::bindings::refcounted::TrustedPromise; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::{ByteString, DOMString, USVString}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::bindings::weakref::MutableWeakRef; +use crate::dom::blob::Blob; +use crate::dom::globalscope::GlobalScope; +use crate::dom::node::Node; +use crate::dom::promise::Promise; +use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; +use crate::dom::url::URL; +use crate::realms::InRealm; +use crate::script_runtime::JSContext as SafeJSContext; +use crate::timers::OneshotTimerCallback; use dom_struct::dom_struct; -use js::jsapi::{HandleObject, HandleValue, Heap, JSContext, JSObject, JSAutoCompartment}; +use js::jsapi::{Heap, JSObject}; use js::jsapi::{JS_NewPlainObject, JS_NewUint8ClampedArray}; use js::jsval::{JSVal, NullValue}; +use js::rust::CustomAutoRooterGuard; +use js::rust::{HandleObject, HandleValue}; +use js::typedarray; +use script_traits::serializable::BlobImpl; use script_traits::MsDuration; -use servo_config::prefs::PREFS; +use servo_config::prefs; use std::borrow::ToOwned; use std::ptr; +use std::ptr::NonNull; use std::rc::Rc; -use timers::OneshotTimerCallback; #[dom_struct] pub struct TestBinding { @@ -51,6 +75,7 @@ pub struct TestBinding { url: MutableWeakRef<URL>, } +#[allow(non_snake_case)] impl TestBinding { fn new_inherited() -> TestBinding { TestBinding { @@ -59,68 +84,108 @@ impl TestBinding { } } - pub fn new(global: &GlobalScope) -> Root<TestBinding> { - reflect_dom_object(box TestBinding::new_inherited(), - global, TestBindingBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<TestBinding> { + reflect_dom_object(Box::new(TestBinding::new_inherited()), global) } - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<TestBinding>> { + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<TestBinding>> { Ok(TestBinding::new(global)) } #[allow(unused_variables)] - pub fn Constructor_(global: &GlobalScope, nums: Vec<f64>) -> Fallible<Root<TestBinding>> { + pub fn Constructor_(global: &GlobalScope, nums: Vec<f64>) -> Fallible<DomRoot<TestBinding>> { Ok(TestBinding::new(global)) } #[allow(unused_variables)] - pub fn Constructor__(global: &GlobalScope, num: f64) -> Fallible<Root<TestBinding>> { + pub fn Constructor__(global: &GlobalScope, num: f64) -> Fallible<DomRoot<TestBinding>> { Ok(TestBinding::new(global)) } } impl TestBindingMethods for TestBinding { - fn BooleanAttribute(&self) -> bool { false } + fn BooleanAttribute(&self) -> bool { + false + } fn SetBooleanAttribute(&self, _: bool) {} - fn ByteAttribute(&self) -> i8 { 0 } + fn ByteAttribute(&self) -> i8 { + 0 + } fn SetByteAttribute(&self, _: i8) {} - fn OctetAttribute(&self) -> u8 { 0 } + fn OctetAttribute(&self) -> u8 { + 0 + } fn SetOctetAttribute(&self, _: u8) {} - fn ShortAttribute(&self) -> i16 { 0 } + fn ShortAttribute(&self) -> i16 { + 0 + } fn SetShortAttribute(&self, _: i16) {} - fn UnsignedShortAttribute(&self) -> u16 { 0 } + fn UnsignedShortAttribute(&self) -> u16 { + 0 + } fn SetUnsignedShortAttribute(&self, _: u16) {} - fn LongAttribute(&self) -> i32 { 0 } + fn LongAttribute(&self) -> i32 { + 0 + } fn SetLongAttribute(&self, _: i32) {} - fn UnsignedLongAttribute(&self) -> u32 { 0 } + fn UnsignedLongAttribute(&self) -> u32 { + 0 + } fn SetUnsignedLongAttribute(&self, _: u32) {} - fn LongLongAttribute(&self) -> i64 { 0 } + fn LongLongAttribute(&self) -> i64 { + 0 + } fn SetLongLongAttribute(&self, _: i64) {} - fn UnsignedLongLongAttribute(&self) -> u64 { 0 } + fn UnsignedLongLongAttribute(&self) -> u64 { + 0 + } fn SetUnsignedLongLongAttribute(&self, _: u64) {} - fn UnrestrictedFloatAttribute(&self) -> f32 { 0. } + fn UnrestrictedFloatAttribute(&self) -> f32 { + 0. + } fn SetUnrestrictedFloatAttribute(&self, _: f32) {} - fn FloatAttribute(&self) -> Finite<f32> { Finite::wrap(0.) } + fn FloatAttribute(&self) -> Finite<f32> { + Finite::wrap(0.) + } fn SetFloatAttribute(&self, _: Finite<f32>) {} - fn UnrestrictedDoubleAttribute(&self) -> f64 { 0. } + fn UnrestrictedDoubleAttribute(&self) -> f64 { + 0. + } fn SetUnrestrictedDoubleAttribute(&self, _: f64) {} - fn DoubleAttribute(&self) -> Finite<f64> { Finite::wrap(0.) } + fn DoubleAttribute(&self) -> Finite<f64> { + Finite::wrap(0.) + } fn SetDoubleAttribute(&self, _: Finite<f64>) {} - fn StringAttribute(&self) -> DOMString { DOMString::new() } + fn StringAttribute(&self) -> DOMString { + DOMString::new() + } fn SetStringAttribute(&self, _: DOMString) {} - fn UsvstringAttribute(&self) -> USVString { USVString("".to_owned()) } + fn UsvstringAttribute(&self) -> USVString { + USVString("".to_owned()) + } fn SetUsvstringAttribute(&self, _: USVString) {} - fn ByteStringAttribute(&self) -> ByteString { ByteString::new(vec!()) } + fn ByteStringAttribute(&self) -> ByteString { + ByteString::new(vec![]) + } fn SetByteStringAttribute(&self, _: ByteString) {} - fn EnumAttribute(&self) -> TestEnum { TestEnum::_empty } + fn EnumAttribute(&self) -> TestEnum { + TestEnum::_empty + } fn SetEnumAttribute(&self, _: TestEnum) {} - fn InterfaceAttribute(&self) -> Root<Blob> { - Blob::new(&self.global(), BlobImpl::new_from_bytes(vec![]), "".to_owned()) + fn InterfaceAttribute(&self) -> DomRoot<Blob> { + Blob::new( + &self.global(), + BlobImpl::new_from_bytes(vec![], "".to_owned()), + ) } fn SetInterfaceAttribute(&self, _: &Blob) {} - fn UnionAttribute(&self) -> HTMLElementOrLong { HTMLElementOrLong::Long(0) } + fn UnionAttribute(&self) -> HTMLElementOrLong { + HTMLElementOrLong::Long(0) + } fn SetUnionAttribute(&self, _: HTMLElementOrLong) {} - fn Union2Attribute(&self) -> EventOrString { EventOrString::String(DOMString::new()) } + fn Union2Attribute(&self) -> EventOrString { + EventOrString::String(DOMString::new()) + } fn SetUnion2Attribute(&self, _: EventOrString) {} fn Union3Attribute(&self) -> EventOrUSVString { EventOrUSVString::USVString(USVString("".to_owned())) @@ -147,82 +212,128 @@ impl TestBindingMethods for TestBinding { } fn SetUnion8Attribute(&self, _: BlobOrUnsignedLong) {} fn Union9Attribute(&self) -> ByteStringOrLong { - ByteStringOrLong::ByteString(ByteString::new(vec!())) + ByteStringOrLong::ByteString(ByteString::new(vec![])) } fn SetUnion9Attribute(&self, _: ByteStringOrLong) {} #[allow(unsafe_code)] - unsafe fn ArrayAttribute(&self, cx: *mut JSContext) -> NonZero<*mut JSObject> { - rooted!(in(cx) let array = JS_NewUint8ClampedArray(cx, 16)); - assert!(!array.is_null()); - NonZero::new(array.get()) + fn ArrayAttribute(&self, cx: SafeJSContext) -> NonNull<JSObject> { + unsafe { + rooted!(in(*cx) let array = JS_NewUint8ClampedArray(*cx, 16)); + NonNull::new(array.get()).expect("got a null pointer") + } } - #[allow(unsafe_code)] - unsafe fn AnyAttribute(&self, _: *mut JSContext) -> JSVal { NullValue() } - #[allow(unsafe_code)] - unsafe fn SetAnyAttribute(&self, _: *mut JSContext, _: HandleValue) {} - #[allow(unsafe_code)] - unsafe fn ObjectAttribute(&self, cx: *mut JSContext) -> NonZero<*mut JSObject> { - rooted!(in(cx) let obj = JS_NewPlainObject(cx)); - assert!(!obj.is_null()); - NonZero::new(obj.get()) + fn AnyAttribute(&self, _: SafeJSContext) -> JSVal { + NullValue() } + fn SetAnyAttribute(&self, _: SafeJSContext, _: HandleValue) {} #[allow(unsafe_code)] - unsafe fn SetObjectAttribute(&self, _: *mut JSContext, _: *mut JSObject) {} + fn ObjectAttribute(&self, cx: SafeJSContext) -> NonNull<JSObject> { + unsafe { + rooted!(in(*cx) let obj = JS_NewPlainObject(*cx)); + NonNull::new(obj.get()).expect("got a null pointer") + } + } + fn SetObjectAttribute(&self, _: SafeJSContext, _: *mut JSObject) {} - fn GetBooleanAttributeNullable(&self) -> Option<bool> { Some(false) } + fn GetBooleanAttributeNullable(&self) -> Option<bool> { + Some(false) + } fn SetBooleanAttributeNullable(&self, _: Option<bool>) {} - fn GetByteAttributeNullable(&self) -> Option<i8> { Some(0) } + fn GetByteAttributeNullable(&self) -> Option<i8> { + Some(0) + } fn SetByteAttributeNullable(&self, _: Option<i8>) {} - fn GetOctetAttributeNullable(&self) -> Option<u8> { Some(0) } + fn GetOctetAttributeNullable(&self) -> Option<u8> { + Some(0) + } fn SetOctetAttributeNullable(&self, _: Option<u8>) {} - fn GetShortAttributeNullable(&self) -> Option<i16> { Some(0) } + fn GetShortAttributeNullable(&self) -> Option<i16> { + Some(0) + } fn SetShortAttributeNullable(&self, _: Option<i16>) {} - fn GetUnsignedShortAttributeNullable(&self) -> Option<u16> { Some(0) } + fn GetUnsignedShortAttributeNullable(&self) -> Option<u16> { + Some(0) + } fn SetUnsignedShortAttributeNullable(&self, _: Option<u16>) {} - fn GetLongAttributeNullable(&self) -> Option<i32> { Some(0) } + fn GetLongAttributeNullable(&self) -> Option<i32> { + Some(0) + } fn SetLongAttributeNullable(&self, _: Option<i32>) {} - fn GetUnsignedLongAttributeNullable(&self) -> Option<u32> { Some(0) } + fn GetUnsignedLongAttributeNullable(&self) -> Option<u32> { + Some(0) + } fn SetUnsignedLongAttributeNullable(&self, _: Option<u32>) {} - fn GetLongLongAttributeNullable(&self) -> Option<i64> { Some(0) } + fn GetLongLongAttributeNullable(&self) -> Option<i64> { + Some(0) + } fn SetLongLongAttributeNullable(&self, _: Option<i64>) {} - fn GetUnsignedLongLongAttributeNullable(&self) -> Option<u64> { Some(0) } + fn GetUnsignedLongLongAttributeNullable(&self) -> Option<u64> { + Some(0) + } fn SetUnsignedLongLongAttributeNullable(&self, _: Option<u64>) {} - fn GetUnrestrictedFloatAttributeNullable(&self) -> Option<f32> { Some(0.) } + fn GetUnrestrictedFloatAttributeNullable(&self) -> Option<f32> { + Some(0.) + } fn SetUnrestrictedFloatAttributeNullable(&self, _: Option<f32>) {} - fn GetFloatAttributeNullable(&self) -> Option<Finite<f32>> { Some(Finite::wrap(0.)) } + fn GetFloatAttributeNullable(&self) -> Option<Finite<f32>> { + Some(Finite::wrap(0.)) + } fn SetFloatAttributeNullable(&self, _: Option<Finite<f32>>) {} - fn GetUnrestrictedDoubleAttributeNullable(&self) -> Option<f64> { Some(0.) } + fn GetUnrestrictedDoubleAttributeNullable(&self) -> Option<f64> { + Some(0.) + } fn SetUnrestrictedDoubleAttributeNullable(&self, _: Option<f64>) {} - fn GetDoubleAttributeNullable(&self) -> Option<Finite<f64>> { Some(Finite::wrap(0.)) } + fn GetDoubleAttributeNullable(&self) -> Option<Finite<f64>> { + Some(Finite::wrap(0.)) + } fn SetDoubleAttributeNullable(&self, _: Option<Finite<f64>>) {} - fn GetByteStringAttributeNullable(&self) -> Option<ByteString> { Some(ByteString::new(vec!())) } + fn GetByteStringAttributeNullable(&self) -> Option<ByteString> { + Some(ByteString::new(vec![])) + } fn SetByteStringAttributeNullable(&self, _: Option<ByteString>) {} - fn GetStringAttributeNullable(&self) -> Option<DOMString> { Some(DOMString::new()) } + fn GetStringAttributeNullable(&self) -> Option<DOMString> { + Some(DOMString::new()) + } fn SetStringAttributeNullable(&self, _: Option<DOMString>) {} - fn GetUsvstringAttributeNullable(&self) -> Option<USVString> { Some(USVString("".to_owned())) } + fn GetUsvstringAttributeNullable(&self) -> Option<USVString> { + Some(USVString("".to_owned())) + } fn SetUsvstringAttributeNullable(&self, _: Option<USVString>) {} fn SetBinaryRenamedAttribute(&self, _: DOMString) {} - fn ForwardedAttribute(&self) -> Root<TestBinding> { Root::from_ref(self) } - fn BinaryRenamedAttribute(&self) -> DOMString { DOMString::new() } + fn ForwardedAttribute(&self) -> DomRoot<TestBinding> { + DomRoot::from_ref(self) + } + fn BinaryRenamedAttribute(&self) -> DOMString { + DOMString::new() + } fn SetBinaryRenamedAttribute2(&self, _: DOMString) {} - fn BinaryRenamedAttribute2(&self) -> DOMString { DOMString::new() } - fn Attr_to_automatically_rename(&self) -> DOMString { DOMString::new() } + fn BinaryRenamedAttribute2(&self) -> DOMString { + DOMString::new() + } + fn Attr_to_automatically_rename(&self) -> DOMString { + DOMString::new() + } fn SetAttr_to_automatically_rename(&self, _: DOMString) {} - fn GetEnumAttributeNullable(&self) -> Option<TestEnum> { Some(TestEnum::_empty) } - fn GetInterfaceAttributeNullable(&self) -> Option<Root<Blob>> { - Some(Blob::new(&self.global(), BlobImpl::new_from_bytes(vec![]), "".to_owned())) + fn GetEnumAttributeNullable(&self) -> Option<TestEnum> { + Some(TestEnum::_empty) + } + fn GetInterfaceAttributeNullable(&self) -> Option<DomRoot<Blob>> { + Some(Blob::new( + &self.global(), + BlobImpl::new_from_bytes(vec![], "".to_owned()), + )) } fn SetInterfaceAttributeNullable(&self, _: Option<&Blob>) {} - fn GetInterfaceAttributeWeak(&self) -> Option<Root<URL>> { + fn GetInterfaceAttributeWeak(&self) -> Option<DomRoot<URL>> { self.url.root() } fn SetInterfaceAttributeWeak(&self, url: Option<&URL>) { self.url.set(url); } - #[allow(unsafe_code)] - unsafe fn GetObjectAttributeNullable(&self, _: *mut JSContext) -> Option<NonZero<*mut JSObject>> { None } - #[allow(unsafe_code)] - unsafe fn SetObjectAttributeNullable(&self, _: *mut JSContext, _: *mut JSObject) {} + fn GetObjectAttributeNullable(&self, _: SafeJSContext) -> Option<NonNull<JSObject>> { + None + } + fn SetObjectAttributeNullable(&self, _: SafeJSContext, _: *mut JSObject) {} fn GetUnionAttributeNullable(&self) -> Option<HTMLElementOrLong> { Some(HTMLElementOrLong::Long(0)) } @@ -244,79 +355,182 @@ impl TestBindingMethods for TestBinding { } fn SetUnion5AttributeNullable(&self, _: Option<StringOrBoolean>) {} fn GetUnion6AttributeNullable(&self) -> Option<ByteStringOrLong> { - Some(ByteStringOrLong::ByteString(ByteString::new(vec!()))) + Some(ByteStringOrLong::ByteString(ByteString::new(vec![]))) } fn SetUnion6AttributeNullable(&self, _: Option<ByteStringOrLong>) {} fn BinaryRenamedMethod(&self) {} fn ReceiveVoid(&self) {} - fn ReceiveBoolean(&self) -> bool { false } - fn ReceiveByte(&self) -> i8 { 0 } - fn ReceiveOctet(&self) -> u8 { 0 } - fn ReceiveShort(&self) -> i16 { 0 } - fn ReceiveUnsignedShort(&self) -> u16 { 0 } - fn ReceiveLong(&self) -> i32 { 0 } - fn ReceiveUnsignedLong(&self) -> u32 { 0 } - fn ReceiveLongLong(&self) -> i64 { 0 } - fn ReceiveUnsignedLongLong(&self) -> u64 { 0 } - fn ReceiveUnrestrictedFloat(&self) -> f32 { 0. } - fn ReceiveFloat(&self) -> Finite<f32> { Finite::wrap(0.) } - fn ReceiveUnrestrictedDouble(&self) -> f64 { 0. } - fn ReceiveDouble(&self) -> Finite<f64> { Finite::wrap(0.) } - fn ReceiveString(&self) -> DOMString { DOMString::new() } - fn ReceiveUsvstring(&self) -> USVString { USVString("".to_owned()) } - fn ReceiveByteString(&self) -> ByteString { ByteString::new(vec!()) } - fn ReceiveEnum(&self) -> TestEnum { TestEnum::_empty } - fn ReceiveInterface(&self) -> Root<Blob> { - Blob::new(&self.global(), BlobImpl::new_from_bytes(vec![]), "".to_owned()) + fn ReceiveBoolean(&self) -> bool { + false } - #[allow(unsafe_code)] - unsafe fn ReceiveAny(&self, _: *mut JSContext) -> JSVal { NullValue() } - #[allow(unsafe_code)] - unsafe fn ReceiveObject(&self, cx: *mut JSContext) -> NonZero<*mut JSObject> { + fn ReceiveByte(&self) -> i8 { + 0 + } + fn ReceiveOctet(&self) -> u8 { + 0 + } + fn ReceiveShort(&self) -> i16 { + 0 + } + fn ReceiveUnsignedShort(&self) -> u16 { + 0 + } + fn ReceiveLong(&self) -> i32 { + 0 + } + fn ReceiveUnsignedLong(&self) -> u32 { + 0 + } + fn ReceiveLongLong(&self) -> i64 { + 0 + } + fn ReceiveUnsignedLongLong(&self) -> u64 { + 0 + } + fn ReceiveUnrestrictedFloat(&self) -> f32 { + 0. + } + fn ReceiveFloat(&self) -> Finite<f32> { + Finite::wrap(0.) + } + fn ReceiveUnrestrictedDouble(&self) -> f64 { + 0. + } + fn ReceiveDouble(&self) -> Finite<f64> { + Finite::wrap(0.) + } + fn ReceiveString(&self) -> DOMString { + DOMString::new() + } + fn ReceiveUsvstring(&self) -> USVString { + USVString("".to_owned()) + } + fn ReceiveByteString(&self) -> ByteString { + ByteString::new(vec![]) + } + fn ReceiveEnum(&self) -> TestEnum { + TestEnum::_empty + } + fn ReceiveInterface(&self) -> DomRoot<Blob> { + Blob::new( + &self.global(), + BlobImpl::new_from_bytes(vec![], "".to_owned()), + ) + } + fn ReceiveAny(&self, _: SafeJSContext) -> JSVal { + NullValue() + } + fn ReceiveObject(&self, cx: SafeJSContext) -> NonNull<JSObject> { self.ObjectAttribute(cx) } - fn ReceiveUnion(&self) -> HTMLElementOrLong { HTMLElementOrLong::Long(0) } - fn ReceiveUnion2(&self) -> EventOrString { EventOrString::String(DOMString::new()) } - fn ReceiveUnion3(&self) -> StringOrLongSequence { StringOrLongSequence::LongSequence(vec![]) } - fn ReceiveUnion4(&self) -> StringOrStringSequence { StringOrStringSequence::StringSequence(vec![]) } - fn ReceiveUnion5(&self) -> BlobOrBlobSequence { BlobOrBlobSequence::BlobSequence(vec![]) } - fn ReceiveUnion6(&self) -> StringOrUnsignedLong { StringOrUnsignedLong::String(DOMString::new()) } - fn ReceiveUnion7(&self) -> StringOrBoolean { StringOrBoolean::Boolean(true) } - fn ReceiveUnion8(&self) -> UnsignedLongOrBoolean { UnsignedLongOrBoolean::UnsignedLong(0u32) } + fn ReceiveUnion(&self) -> HTMLElementOrLong { + HTMLElementOrLong::Long(0) + } + fn ReceiveUnion2(&self) -> EventOrString { + EventOrString::String(DOMString::new()) + } + fn ReceiveUnion3(&self) -> StringOrLongSequence { + StringOrLongSequence::LongSequence(vec![]) + } + fn ReceiveUnion4(&self) -> StringOrStringSequence { + StringOrStringSequence::StringSequence(vec![]) + } + fn ReceiveUnion5(&self) -> BlobOrBlobSequence { + BlobOrBlobSequence::BlobSequence(vec![]) + } + fn ReceiveUnion6(&self) -> StringOrUnsignedLong { + StringOrUnsignedLong::String(DOMString::new()) + } + fn ReceiveUnion7(&self) -> StringOrBoolean { + StringOrBoolean::Boolean(true) + } + fn ReceiveUnion8(&self) -> UnsignedLongOrBoolean { + UnsignedLongOrBoolean::UnsignedLong(0u32) + } fn ReceiveUnion9(&self) -> HTMLElementOrUnsignedLongOrStringOrBoolean { HTMLElementOrUnsignedLongOrStringOrBoolean::Boolean(true) } - fn ReceiveUnion10(&self) -> ByteStringOrLong { ByteStringOrLong::ByteString(ByteString::new(vec!())) } + fn ReceiveUnion10(&self) -> ByteStringOrLong { + ByteStringOrLong::ByteString(ByteString::new(vec![])) + } fn ReceiveUnion11(&self) -> ByteStringSequenceOrLongOrString { - ByteStringSequenceOrLongOrString::ByteStringSequence(vec!(ByteString::new(vec!()))) - } - fn ReceiveSequence(&self) -> Vec<i32> { vec![1] } - fn ReceiveInterfaceSequence(&self) -> Vec<Root<Blob>> { - vec![Blob::new(&self.global(), BlobImpl::new_from_bytes(vec![]), "".to_owned())] - } - - fn ReceiveNullableBoolean(&self) -> Option<bool> { Some(false) } - fn ReceiveNullableByte(&self) -> Option<i8> { Some(0) } - fn ReceiveNullableOctet(&self) -> Option<u8> { Some(0) } - fn ReceiveNullableShort(&self) -> Option<i16> { Some(0) } - fn ReceiveNullableUnsignedShort(&self) -> Option<u16> { Some(0) } - fn ReceiveNullableLong(&self) -> Option<i32> { Some(0) } - fn ReceiveNullableUnsignedLong(&self) -> Option<u32> { Some(0) } - fn ReceiveNullableLongLong(&self) -> Option<i64> { Some(0) } - fn ReceiveNullableUnsignedLongLong(&self) -> Option<u64> { Some(0) } - fn ReceiveNullableUnrestrictedFloat(&self) -> Option<f32> { Some(0.) } - fn ReceiveNullableFloat(&self) -> Option<Finite<f32>> { Some(Finite::wrap(0.)) } - fn ReceiveNullableUnrestrictedDouble(&self) -> Option<f64> { Some(0.) } - fn ReceiveNullableDouble(&self) -> Option<Finite<f64>> { Some(Finite::wrap(0.)) } - fn ReceiveNullableString(&self) -> Option<DOMString> { Some(DOMString::new()) } - fn ReceiveNullableUsvstring(&self) -> Option<USVString> { Some(USVString("".to_owned())) } - fn ReceiveNullableByteString(&self) -> Option<ByteString> { Some(ByteString::new(vec!())) } - fn ReceiveNullableEnum(&self) -> Option<TestEnum> { Some(TestEnum::_empty) } - fn ReceiveNullableInterface(&self) -> Option<Root<Blob>> { - Some(Blob::new(&self.global(), BlobImpl::new_from_bytes(vec![]), "".to_owned())) + ByteStringSequenceOrLongOrString::ByteStringSequence(vec![ByteString::new(vec![])]) } - #[allow(unsafe_code)] - unsafe fn ReceiveNullableObject(&self, cx: *mut JSContext) -> Option<NonZero<*mut JSObject>> { + fn ReceiveSequence(&self) -> Vec<i32> { + vec![1] + } + fn ReceiveInterfaceSequence(&self) -> Vec<DomRoot<Blob>> { + vec![Blob::new( + &self.global(), + BlobImpl::new_from_bytes(vec![], "".to_owned()), + )] + } + fn ReceiveUnionIdentity( + &self, + _: SafeJSContext, + arg: UnionTypes::StringOrObject, + ) -> UnionTypes::StringOrObject { + arg + } + + fn ReceiveNullableBoolean(&self) -> Option<bool> { + Some(false) + } + fn ReceiveNullableByte(&self) -> Option<i8> { + Some(0) + } + fn ReceiveNullableOctet(&self) -> Option<u8> { + Some(0) + } + fn ReceiveNullableShort(&self) -> Option<i16> { + Some(0) + } + fn ReceiveNullableUnsignedShort(&self) -> Option<u16> { + Some(0) + } + fn ReceiveNullableLong(&self) -> Option<i32> { + Some(0) + } + fn ReceiveNullableUnsignedLong(&self) -> Option<u32> { + Some(0) + } + fn ReceiveNullableLongLong(&self) -> Option<i64> { + Some(0) + } + fn ReceiveNullableUnsignedLongLong(&self) -> Option<u64> { + Some(0) + } + fn ReceiveNullableUnrestrictedFloat(&self) -> Option<f32> { + Some(0.) + } + fn ReceiveNullableFloat(&self) -> Option<Finite<f32>> { + Some(Finite::wrap(0.)) + } + fn ReceiveNullableUnrestrictedDouble(&self) -> Option<f64> { + Some(0.) + } + fn ReceiveNullableDouble(&self) -> Option<Finite<f64>> { + Some(Finite::wrap(0.)) + } + fn ReceiveNullableString(&self) -> Option<DOMString> { + Some(DOMString::new()) + } + fn ReceiveNullableUsvstring(&self) -> Option<USVString> { + Some(USVString("".to_owned())) + } + fn ReceiveNullableByteString(&self) -> Option<ByteString> { + Some(ByteString::new(vec![])) + } + fn ReceiveNullableEnum(&self) -> Option<TestEnum> { + Some(TestEnum::_empty) + } + fn ReceiveNullableInterface(&self) -> Option<DomRoot<Blob>> { + Some(Blob::new( + &self.global(), + BlobImpl::new_from_bytes(vec![], "".to_owned()), + )) + } + fn ReceiveNullableObject(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> { self.GetObjectAttributeNullable(cx) } fn ReceiveNullableUnion(&self) -> Option<HTMLElementOrLong> { @@ -335,17 +549,20 @@ impl TestBindingMethods for TestBinding { Some(UnsignedLongOrBoolean::UnsignedLong(0u32)) } fn ReceiveNullableUnion6(&self) -> Option<ByteStringOrLong> { - Some(ByteStringOrLong::ByteString(ByteString::new(vec!()))) + Some(ByteStringOrLong::ByteString(ByteString::new(vec![]))) + } + fn ReceiveNullableSequence(&self) -> Option<Vec<i32>> { + Some(vec![1]) } - fn ReceiveNullableSequence(&self) -> Option<Vec<i32>> { Some(vec![1]) } - fn ReceiveTestDictionaryWithSuccessOnKeyword(&self) -> TestDictionary { - TestDictionary { - anyValue: Heap::new(NullValue()), + fn ReceiveTestDictionaryWithSuccessOnKeyword(&self) -> RootedTraceableBox<TestDictionary> { + RootedTraceableBox::new(TestDictionary { + anyValue: RootedTraceableBox::new(Heap::default()), booleanValue: None, byteValue: None, - dict: TestDictionaryDefaults { + dict: RootedTraceableBox::new(TestDictionaryDefaults { UnrestrictedDoubleValue: 0.0, - anyValue: Heap::new(NullValue()), + anyValue: RootedTraceableBox::new(Heap::default()), + arrayValue: Vec::new(), booleanValue: false, bytestringValue: ByteString::new(vec![]), byteValue: 0, @@ -361,7 +578,7 @@ impl TestBindingMethods for TestBinding { nullableFloatValue: None, nullableLongLongValue: None, nullableLongValue: None, - nullableObjectValue: Heap::new(ptr::null_mut()), + nullableObjectValue: RootedTraceableBox::new(Heap::default()), nullableOctetValue: None, nullableShortValue: None, nullableStringValue: None, @@ -379,7 +596,7 @@ impl TestBindingMethods for TestBinding { unsignedLongValue: 0, unsignedShortValue: 0, usvstringValue: USVString("".to_owned()), - }, + }), doubleValue: None, enumValue: None, floatValue: None, @@ -390,6 +607,7 @@ impl TestBindingMethods for TestBinding { octetValue: None, requiredValue: true, seqDict: None, + elementSequence: None, shortValue: None, stringValue: None, type_: Some(DOMString::from("success")), @@ -401,7 +619,7 @@ impl TestBindingMethods for TestBinding { usvstringValue: None, nonRequiredNullable: None, nonRequiredNullable2: Some(None), // null - } + }) } fn DictMatchesPassedValues(&self, arg: RootedTraceableBox<TestDictionary>) -> bool { @@ -428,6 +646,9 @@ impl TestBindingMethods for TestBinding { fn PassByteString(&self, _: ByteString) {} fn PassEnum(&self, _: TestEnum) {} fn PassInterface(&self, _: &Blob) {} + fn PassTypedArray(&self, _: CustomAutoRooterGuard<typedarray::Int8Array>) {} + fn PassTypedArray2(&self, _: CustomAutoRooterGuard<typedarray::ArrayBuffer>) {} + fn PassTypedArray3(&self, _: CustomAutoRooterGuard<typedarray::ArrayBufferView>) {} fn PassUnion(&self, _: HTMLElementOrLong) {} fn PassUnion2(&self, _: EventOrString) {} fn PassUnion3(&self, _: BlobOrString) {} @@ -436,20 +657,38 @@ impl TestBindingMethods for TestBinding { fn PassUnion6(&self, _: UnsignedLongOrBoolean) {} fn PassUnion7(&self, _: StringSequenceOrUnsignedLong) {} fn PassUnion8(&self, _: ByteStringSequenceOrLong) {} - fn PassUnion9(&self, _: RootedTraceableBox<UnionTypes::TestDictionaryOrLong>) {} - #[allow(unsafe_code)] - unsafe fn PassUnion10(&self, _: *mut JSContext, _: RootedTraceableBox<UnionTypes::StringOrObject>) {} + fn PassUnion9(&self, _: UnionTypes::TestDictionaryOrLong) {} + fn PassUnion10(&self, _: SafeJSContext, _: UnionTypes::StringOrObject) {} + fn PassUnion11(&self, _: UnionTypes::ArrayBufferOrArrayBufferView) {} fn PassUnionWithTypedef(&self, _: DocumentOrTestTypedef) {} fn PassUnionWithTypedef2(&self, _: LongSequenceOrTestTypedef) {} - #[allow(unsafe_code)] - unsafe fn PassAny(&self, _: *mut JSContext, _: HandleValue) {} - #[allow(unsafe_code)] - unsafe fn PassObject(&self, _: *mut JSContext, _: *mut JSObject) {} + fn PassAny(&self, _: SafeJSContext, _: HandleValue) {} + fn PassObject(&self, _: SafeJSContext, _: *mut JSObject) {} fn PassCallbackFunction(&self, _: Rc<Function>) {} fn PassCallbackInterface(&self, _: Rc<EventListener>) {} fn PassSequence(&self, _: Vec<i32>) {} + fn PassAnySequence(&self, _: SafeJSContext, _: CustomAutoRooterGuard<Vec<JSVal>>) {} + fn AnySequencePassthrough( + &self, + _: SafeJSContext, + seq: CustomAutoRooterGuard<Vec<JSVal>>, + ) -> Vec<JSVal> { + (*seq).clone() + } + fn PassObjectSequence(&self, _: SafeJSContext, _: CustomAutoRooterGuard<Vec<*mut JSObject>>) {} fn PassStringSequence(&self, _: Vec<DOMString>) {} - fn PassInterfaceSequence(&self, _: Vec<Root<Blob>>) {} + fn PassInterfaceSequence(&self, _: Vec<DomRoot<Blob>>) {} + + fn PassOverloaded(&self, _: CustomAutoRooterGuard<typedarray::ArrayBuffer>) {} + fn PassOverloaded_(&self, _: DOMString) {} + + fn PassOverloadedDict(&self, _: &Node) -> DOMString { + "node".into() + } + + fn PassOverloadedDict_(&self, u: &TestURLLike) -> DOMString { + u.href.clone() + } fn PassNullableBoolean(&self, _: Option<bool>) {} fn PassNullableByte(&self, _: Option<i8>) {} @@ -469,8 +708,8 @@ impl TestBindingMethods for TestBinding { fn PassNullableByteString(&self, _: Option<ByteString>) {} // fn PassNullableEnum(self, _: Option<TestEnum>) {} fn PassNullableInterface(&self, _: Option<&Blob>) {} - #[allow(unsafe_code)] - unsafe fn PassNullableObject(&self, _: *mut JSContext, _: *mut JSObject) {} + fn PassNullableObject(&self, _: SafeJSContext, _: *mut JSObject) {} + fn PassNullableTypedArray(&self, _: CustomAutoRooterGuard<Option<typedarray::Int8Array>>) {} fn PassNullableUnion(&self, _: Option<HTMLElementOrLong>) {} fn PassNullableUnion2(&self, _: Option<EventOrString>) {} fn PassNullableUnion3(&self, _: Option<StringOrLongSequence>) {} @@ -505,10 +744,8 @@ impl TestBindingMethods for TestBinding { fn PassOptionalUnion4(&self, _: Option<LongSequenceOrBoolean>) {} fn PassOptionalUnion5(&self, _: Option<UnsignedLongOrBoolean>) {} fn PassOptionalUnion6(&self, _: Option<ByteStringOrLong>) {} - #[allow(unsafe_code)] - unsafe fn PassOptionalAny(&self, _: *mut JSContext, _: HandleValue) {} - #[allow(unsafe_code)] - unsafe fn PassOptionalObject(&self, _: *mut JSContext, _: Option<*mut JSObject>) {} + fn PassOptionalAny(&self, _: SafeJSContext, _: HandleValue) {} + fn PassOptionalObject(&self, _: SafeJSContext, _: Option<*mut JSObject>) {} fn PassOptionalCallbackFunction(&self, _: Option<Rc<Function>>) {} fn PassOptionalCallbackInterface(&self, _: Option<Rc<EventListener>>) {} fn PassOptionalSequence(&self, _: Option<Vec<i32>>) {} @@ -531,8 +768,7 @@ impl TestBindingMethods for TestBinding { fn PassOptionalNullableByteString(&self, _: Option<Option<ByteString>>) {} // fn PassOptionalNullableEnum(self, _: Option<Option<TestEnum>>) {} fn PassOptionalNullableInterface(&self, _: Option<Option<&Blob>>) {} - #[allow(unsafe_code)] - unsafe fn PassOptionalNullableObject(&self, _: *mut JSContext, _: Option<*mut JSObject>) {} + fn PassOptionalNullableObject(&self, _: SafeJSContext, _: Option<*mut JSObject>) {} fn PassOptionalNullableUnion(&self, _: Option<Option<HTMLElementOrLong>>) {} fn PassOptionalNullableUnion2(&self, _: Option<Option<EventOrString>>) {} fn PassOptionalNullableUnion3(&self, _: Option<Option<StringOrLongSequence>>) {} @@ -556,6 +792,7 @@ impl TestBindingMethods for TestBinding { fn PassOptionalUsvstringWithDefault(&self, _: USVString) {} fn PassOptionalBytestringWithDefault(&self, _: ByteString) {} fn PassOptionalEnumWithDefault(&self, _: TestEnum) {} + fn PassOptionalSequenceWithDefault(&self, _: Vec<i32>) {} fn PassOptionalNullableBooleanWithDefault(&self, _: Option<bool>) {} fn PassOptionalNullableByteWithDefault(&self, _: Option<i8>) {} @@ -575,14 +812,12 @@ impl TestBindingMethods for TestBinding { fn PassOptionalNullableByteStringWithDefault(&self, _: Option<ByteString>) {} // fn PassOptionalNullableEnumWithDefault(self, _: Option<TestEnum>) {} fn PassOptionalNullableInterfaceWithDefault(&self, _: Option<&Blob>) {} - #[allow(unsafe_code)] - unsafe fn PassOptionalNullableObjectWithDefault(&self, _: *mut JSContext, _: *mut JSObject) {} + fn PassOptionalNullableObjectWithDefault(&self, _: SafeJSContext, _: *mut JSObject) {} fn PassOptionalNullableUnionWithDefault(&self, _: Option<HTMLElementOrLong>) {} fn PassOptionalNullableUnion2WithDefault(&self, _: Option<EventOrString>) {} // fn PassOptionalNullableCallbackFunctionWithDefault(self, _: Option<Function>) {} fn PassOptionalNullableCallbackInterfaceWithDefault(&self, _: Option<Rc<EventListener>>) {} - #[allow(unsafe_code)] - unsafe fn PassOptionalAnyWithDefault(&self, _: *mut JSContext, _: HandleValue) {} + fn PassOptionalAnyWithDefault(&self, _: SafeJSContext, _: HandleValue) {} fn PassOptionalNullableBooleanWithNonNullDefault(&self, _: Option<bool>) {} fn PassOptionalNullableByteWithNonNullDefault(&self, _: Option<i8>) {} @@ -600,6 +835,10 @@ impl TestBindingMethods for TestBinding { fn PassOptionalNullableStringWithNonNullDefault(&self, _: Option<DOMString>) {} fn PassOptionalNullableUsvstringWithNonNullDefault(&self, _: Option<USVString>) {} // fn PassOptionalNullableEnumWithNonNullDefault(self, _: Option<TestEnum>) {} + fn PassOptionalOverloaded(&self, a: &TestBinding, _: u32, _: u32) -> DomRoot<TestBinding> { + DomRoot::from_ref(a) + } + fn PassOptionalOverloaded_(&self, _: &Blob, _: u32) {} fn PassVariadicBoolean(&self, _: Vec<bool>) {} fn PassVariadicBooleanAndDefault(&self, _: bool, _: Vec<bool>) {} @@ -627,75 +866,118 @@ impl TestBindingMethods for TestBinding { fn PassVariadicUnion5(&self, _: Vec<StringOrUnsignedLong>) {} fn PassVariadicUnion6(&self, _: Vec<UnsignedLongOrBoolean>) {} fn PassVariadicUnion7(&self, _: Vec<ByteStringOrLong>) {} - #[allow(unsafe_code)] - unsafe fn PassVariadicAny(&self, _: *mut JSContext, _: Vec<HandleValue>) {} - #[allow(unsafe_code)] - unsafe fn PassVariadicObject(&self, _: *mut JSContext, _: Vec<*mut JSObject>) {} + fn PassVariadicAny(&self, _: SafeJSContext, _: Vec<HandleValue>) {} + fn PassVariadicObject(&self, _: SafeJSContext, _: Vec<*mut JSObject>) {} fn BooleanMozPreference(&self, pref_name: DOMString) -> bool { - PREFS.get(pref_name.as_ref()).as_boolean().unwrap_or(false) + prefs::pref_map() + .get(pref_name.as_ref()) + .as_bool() + .unwrap_or(false) } fn StringMozPreference(&self, pref_name: DOMString) -> DOMString { - PREFS.get(pref_name.as_ref()).as_string().map(|s| DOMString::from(s)).unwrap_or_else(|| DOMString::new()) + prefs::pref_map() + .get(pref_name.as_ref()) + .as_str() + .map(|s| DOMString::from(s)) + .unwrap_or_else(|| DOMString::new()) + } + fn PrefControlledAttributeDisabled(&self) -> bool { + false + } + fn PrefControlledAttributeEnabled(&self) -> bool { + false } - fn PrefControlledAttributeDisabled(&self) -> bool { false } - fn PrefControlledAttributeEnabled(&self) -> bool { false } fn PrefControlledMethodDisabled(&self) {} fn PrefControlledMethodEnabled(&self) {} - fn FuncControlledAttributeDisabled(&self) -> bool { false } - fn FuncControlledAttributeEnabled(&self) -> bool { false } + fn FuncControlledAttributeDisabled(&self) -> bool { + false + } + fn FuncControlledAttributeEnabled(&self) -> bool { + false + } fn FuncControlledMethodDisabled(&self) {} fn FuncControlledMethodEnabled(&self) {} - fn PassMozMap(&self, _: MozMap<i32>) {} - fn PassNullableMozMap(&self, _: Option<MozMap<i32> >) {} - fn PassMozMapOfNullableInts(&self, _: MozMap<Option<i32>>) {} - fn PassOptionalMozMapOfNullableInts(&self, _: Option<MozMap<Option<i32>>>) {} - fn PassOptionalNullableMozMapOfNullableInts(&self, _: Option<Option<MozMap<Option<i32>> >>) {} - fn PassCastableObjectMozMap(&self, _: MozMap<Root<TestBinding>>) {} - fn PassNullableCastableObjectMozMap(&self, _: MozMap<Option<Root<TestBinding>>>) {} - fn PassCastableObjectNullableMozMap(&self, _: Option<MozMap<Root<TestBinding>>>) {} - fn PassNullableCastableObjectNullableMozMap(&self, _: Option<MozMap<Option<Root<TestBinding>>>>) {} - fn PassOptionalMozMap(&self, _: Option<MozMap<i32>>) {} - fn PassOptionalNullableMozMap(&self, _: Option<Option<MozMap<i32>>>) {} - fn PassOptionalNullableMozMapWithDefaultValue(&self, _: Option<MozMap<i32>>) {} - fn PassOptionalObjectMozMap(&self, _: Option<MozMap<Root<TestBinding>>>) {} - fn PassStringMozMap(&self, _: MozMap<DOMString>) {} - fn PassByteStringMozMap(&self, _: MozMap<ByteString>) {} - fn PassMozMapOfMozMaps(&self, _: MozMap<MozMap<i32>>) {} - fn PassMozMapUnion(&self, _: UnionTypes::LongOrByteStringMozMap) {} - fn PassMozMapUnion2(&self, _: UnionTypes::TestBindingOrByteStringMozMap) {} - fn PassMozMapUnion3(&self, _: UnionTypes::TestBindingOrByteStringSequenceSequenceOrByteStringMozMap) {} - fn ReceiveMozMap(&self) -> MozMap<i32> { MozMap::new() } - fn ReceiveNullableMozMap(&self) -> Option<MozMap<i32>> { Some(MozMap::new()) } - fn ReceiveMozMapOfNullableInts(&self) -> MozMap<Option<i32>> { MozMap::new() } - fn ReceiveNullableMozMapOfNullableInts(&self) -> Option<MozMap<Option<i32>>> { Some(MozMap::new()) } - fn ReceiveMozMapOfMozMaps(&self) -> MozMap<MozMap<i32>> { MozMap::new() } - fn ReceiveAnyMozMap(&self) -> MozMap<JSVal> { MozMap::new() } + fn PassRecord(&self, _: Record<DOMString, i32>) {} + fn PassRecordWithUSVStringKey(&self, _: Record<USVString, i32>) {} + fn PassRecordWithByteStringKey(&self, _: Record<ByteString, i32>) {} + fn PassNullableRecord(&self, _: Option<Record<DOMString, i32>>) {} + fn PassRecordOfNullableInts(&self, _: Record<DOMString, Option<i32>>) {} + fn PassOptionalRecordOfNullableInts(&self, _: Option<Record<DOMString, Option<i32>>>) {} + fn PassOptionalNullableRecordOfNullableInts( + &self, + _: Option<Option<Record<DOMString, Option<i32>>>>, + ) { + } + fn PassCastableObjectRecord(&self, _: Record<DOMString, DomRoot<TestBinding>>) {} + fn PassNullableCastableObjectRecord(&self, _: Record<DOMString, Option<DomRoot<TestBinding>>>) { + } + fn PassCastableObjectNullableRecord(&self, _: Option<Record<DOMString, DomRoot<TestBinding>>>) { + } + fn PassNullableCastableObjectNullableRecord( + &self, + _: Option<Record<DOMString, Option<DomRoot<TestBinding>>>>, + ) { + } + fn PassOptionalRecord(&self, _: Option<Record<DOMString, i32>>) {} + fn PassOptionalNullableRecord(&self, _: Option<Option<Record<DOMString, i32>>>) {} + fn PassOptionalNullableRecordWithDefaultValue(&self, _: Option<Record<DOMString, i32>>) {} + fn PassOptionalObjectRecord(&self, _: Option<Record<DOMString, DomRoot<TestBinding>>>) {} + fn PassStringRecord(&self, _: Record<DOMString, DOMString>) {} + fn PassByteStringRecord(&self, _: Record<DOMString, ByteString>) {} + fn PassRecordOfRecords(&self, _: Record<DOMString, Record<DOMString, i32>>) {} + fn PassRecordUnion(&self, _: UnionTypes::LongOrStringByteStringRecord) {} + fn PassRecordUnion2(&self, _: UnionTypes::TestBindingOrStringByteStringRecord) {} + fn PassRecordUnion3( + &self, + _: UnionTypes::TestBindingOrByteStringSequenceSequenceOrStringByteStringRecord, + ) { + } + fn ReceiveRecord(&self) -> Record<DOMString, i32> { + Record::new() + } + fn ReceiveRecordWithUSVStringKey(&self) -> Record<USVString, i32> { + Record::new() + } + fn ReceiveRecordWithByteStringKey(&self) -> Record<ByteString, i32> { + Record::new() + } + fn ReceiveNullableRecord(&self) -> Option<Record<DOMString, i32>> { + Some(Record::new()) + } + fn ReceiveRecordOfNullableInts(&self) -> Record<DOMString, Option<i32>> { + Record::new() + } + fn ReceiveNullableRecordOfNullableInts(&self) -> Option<Record<DOMString, Option<i32>>> { + Some(Record::new()) + } + fn ReceiveRecordOfRecords(&self) -> Record<DOMString, Record<DOMString, i32>> { + Record::new() + } + fn ReceiveAnyRecord(&self) -> Record<DOMString, JSVal> { + Record::new() + } #[allow(unrooted_must_root)] - #[allow(unsafe_code)] - unsafe fn ReturnResolvedPromise(&self, cx: *mut JSContext, v: HandleValue) -> Fallible<Rc<Promise>> { - Promise::Resolve(&self.global(), cx, v) + fn ReturnResolvedPromise(&self, cx: SafeJSContext, v: HandleValue) -> Fallible<Rc<Promise>> { + Promise::new_resolved(&self.global(), cx, v) } #[allow(unrooted_must_root)] - #[allow(unsafe_code)] - unsafe fn ReturnRejectedPromise(&self, cx: *mut JSContext, v: HandleValue) -> Fallible<Rc<Promise>> { - Promise::Reject(&self.global(), cx, v) + fn ReturnRejectedPromise(&self, cx: SafeJSContext, v: HandleValue) -> Fallible<Rc<Promise>> { + Promise::new_rejected(&self.global(), cx, v) } - #[allow(unsafe_code)] - unsafe fn PromiseResolveNative(&self, cx: *mut JSContext, p: &Promise, v: HandleValue) { + fn PromiseResolveNative(&self, cx: SafeJSContext, p: &Promise, v: HandleValue) { p.resolve(cx, v); } - #[allow(unsafe_code)] - unsafe fn PromiseRejectNative(&self, cx: *mut JSContext, p: &Promise, v: HandleValue) { + fn PromiseRejectNative(&self, cx: SafeJSContext, p: &Promise, v: HandleValue) { p.reject(cx, v); } fn PromiseRejectWithTypeError(&self, p: &Promise, s: USVString) { - p.reject_error(self.global().get_cx(), Error::Type(s.0)); + p.reject_error(Error::Type(s.0)); } #[allow(unrooted_must_root)] @@ -705,62 +987,62 @@ impl TestBindingMethods for TestBinding { promise: TrustedPromise::new(promise), value: value, }; - let _ = self.global() - .schedule_callback( - OneshotTimerCallback::TestBindingCallback(cb), - MsDuration::new(delay)); + let _ = self.global().schedule_callback( + OneshotTimerCallback::TestBindingCallback(cb), + MsDuration::new(delay), + ); } - #[allow(unrooted_must_root)] - fn PromiseNativeHandler(&self, - resolve: Option<Rc<SimpleCallback>>, - reject: Option<Rc<SimpleCallback>>) -> Rc<Promise> { + fn PromiseNativeHandler( + &self, + resolve: Option<Rc<SimpleCallback>>, + reject: Option<Rc<SimpleCallback>>, + comp: InRealm, + ) -> Rc<Promise> { let global = self.global(); - let handler = PromiseNativeHandler::new(&global, - resolve.map(SimpleHandler::new), - reject.map(SimpleHandler::new)); - let p = Promise::new(&global); - p.append_native_handler(&handler); + let handler = PromiseNativeHandler::new( + &global, + resolve.map(SimpleHandler::new), + reject.map(SimpleHandler::new), + ); + let p = Promise::new_in_current_realm(&global, comp.clone()); + p.append_native_handler(&handler, comp); return p; - #[derive(JSTraceable, HeapSizeOf)] + #[derive(JSTraceable, MallocSizeOf)] struct SimpleHandler { - #[ignore_heap_size_of = "Rc has unclear ownership semantics"] + #[ignore_malloc_size_of = "Rc has unclear ownership semantics"] handler: Rc<SimpleCallback>, } impl SimpleHandler { - fn new(callback: Rc<SimpleCallback>) -> Box<Callback> { - box SimpleHandler { handler: callback } + fn new(callback: Rc<SimpleCallback>) -> Box<dyn Callback> { + Box::new(SimpleHandler { handler: callback }) } } impl Callback for SimpleHandler { - #[allow(unsafe_code)] - fn callback(&self, cx: *mut JSContext, v: HandleValue) { - let global = unsafe { GlobalScope::from_context(cx) }; + fn callback(&self, cx: SafeJSContext, v: HandleValue, realm: InRealm) { + let global = GlobalScope::from_safe_context(cx, realm); let _ = self.handler.Call_(&*global, v, ExceptionHandling::Report); } } } - #[allow(unrooted_must_root)] - fn PromiseAttribute(&self) -> Rc<Promise> { - Promise::new(&self.global()) + fn PromiseAttribute(&self, comp: InRealm) -> Rc<Promise> { + Promise::new_in_current_realm(&self.global(), comp) } - fn AcceptPromise(&self, _promise: &Promise) { - } - - fn AcceptNullablePromise(&self, _promise: Option<&Promise>) { - } + fn AcceptPromise(&self, _promise: &Promise) {} fn PassSequenceSequence(&self, _seq: Vec<Vec<i32>>) {} - fn ReturnSequenceSequence(&self) -> Vec<Vec<i32>> { vec![] } + fn ReturnSequenceSequence(&self) -> Vec<Vec<i32>> { + vec![] + } fn PassUnionSequenceSequence(&self, seq: LongOrLongSequenceSequence) { match seq { LongOrLongSequenceSequence::Long(_) => (), LongOrLongSequenceSequence::LongSequenceSequence(seq) => { let _seq: Vec<Vec<i32>> = seq; - } + }, } } @@ -773,43 +1055,80 @@ impl TestBindingMethods for TestBinding { } } - fn AdvanceClock(&self, ms: i32, tick: bool) { - self.global().as_window().advance_animation_clock(ms, tick); + fn AdvanceClock(&self, ms: i32) { + self.global().as_window().advance_animation_clock(ms); } - fn Panic(&self) { panic!("explicit panic from script") } + fn Panic(&self) { + panic!("explicit panic from script") + } - fn EntryGlobal(&self) -> Root<GlobalScope> { + fn EntryGlobal(&self) -> DomRoot<GlobalScope> { GlobalScope::entry() } - fn IncumbentGlobal(&self) -> Root<GlobalScope> { + fn IncumbentGlobal(&self) -> DomRoot<GlobalScope> { GlobalScope::incumbent().unwrap() } + + fn SemiExposedBoolFromInterface(&self) -> bool { + true + } + + fn BoolFromSemiExposedPartialInterface(&self) -> bool { + true + } + + fn SemiExposedBoolFromPartialInterface(&self) -> bool { + true + } + + fn GetDictionaryWithParent(&self, s1: DOMString, s2: DOMString) -> TestDictionaryWithParent { + TestDictionaryWithParent { + parent: TestDictionaryParent { + parentStringMember: Some(s1), + }, + stringMember: Some(s2), + } + } } +#[allow(non_snake_case)] impl TestBinding { - pub fn BooleanAttributeStatic(_: &GlobalScope) -> bool { false } + pub fn BooleanAttributeStatic(_: &GlobalScope) -> bool { + false + } pub fn SetBooleanAttributeStatic(_: &GlobalScope, _: bool) {} pub fn ReceiveVoidStatic(_: &GlobalScope) {} - pub fn PrefControlledStaticAttributeDisabled(_: &GlobalScope) -> bool { false } - pub fn PrefControlledStaticAttributeEnabled(_: &GlobalScope) -> bool { false } + pub fn PrefControlledStaticAttributeDisabled(_: &GlobalScope) -> bool { + false + } + pub fn PrefControlledStaticAttributeEnabled(_: &GlobalScope) -> bool { + false + } pub fn PrefControlledStaticMethodDisabled(_: &GlobalScope) {} pub fn PrefControlledStaticMethodEnabled(_: &GlobalScope) {} - pub fn FuncControlledStaticAttributeDisabled(_: &GlobalScope) -> bool { false } - pub fn FuncControlledStaticAttributeEnabled(_: &GlobalScope) -> bool { false } + pub fn FuncControlledStaticAttributeDisabled(_: &GlobalScope) -> bool { + false + } + pub fn FuncControlledStaticAttributeEnabled(_: &GlobalScope) -> bool { + false + } pub fn FuncControlledStaticMethodDisabled(_: &GlobalScope) {} pub fn FuncControlledStaticMethodEnabled(_: &GlobalScope) {} } -#[allow(unsafe_code)] impl TestBinding { - pub unsafe fn condition_satisfied(_: *mut JSContext, _: HandleObject) -> bool { true } - pub unsafe fn condition_unsatisfied(_: *mut JSContext, _: HandleObject) -> bool { false } + pub fn condition_satisfied(_: SafeJSContext, _: HandleObject) -> bool { + true + } + pub fn condition_unsatisfied(_: SafeJSContext, _: HandleObject) -> bool { + false + } } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] pub struct TestBindingCallback { - #[ignore_heap_size_of = "unclear ownership semantics"] + #[ignore_malloc_size_of = "unclear ownership semantics"] promise: TrustedPromise, value: DOMString, } @@ -817,9 +1136,6 @@ pub struct TestBindingCallback { impl TestBindingCallback { #[allow(unrooted_must_root)] pub fn invoke(self) { - let p = self.promise.root(); - let cx = p.global().get_cx(); - let _ac = JSAutoCompartment::new(cx, p.reflector().get_jsobject().get()); - p.resolve_native(cx, &self.value); + self.promise.root().resolve_native(&self.value); } } diff --git a/components/script/dom/testbindingiterable.rs b/components/script/dom/testbindingiterable.rs index d89d7345f91..5befaaa2c59 100644 --- a/components/script/dom/testbindingiterable.rs +++ b/components/script/dom/testbindingiterable.rs @@ -1,41 +1,51 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // check-tidy: no specs after this line -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::TestBindingIterableBinding::{self, TestBindingIterableMethods}; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::TestBindingIterableBinding::TestBindingIterableMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; #[dom_struct] pub struct TestBindingIterable { reflector: Reflector, - vals: DOMRefCell<Vec<DOMString>>, + vals: DomRefCell<Vec<DOMString>>, } impl TestBindingIterable { - fn new(global: &GlobalScope) -> Root<TestBindingIterable> { - reflect_dom_object(box TestBindingIterable { - reflector: Reflector::new(), - vals: DOMRefCell::new(vec![]), - }, global, TestBindingIterableBinding::Wrap) + fn new(global: &GlobalScope) -> DomRoot<TestBindingIterable> { + reflect_dom_object( + Box::new(TestBindingIterable { + reflector: Reflector::new(), + vals: DomRefCell::new(vec![]), + }), + global, + ) } - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<TestBindingIterable>> { + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<TestBindingIterable>> { Ok(TestBindingIterable::new(global)) } } impl TestBindingIterableMethods for TestBindingIterable { - fn Add(&self, v: DOMString) { self.vals.borrow_mut().push(v); } - fn Length(&self) -> u32 { self.vals.borrow().len() as u32 } - fn GetItem(&self, n: u32) -> DOMString { self.IndexedGetter(n).unwrap_or_default() } + fn Add(&self, v: DOMString) { + self.vals.borrow_mut().push(v); + } + fn Length(&self) -> u32 { + self.vals.borrow().len() as u32 + } + fn GetItem(&self, n: u32) -> DOMString { + self.IndexedGetter(n).unwrap_or_default() + } fn IndexedGetter(&self, n: u32) -> Option<DOMString> { self.vals.borrow().get(n as usize).cloned() } diff --git a/components/script/dom/testbindingpairiterable.rs b/components/script/dom/testbindingpairiterable.rs index c31d0e34fb5..e8775af5ce7 100644 --- a/components/script/dom/testbindingpairiterable.rs +++ b/components/script/dom/testbindingpairiterable.rs @@ -1,24 +1,23 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // check-tidy: no specs after this line -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::TestBindingPairIterableBinding; -use dom::bindings::codegen::Bindings::TestBindingPairIterableBinding::TestBindingPairIterableMethods; -use dom::bindings::error::Fallible; -use dom::bindings::iterable::Iterable; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::TestBindingPairIterableBinding::TestBindingPairIterableMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::iterable::Iterable; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; #[dom_struct] pub struct TestBindingPairIterable { reflector: Reflector, - map: DOMRefCell<Vec<(DOMString, u32)>>, + map: DomRefCell<Vec<(DOMString, u32)>>, } impl Iterable for TestBindingPairIterable { @@ -28,22 +27,38 @@ impl Iterable for TestBindingPairIterable { self.map.borrow().len() as u32 } fn get_value_at_index(&self, index: u32) -> u32 { - self.map.borrow().iter().nth(index as usize).map(|a| &a.1).unwrap().clone() + self.map + .borrow() + .iter() + .nth(index as usize) + .map(|a| &a.1) + .unwrap() + .clone() } fn get_key_at_index(&self, index: u32) -> DOMString { - self.map.borrow().iter().nth(index as usize).map(|a| &a.0).unwrap().clone() + self.map + .borrow() + .iter() + .nth(index as usize) + .map(|a| &a.0) + .unwrap() + .clone() } } impl TestBindingPairIterable { - fn new(global: &GlobalScope) -> Root<TestBindingPairIterable> { - reflect_dom_object(box TestBindingPairIterable { - reflector: Reflector::new(), - map: DOMRefCell::new(vec![]), - }, global, TestBindingPairIterableBinding::TestBindingPairIterableWrap) + fn new(global: &GlobalScope) -> DomRoot<TestBindingPairIterable> { + reflect_dom_object( + Box::new(TestBindingPairIterable { + reflector: Reflector::new(), + map: DomRefCell::new(vec![]), + }), + global, + ) } - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<TestBindingPairIterable>> { + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<TestBindingPairIterable>> { Ok(TestBindingPairIterable::new(global)) } } diff --git a/components/script/dom/testbindingproxy.rs b/components/script/dom/testbindingproxy.rs index c43040eacb7..3b352acb097 100644 --- a/components/script/dom/testbindingproxy.rs +++ b/components/script/dom/testbindingproxy.rs @@ -1,32 +1,45 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // check-tidy: no specs after this line -use dom::bindings::codegen::Bindings::TestBindingProxyBinding::TestBindingProxyMethods; -use dom::bindings::reflector::Reflector; -use dom::bindings::str::DOMString; +use crate::dom::bindings::codegen::Bindings::TestBindingProxyBinding::TestBindingProxyMethods; +use crate::dom::bindings::str::DOMString; +use crate::dom::testbinding::TestBinding; use dom_struct::dom_struct; #[dom_struct] pub struct TestBindingProxy { - reflector_: Reflector + testbinding_: TestBinding, } impl TestBindingProxyMethods for TestBindingProxy { - fn Length(&self) -> u32 { 0 } - fn SupportedPropertyNames(&self) -> Vec<DOMString> { vec![] } - fn GetNamedItem(&self, _: DOMString) -> DOMString { DOMString::new() } + fn Length(&self) -> u32 { + 0 + } + fn SupportedPropertyNames(&self) -> Vec<DOMString> { + vec![] + } + fn GetNamedItem(&self, _: DOMString) -> DOMString { + DOMString::new() + } fn SetNamedItem(&self, _: DOMString, _: DOMString) {} - fn GetItem(&self, _: u32) -> DOMString { DOMString::new() } + fn GetItem(&self, _: u32) -> DOMString { + DOMString::new() + } fn SetItem(&self, _: u32, _: DOMString) {} fn RemoveItem(&self, _: DOMString) {} - fn Stringifier(&self) -> DOMString { DOMString::new() } - fn IndexedGetter(&self, _: u32) -> Option<DOMString> { None } + fn Stringifier(&self) -> DOMString { + DOMString::new() + } + fn IndexedGetter(&self, _: u32) -> Option<DOMString> { + None + } fn NamedDeleter(&self, _: DOMString) {} fn IndexedSetter(&self, _: u32, _: DOMString) {} fn NamedSetter(&self, _: DOMString, _: DOMString) {} - fn NamedGetter(&self, _: DOMString) -> Option<DOMString> { None } - + fn NamedGetter(&self, _: DOMString) -> Option<DOMString> { + None + } } diff --git a/components/script/dom/testrunner.rs b/components/script/dom/testrunner.rs index 1783fce7223..ac354905d25 100644 --- a/components/script/dom/testrunner.rs +++ b/components/script/dom/testrunner.rs @@ -1,20 +1,20 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::dom::bindings::codegen::Bindings::TestRunnerBinding::TestRunnerMethods; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; use bluetooth_traits::BluetoothRequest; -use dom::bindings::codegen::Bindings::TestRunnerBinding; -use dom::bindings::codegen::Bindings::TestRunnerBinding::TestRunnerMethods; -use dom::bindings::error::{Error, ErrorResult}; -use dom::bindings::js::Root; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::globalscope::GlobalScope; use dom_struct::dom_struct; -use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::ipc::IpcSender; +use profile_traits::ipc; // https://webbluetoothcg.github.io/web-bluetooth/tests#test-runner - #[dom_struct] +#[dom_struct] pub struct TestRunner { reflector_: Reflector, } @@ -26,10 +26,8 @@ impl TestRunner { } } - pub fn new(global: &GlobalScope) -> Root<TestRunner> { - reflect_dom_object(box TestRunner::new_inherited(), - global, - TestRunnerBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<TestRunner> { + reflect_dom_object(Box::new(TestRunner::new_inherited()), global) } fn get_bluetooth_thread(&self) -> IpcSender<BluetoothRequest> { @@ -39,16 +37,15 @@ impl TestRunner { impl TestRunnerMethods for TestRunner { // https://webbluetoothcg.github.io/web-bluetooth/tests#setBluetoothMockDataSet + #[allow(non_snake_case)] fn SetBluetoothMockDataSet(&self, dataSetName: DOMString) -> ErrorResult { - let (sender, receiver) = ipc::channel().unwrap(); - self.get_bluetooth_thread().send(BluetoothRequest::Test(String::from(dataSetName), sender)).unwrap(); + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); + self.get_bluetooth_thread() + .send(BluetoothRequest::Test(String::from(dataSetName), sender)) + .unwrap(); match receiver.recv().unwrap().into() { - Ok(()) => { - Ok(()) - }, - Err(error) => { - Err(Error::from(error)) - }, + Ok(()) => Ok(()), + Err(error) => Err(Error::from(error)), } } } diff --git a/components/script/dom/testworklet.rs b/components/script/dom/testworklet.rs new file mode 100644 index 00000000000..16e49b55493 --- /dev/null +++ b/components/script/dom/testworklet.rs @@ -0,0 +1,66 @@ +/* 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/. */ + +// check-tidy: no specs after this line +use crate::dom::bindings::codegen::Bindings::TestWorkletBinding::TestWorkletMethods; +use crate::dom::bindings::codegen::Bindings::WorkletBinding::WorkletBinding::WorkletMethods; +use crate::dom::bindings::codegen::Bindings::WorkletBinding::WorkletOptions; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::str::USVString; +use crate::dom::promise::Promise; +use crate::dom::window::Window; +use crate::dom::worklet::Worklet; +use crate::dom::workletglobalscope::WorkletGlobalScopeType; +use crate::realms::InRealm; +use crate::script_thread::ScriptThread; +use dom_struct::dom_struct; +use std::rc::Rc; + +#[dom_struct] +pub struct TestWorklet { + reflector: Reflector, + worklet: Dom<Worklet>, +} + +impl TestWorklet { + fn new_inherited(worklet: &Worklet) -> TestWorklet { + TestWorklet { + reflector: Reflector::new(), + worklet: Dom::from_ref(worklet), + } + } + + fn new(window: &Window) -> DomRoot<TestWorklet> { + let worklet = Worklet::new(window, WorkletGlobalScopeType::Test); + reflect_dom_object(Box::new(TestWorklet::new_inherited(&*worklet)), window) + } + + #[allow(non_snake_case)] + pub fn Constructor(window: &Window) -> Fallible<DomRoot<TestWorklet>> { + Ok(TestWorklet::new(window)) + } +} + +impl TestWorkletMethods for TestWorklet { + #[allow(non_snake_case)] + fn AddModule( + &self, + moduleURL: USVString, + options: &WorkletOptions, + comp: InRealm, + ) -> Rc<Promise> { + self.worklet.AddModule(moduleURL, options, comp) + } + + fn Lookup(&self, key: DOMString) -> Option<DOMString> { + let id = self.worklet.worklet_id(); + let pool = ScriptThread::worklet_thread_pool(); + pool.test_worklet_lookup(id, String::from(key)) + .map(DOMString::from) + } +} diff --git a/components/script/dom/testworkletglobalscope.rs b/components/script/dom/testworkletglobalscope.rs new file mode 100644 index 00000000000..c67e1536457 --- /dev/null +++ b/components/script/dom/testworkletglobalscope.rs @@ -0,0 +1,79 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::TestWorkletGlobalScopeBinding; +use crate::dom::bindings::codegen::Bindings::TestWorkletGlobalScopeBinding::TestWorkletGlobalScopeMethods; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::worklet::WorkletExecutor; +use crate::dom::workletglobalscope::WorkletGlobalScope; +use crate::dom::workletglobalscope::WorkletGlobalScopeInit; +use crate::script_runtime::JSContext; +use crossbeam_channel::Sender; +use dom_struct::dom_struct; +use js::rust::Runtime; +use msg::constellation_msg::PipelineId; +use servo_url::ServoUrl; +use std::collections::HashMap; + +// check-tidy: no specs after this line + +#[dom_struct] +pub struct TestWorkletGlobalScope { + // The worklet global for this object + worklet_global: WorkletGlobalScope, + // The key/value pairs + lookup_table: DomRefCell<HashMap<String, String>>, +} + +impl TestWorkletGlobalScope { + #[allow(unsafe_code)] + pub fn new( + runtime: &Runtime, + pipeline_id: PipelineId, + base_url: ServoUrl, + executor: WorkletExecutor, + init: &WorkletGlobalScopeInit, + ) -> DomRoot<TestWorkletGlobalScope> { + debug!( + "Creating test worklet global scope for pipeline {}.", + pipeline_id + ); + let global = Box::new(TestWorkletGlobalScope { + worklet_global: WorkletGlobalScope::new_inherited( + pipeline_id, + base_url, + executor, + init, + ), + lookup_table: Default::default(), + }); + unsafe { TestWorkletGlobalScopeBinding::Wrap(JSContext::from_ptr(runtime.cx()), global) } + } + + pub fn perform_a_worklet_task(&self, task: TestWorkletTask) { + match task { + TestWorkletTask::Lookup(key, sender) => { + debug!("Looking up key {}.", key); + let result = self.lookup_table.borrow().get(&key).cloned(); + let _ = sender.send(result); + }, + } + } +} + +impl TestWorkletGlobalScopeMethods for TestWorkletGlobalScope { + fn RegisterKeyValue(&self, key: DOMString, value: DOMString) { + debug!("Registering test worklet key/value {}/{}.", key, value); + self.lookup_table + .borrow_mut() + .insert(String::from(key), String::from(value)); + } +} + +/// Tasks which can be performed by test worklets. +pub enum TestWorkletTask { + Lookup(String, Sender<Option<String>>), +} diff --git a/components/script/dom/text.rs b/components/script/dom/text.rs index 0343b373931..64fae977556 100644 --- a/components/script/dom/text.rs +++ b/components/script/dom/text.rs @@ -1,21 +1,20 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::Bindings::TextBinding::{self, TextMethods}; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::js::RootedReference; -use dom::bindings::str::DOMString; -use dom::characterdata::CharacterData; -use dom::document::Document; -use dom::node::Node; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::TextBinding::TextMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::characterdata::CharacterData; +use crate::dom::document::Document; +use crate::dom::node::Node; +use crate::dom::window::Window; use dom_struct::dom_struct; /// An HTML text node. @@ -25,18 +24,18 @@ pub struct Text { } impl Text { - fn new_inherited(text: DOMString, document: &Document) -> Text { + pub fn new_inherited(text: DOMString, document: &Document) -> Text { Text { - characterdata: CharacterData::new_inherited(text, document) + characterdata: CharacterData::new_inherited(text, document), } } - pub fn new(text: DOMString, document: &Document) -> Root<Text> { - Node::reflect_node(box Text::new_inherited(text, document), - document, TextBinding::Wrap) + pub fn new(text: DOMString, document: &Document) -> DomRoot<Text> { + Node::reflect_node(Box::new(Text::new_inherited(text, document)), document) } - pub fn Constructor(window: &Window, text: DOMString) -> Fallible<Root<Text>> { + #[allow(non_snake_case)] + pub fn Constructor(window: &Window, text: DOMString) -> Fallible<DomRoot<Text>> { let document = window.Document(); Ok(Text::new(text, &document)) } @@ -45,7 +44,7 @@ impl Text { impl TextMethods for Text { // https://dom.spec.whatwg.org/#dom-text-splittext // https://dom.spec.whatwg.org/#concept-text-split - fn SplitText(&self, offset: u32) -> Fallible<Root<Text>> { + fn SplitText(&self, offset: u32) -> Fallible<DomRoot<Text>> { let cdata = self.upcast::<CharacterData>(); // Step 1. let length = cdata.Length(); @@ -65,9 +64,12 @@ impl TextMethods for Text { let parent = node.GetParentNode(); if let Some(ref parent) = parent { // Step 7.1. - parent.InsertBefore(new_node.upcast(), node.GetNextSibling().r()).unwrap(); + parent + .InsertBefore(new_node.upcast(), node.GetNextSibling().as_deref()) + .unwrap(); // Steps 7.2-3. - node.ranges().move_to_following_text_sibling_above(node, offset, new_node.upcast()); + node.ranges() + .move_to_following_text_sibling_above(node, offset, new_node.upcast()); // Steps 7.4-5. parent.ranges().increment_at(&parent, node.index() + 1); } @@ -79,11 +81,15 @@ impl TextMethods for Text { // https://dom.spec.whatwg.org/#dom-text-wholetext fn WholeText(&self) -> DOMString { - let first = self.upcast::<Node>().inclusively_preceding_siblings() - .take_while(|node| node.is::<Text>()) - .last().unwrap(); - let nodes = first.inclusively_following_siblings() - .take_while(|node| node.is::<Text>()); + let first = self + .upcast::<Node>() + .inclusively_preceding_siblings() + .take_while(|node| node.is::<Text>()) + .last() + .unwrap(); + let nodes = first + .inclusively_following_siblings() + .take_while(|node| node.is::<Text>()); let mut text = String::new(); for ref node in nodes { let cdata = node.downcast::<CharacterData>().unwrap(); diff --git a/components/script/dom/textcontrol.rs b/components/script/dom/textcontrol.rs new file mode 100644 index 00000000000..0f215b4cbc8 --- /dev/null +++ b/components/script/dom/textcontrol.rs @@ -0,0 +1,319 @@ +/* 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/. */ + +//! This is an abstraction used by `HTMLInputElement` and `HTMLTextAreaElement` to implement the +//! text control selection DOM API. +//! +//! https://html.spec.whatwg.org/multipage/#textFieldSelection + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode; +use crate::dom::bindings::conversions::DerivedFrom; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::node::{window_from_node, Node, NodeDamage}; +use crate::textinput::{SelectionDirection, SelectionState, TextInput, UTF8Bytes}; +use script_traits::ScriptToConstellationChan; + +pub trait TextControlElement: DerivedFrom<EventTarget> + DerivedFrom<Node> { + fn selection_api_applies(&self) -> bool; + fn has_selectable_text(&self) -> bool; + fn set_dirty_value_flag(&self, value: bool); +} + +pub struct TextControlSelection<'a, E: TextControlElement> { + element: &'a E, + textinput: &'a DomRefCell<TextInput<ScriptToConstellationChan>>, +} + +impl<'a, E: TextControlElement> TextControlSelection<'a, E> { + pub fn new( + element: &'a E, + textinput: &'a DomRefCell<TextInput<ScriptToConstellationChan>>, + ) -> Self { + TextControlSelection { element, textinput } + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-select + pub fn dom_select(&self) { + // Step 1 + if !self.element.has_selectable_text() { + return; + } + + // Step 2 + self.set_range(Some(0), Some(u32::max_value()), None, None); + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart + pub fn dom_start(&self) -> Option<u32> { + // Step 1 + if !self.element.selection_api_applies() { + return None; + } + + // Steps 2-3 + Some(self.start()) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart + pub fn set_dom_start(&self, start: Option<u32>) -> ErrorResult { + // Step 1 + if !self.element.selection_api_applies() { + return Err(Error::InvalidState); + } + + // Step 2 + let mut end = self.end(); + + // Step 3 + if let Some(s) = start { + if end < s { + end = s; + } + } + + // Step 4 + self.set_range(start, Some(end), Some(self.direction()), None); + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend + pub fn dom_end(&self) -> Option<u32> { + // Step 1 + if !self.element.selection_api_applies() { + return None; + } + + // Steps 2-3 + Some(self.end()) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend + pub fn set_dom_end(&self, end: Option<u32>) -> ErrorResult { + // Step 1 + if !self.element.selection_api_applies() { + return Err(Error::InvalidState); + } + + // Step 2 + self.set_range(Some(self.start()), end, Some(self.direction()), None); + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection + pub fn dom_direction(&self) -> Option<DOMString> { + // Step 1 + if !self.element.selection_api_applies() { + return None; + } + + Some(DOMString::from(self.direction())) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection + pub fn set_dom_direction(&self, direction: Option<DOMString>) -> ErrorResult { + // Step 1 + if !self.element.selection_api_applies() { + return Err(Error::InvalidState); + } + + // Step 2 + self.set_range( + Some(self.start()), + Some(self.end()), + direction.map(|d| SelectionDirection::from(d)), + None, + ); + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange + pub fn set_dom_range(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult { + // Step 1 + if !self.element.selection_api_applies() { + return Err(Error::InvalidState); + } + + // Step 2 + self.set_range( + Some(start), + Some(end), + direction.map(|d| SelectionDirection::from(d)), + None, + ); + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext + pub fn set_dom_range_text( + &self, + replacement: DOMString, + start: Option<u32>, + end: Option<u32>, + selection_mode: SelectionMode, + ) -> ErrorResult { + // Step 1 + if !self.element.selection_api_applies() { + return Err(Error::InvalidState); + } + + // Step 2 + self.element.set_dirty_value_flag(true); + + // Step 3 + let mut start = start.unwrap_or_else(|| self.start()); + let mut end = end.unwrap_or_else(|| self.end()); + + // Step 4 + if start > end { + return Err(Error::IndexSize); + } + + // Save the original selection state to later pass to set_selection_range, because we will + // change the selection state in order to replace the text in the range. + let original_selection_state = self.textinput.borrow().selection_state(); + + let UTF8Bytes(content_length) = self.textinput.borrow().len_utf8(); + let content_length = content_length as u32; + + // Step 5 + if start > content_length { + start = content_length; + } + + // Step 6 + if end > content_length { + end = content_length; + } + + // Step 7 + let mut selection_start = self.start(); + + // Step 8 + let mut selection_end = self.end(); + + // Step 11 + // Must come before the textinput.replace_selection() call, as replacement gets moved in + // that call. + let new_length = replacement.len() as u32; + + { + let mut textinput = self.textinput.borrow_mut(); + + // Steps 9-10 + textinput.set_selection_range(start, end, SelectionDirection::None); + textinput.replace_selection(replacement); + } + + // Step 12 + let new_end = start + new_length; + + // Step 13 + match selection_mode { + SelectionMode::Select => { + selection_start = start; + selection_end = new_end; + }, + + SelectionMode::Start => { + selection_start = start; + selection_end = start; + }, + + SelectionMode::End => { + selection_start = new_end; + selection_end = new_end; + }, + + SelectionMode::Preserve => { + // Sub-step 1 + let old_length = end - start; + + // Sub-step 2 + let delta = (new_length as isize) - (old_length as isize); + + // Sub-step 3 + if selection_start > end { + selection_start = ((selection_start as isize) + delta) as u32; + } else if selection_start > start { + selection_start = start; + } + + // Sub-step 4 + if selection_end > end { + selection_end = ((selection_end as isize) + delta) as u32; + } else if selection_end > start { + selection_end = new_end; + } + }, + } + + // Step 14 + self.set_range( + Some(selection_start), + Some(selection_end), + None, + Some(original_selection_state), + ); + Ok(()) + } + + fn start(&self) -> u32 { + let UTF8Bytes(offset) = self.textinput.borrow().selection_start_offset(); + offset as u32 + } + + fn end(&self) -> u32 { + let UTF8Bytes(offset) = self.textinput.borrow().selection_end_offset(); + offset as u32 + } + + fn direction(&self) -> SelectionDirection { + self.textinput.borrow().selection_direction() + } + + // https://html.spec.whatwg.org/multipage/#set-the-selection-range + fn set_range( + &self, + start: Option<u32>, + end: Option<u32>, + direction: Option<SelectionDirection>, + original_selection_state: Option<SelectionState>, + ) { + let mut textinput = self.textinput.borrow_mut(); + let original_selection_state = + original_selection_state.unwrap_or_else(|| textinput.selection_state()); + + // Step 1 + let start = start.unwrap_or(0); + + // Step 2 + let end = end.unwrap_or(0); + + // Steps 3-5 + textinput.set_selection_range(start, end, direction.unwrap_or(SelectionDirection::None)); + + // Step 6 + if textinput.selection_state() != original_selection_state { + let window = window_from_node(self.element); + window + .task_manager() + .user_interaction_task_source() + .queue_event( + &self.element.upcast::<EventTarget>(), + atom!("select"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + &window, + ); + } + + self.element + .upcast::<Node>() + .dirty(NodeDamage::OtherNodeDamage); + } +} diff --git a/components/script/dom/textdecoder.rs b/components/script/dom/textdecoder.rs index e1b1edc1048..7d664242467 100644 --- a/components/script/dom/textdecoder.rs +++ b/components/script/dom/textdecoder.rs @@ -1,74 +1,94 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::TextDecoderBinding; -use dom::bindings::codegen::Bindings::TextDecoderBinding::TextDecoderMethods; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::globalscope::GlobalScope; +use crate::dom::bindings::codegen::Bindings::TextDecoderBinding; +use crate::dom::bindings::codegen::Bindings::TextDecoderBinding::{ + TextDecodeOptions, TextDecoderMethods, +}; +use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::globalscope::GlobalScope; use dom_struct::dom_struct; -use encoding::label::encoding_from_whatwg_label; -use encoding::types::{DecoderTrap, EncodingRef}; -use js::jsapi::{JSContext, JSObject}; +use encoding_rs::{Decoder, DecoderResult, Encoding}; use std::borrow::ToOwned; +use std::cell::{Cell, RefCell}; #[dom_struct] +#[allow(non_snake_case)] pub struct TextDecoder { reflector_: Reflector, - #[ignore_heap_size_of = "Defined in rust-encoding"] - encoding: EncodingRef, + encoding: &'static Encoding, fatal: bool, + ignoreBOM: bool, + #[ignore_malloc_size_of = "defined in encoding_rs"] + decoder: RefCell<Decoder>, + in_stream: RefCell<Vec<u8>>, + do_not_flush: Cell<bool>, } +#[allow(non_snake_case)] impl TextDecoder { - fn new_inherited(encoding: EncodingRef, fatal: bool) -> TextDecoder { + fn new_inherited(encoding: &'static Encoding, fatal: bool, ignoreBOM: bool) -> TextDecoder { TextDecoder { reflector_: Reflector::new(), encoding: encoding, fatal: fatal, + ignoreBOM: ignoreBOM, + decoder: RefCell::new(if ignoreBOM { + encoding.new_decoder() + } else { + encoding.new_decoder_without_bom_handling() + }), + in_stream: RefCell::new(Vec::new()), + do_not_flush: Cell::new(false), } } - fn make_range_error() -> Fallible<Root<TextDecoder>> { - Err(Error::Range("The given encoding is not supported.".to_owned())) + fn make_range_error() -> Fallible<DomRoot<TextDecoder>> { + Err(Error::Range( + "The given encoding is not supported.".to_owned(), + )) } - pub fn new(global: &GlobalScope, encoding: EncodingRef, fatal: bool) -> Root<TextDecoder> { - reflect_dom_object(box TextDecoder::new_inherited(encoding, fatal), - global, - TextDecoderBinding::Wrap) + pub fn new( + global: &GlobalScope, + encoding: &'static Encoding, + fatal: bool, + ignoreBOM: bool, + ) -> DomRoot<TextDecoder> { + reflect_dom_object( + Box::new(TextDecoder::new_inherited(encoding, fatal, ignoreBOM)), + global, + ) } - /// https://encoding.spec.whatwg.org/#dom-textdecoder - pub fn Constructor(global: &GlobalScope, - label: DOMString, - options: &TextDecoderBinding::TextDecoderOptions) - -> Fallible<Root<TextDecoder>> { - let encoding = match encoding_from_whatwg_label(&label) { + /// <https://encoding.spec.whatwg.org/#dom-textdecoder> + pub fn Constructor( + global: &GlobalScope, + label: DOMString, + options: &TextDecoderBinding::TextDecoderOptions, + ) -> Fallible<DomRoot<TextDecoder>> { + let encoding = match Encoding::for_label_no_replacement(label.as_bytes()) { None => return TextDecoder::make_range_error(), - Some(enc) => enc + Some(enc) => enc, }; - // The rust-encoding crate has WHATWG compatibility, so we are - // guaranteed to have a whatwg_name because we successfully got - // the encoding from encoding_from_whatwg_label. - // Use match + panic! instead of unwrap for better error message - match encoding.whatwg_name() { - None => panic!("Label {} fits valid encoding without valid name", label), - Some("replacement") => return TextDecoder::make_range_error(), - _ => () - }; - Ok(TextDecoder::new(global, encoding, options.fatal)) + Ok(TextDecoder::new( + global, + encoding, + options.fatal, + options.ignoreBOM, + )) } } - impl TextDecoderMethods for TextDecoder { // https://encoding.spec.whatwg.org/#dom-textdecoder-encoding fn Encoding(&self) -> DOMString { - DOMString::from(self.encoding.whatwg_name().unwrap()) + DOMString::from(self.encoding.name().to_ascii_lowercase()) } // https://encoding.spec.whatwg.org/#dom-textdecoder-fatal @@ -76,32 +96,75 @@ impl TextDecoderMethods for TextDecoder { self.fatal } - #[allow(unsafe_code)] - // https://encoding.spec.whatwg.org/#dom-textdecoder-decode - unsafe fn Decode(&self, _cx: *mut JSContext, input: Option<*mut JSObject>) - -> Fallible<USVString> { - let input = match input { - Some(input) => input, - None => return Ok(USVString("".to_owned())), - }; + // https://encoding.spec.whatwg.org/#dom-textdecoder-ignorebom + fn IgnoreBOM(&self) -> bool { + self.ignoreBOM + } - typedarray!(in(_cx) let data_res: ArrayBufferView = input); - let mut data = match data_res { - Ok(data) => data, - Err(_) => { - return Err(Error::Type("Argument to TextDecoder.decode is not an ArrayBufferView".to_owned())); + // https://encoding.spec.whatwg.org/#dom-textdecoder-decode + fn Decode( + &self, + input: Option<ArrayBufferViewOrArrayBuffer>, + options: &TextDecodeOptions, + ) -> Fallible<USVString> { + // Step 1. + if !self.do_not_flush.get() { + if self.ignoreBOM { + self.decoder + .replace(self.encoding.new_decoder_without_bom_handling()); + } else { + self.decoder.replace(self.encoding.new_decoder()); } - }; + self.in_stream.replace(Vec::new()); + } + + // Step 2. + self.do_not_flush.set(options.stream); - let trap = if self.fatal { - DecoderTrap::Strict - } else { - DecoderTrap::Replace + // Step 3. + match input { + Some(ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref a)) => { + self.in_stream.borrow_mut().extend_from_slice(&a.to_vec()); + }, + Some(ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref a)) => { + self.in_stream.borrow_mut().extend_from_slice(&a.to_vec()); + }, + None => {}, }; - match self.encoding.decode(data.as_slice(), trap) { - Ok(s) => Ok(USVString(s)), - Err(_) => Err(Error::Type("Decoding failed".to_owned())), - } + let mut decoder = self.decoder.borrow_mut(); + let (remaining, s) = { + let mut in_stream = self.in_stream.borrow_mut(); + + let (remaining, s) = if self.fatal { + // Step 4. + let mut out_stream = String::with_capacity( + decoder + .max_utf8_buffer_length_without_replacement(in_stream.len()) + .unwrap(), + ); + // Step 5: Implemented by encoding_rs::Decoder. + match decoder.decode_to_string_without_replacement( + &in_stream, + &mut out_stream, + !options.stream, + ) { + (DecoderResult::InputEmpty, read) => (in_stream.split_off(read), out_stream), + // Step 5.3.3. + _ => return Err(Error::Type("Decoding failed".to_owned())), + } + } else { + // Step 4. + let mut out_stream = + String::with_capacity(decoder.max_utf8_buffer_length(in_stream.len()).unwrap()); + // Step 5: Implemented by encoding_rs::Decoder. + let (_result, read, _replaced) = + decoder.decode_to_string(&in_stream, &mut out_stream, !options.stream); + (in_stream.split_off(read), out_stream) + }; + (remaining, s) + }; + self.in_stream.replace(remaining); + Ok(USVString(s)) } } diff --git a/components/script/dom/textencoder.rs b/components/script/dom/textencoder.rs index 765c95ade0b..4df1143531f 100644 --- a/components/script/dom/textencoder.rs +++ b/components/script/dom/textencoder.rs @@ -1,22 +1,19 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use core::nonzero::NonZero; -use dom::bindings::codegen::Bindings::TextEncoderBinding; -use dom::bindings::codegen::Bindings::TextEncoderBinding::TextEncoderMethods; -use dom::bindings::error::Fallible; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::globalscope::GlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::TextEncoderBinding::TextEncoderMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use encoding::EncoderTrap; -use encoding::Encoding; -use encoding::all::UTF_8; -use js::jsapi::{JSContext, JSObject}; -use js::typedarray::{Uint8Array, CreateWith}; +use js::jsapi::JSObject; +use js::typedarray::{CreateWith, Uint8Array}; use std::ptr; +use std::ptr::NonNull; #[dom_struct] pub struct TextEncoder { @@ -30,14 +27,13 @@ impl TextEncoder { } } - pub fn new(global: &GlobalScope) -> Root<TextEncoder> { - reflect_dom_object(box TextEncoder::new_inherited(), - global, - TextEncoderBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<TextEncoder> { + reflect_dom_object(Box::new(TextEncoder::new_inherited()), global) } // https://encoding.spec.whatwg.org/#dom-textencoder - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<TextEncoder>> { + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<TextEncoder>> { Ok(TextEncoder::new(global)) } } @@ -45,17 +41,22 @@ impl TextEncoder { impl TextEncoderMethods for TextEncoder { // https://encoding.spec.whatwg.org/#dom-textencoder-encoding fn Encoding(&self) -> DOMString { - DOMString::from(UTF_8.name()) + DOMString::from("utf-8") } #[allow(unsafe_code)] // https://encoding.spec.whatwg.org/#dom-textencoder-encode - unsafe fn Encode(&self, cx: *mut JSContext, input: USVString) -> NonZero<*mut JSObject> { - let encoded = UTF_8.encode(&input.0, EncoderTrap::Strict).unwrap(); + fn Encode(&self, cx: JSContext, input: USVString) -> NonNull<JSObject> { + let encoded = input.0.as_bytes(); - rooted!(in(cx) let mut js_object = ptr::null_mut()); - assert!(Uint8Array::create(cx, CreateWith::Slice(&encoded), js_object.handle_mut()).is_ok()); + unsafe { + rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>()); + assert!( + Uint8Array::create(*cx, CreateWith::Slice(&encoded), js_object.handle_mut()) + .is_ok() + ); - NonZero::new(js_object.get()) + NonNull::new_unchecked(js_object.get()) + } } } diff --git a/components/script/dom/textmetrics.rs b/components/script/dom/textmetrics.rs new file mode 100644 index 00000000000..675fcf1aa1d --- /dev/null +++ b/components/script/dom/textmetrics.rs @@ -0,0 +1,158 @@ +/* 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 crate::dom::bindings::codegen::Bindings::TextMetricsBinding::TextMetricsMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; + +#[dom_struct] +#[allow(non_snake_case)] +pub struct TextMetrics { + reflector_: Reflector, + width: Finite<f64>, + actualBoundingBoxLeft: Finite<f64>, + actualBoundingBoxRight: Finite<f64>, + fontBoundingBoxAscent: Finite<f64>, + fontBoundingBoxDescent: Finite<f64>, + actualBoundingBoxAscent: Finite<f64>, + actualBoundingBoxDescent: Finite<f64>, + emHeightAscent: Finite<f64>, + emHeightDescent: Finite<f64>, + hangingBaseline: Finite<f64>, + alphabeticBaseline: Finite<f64>, + ideographicBaseline: Finite<f64>, +} + +#[allow(non_snake_case)] +impl TextMetrics { + fn new_inherited( + width: f64, + actualBoundingBoxLeft: f64, + actualBoundingBoxRight: f64, + fontBoundingBoxAscent: f64, + fontBoundingBoxDescent: f64, + actualBoundingBoxAscent: f64, + actualBoundingBoxDescent: f64, + emHeightAscent: f64, + emHeightDescent: f64, + hangingBaseline: f64, + alphabeticBaseline: f64, + ideographicBaseline: f64, + ) -> TextMetrics { + TextMetrics { + reflector_: Reflector::new(), + width: Finite::wrap(width), + actualBoundingBoxLeft: Finite::wrap(actualBoundingBoxLeft), + actualBoundingBoxRight: Finite::wrap(actualBoundingBoxRight), + fontBoundingBoxAscent: Finite::wrap(fontBoundingBoxAscent), + fontBoundingBoxDescent: Finite::wrap(fontBoundingBoxDescent), + actualBoundingBoxAscent: Finite::wrap(actualBoundingBoxAscent), + actualBoundingBoxDescent: Finite::wrap(actualBoundingBoxDescent), + emHeightAscent: Finite::wrap(emHeightAscent), + emHeightDescent: Finite::wrap(emHeightDescent), + hangingBaseline: Finite::wrap(hangingBaseline), + alphabeticBaseline: Finite::wrap(alphabeticBaseline), + ideographicBaseline: Finite::wrap(ideographicBaseline), + } + } + + pub fn new( + global: &GlobalScope, + width: f64, + actualBoundingBoxLeft: f64, + actualBoundingBoxRight: f64, + fontBoundingBoxAscent: f64, + fontBoundingBoxDescent: f64, + actualBoundingBoxAscent: f64, + actualBoundingBoxDescent: f64, + emHeightAscent: f64, + emHeightDescent: f64, + hangingBaseline: f64, + alphabeticBaseline: f64, + ideographicBaseline: f64, + ) -> DomRoot<TextMetrics> { + reflect_dom_object( + Box::new(TextMetrics::new_inherited( + width, + actualBoundingBoxLeft, + actualBoundingBoxRight, + fontBoundingBoxAscent, + fontBoundingBoxDescent, + actualBoundingBoxAscent, + actualBoundingBoxDescent, + emHeightAscent, + emHeightDescent, + hangingBaseline, + alphabeticBaseline, + ideographicBaseline, + )), + global, + ) + } +} + +impl TextMetricsMethods for TextMetrics { + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-width + fn Width(&self) -> Finite<f64> { + self.width + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-actualboundingboxleft + fn ActualBoundingBoxLeft(&self) -> Finite<f64> { + self.actualBoundingBoxLeft + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-actualboundingboxright + fn ActualBoundingBoxRight(&self) -> Finite<f64> { + self.actualBoundingBoxRight + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-fontboundingboxascent + fn FontBoundingBoxAscent(&self) -> Finite<f64> { + self.fontBoundingBoxAscent + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-fontboundingboxascent + fn FontBoundingBoxDescent(&self) -> Finite<f64> { + self.fontBoundingBoxDescent + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-actualboundingboxascent + fn ActualBoundingBoxAscent(&self) -> Finite<f64> { + self.actualBoundingBoxAscent + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-actualboundingboxdescent + fn ActualBoundingBoxDescent(&self) -> Finite<f64> { + self.actualBoundingBoxDescent + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-emheightascent + fn EmHeightAscent(&self) -> Finite<f64> { + self.emHeightAscent + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-emheightdescent + fn EmHeightDescent(&self) -> Finite<f64> { + self.emHeightDescent + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-hangingbaseline + fn HangingBaseline(&self) -> Finite<f64> { + self.hangingBaseline + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-alphabeticbaseline + fn AlphabeticBaseline(&self) -> Finite<f64> { + self.alphabeticBaseline + } + + /// https://html.spec.whatwg.org/multipage/#dom-textmetrics-ideographicbaseline + fn IdeographicBaseline(&self) -> Finite<f64> { + self.ideographicBaseline + } +} diff --git a/components/script/dom/texttrack.rs b/components/script/dom/texttrack.rs new file mode 100644 index 00000000000..0e6cd2e48c7 --- /dev/null +++ b/components/script/dom/texttrack.rs @@ -0,0 +1,167 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{ + TextTrackKind, TextTrackMethods, TextTrackMode, +}; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::eventtarget::EventTarget; +use crate::dom::texttrackcue::TextTrackCue; +use crate::dom::texttrackcuelist::TextTrackCueList; +use crate::dom::texttracklist::TextTrackList; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct TextTrack { + eventtarget: EventTarget, + kind: TextTrackKind, + label: String, + language: String, + id: String, + mode: Cell<TextTrackMode>, + cue_list: MutNullableDom<TextTrackCueList>, + track_list: DomRefCell<Option<Dom<TextTrackList>>>, +} + +impl TextTrack { + pub fn new_inherited( + id: DOMString, + kind: TextTrackKind, + label: DOMString, + language: DOMString, + mode: TextTrackMode, + track_list: Option<&TextTrackList>, + ) -> TextTrack { + TextTrack { + eventtarget: EventTarget::new_inherited(), + kind: kind, + label: label.into(), + language: language.into(), + id: id.into(), + mode: Cell::new(mode), + cue_list: Default::default(), + track_list: DomRefCell::new(track_list.map(|t| Dom::from_ref(t))), + } + } + + pub fn new( + window: &Window, + id: DOMString, + kind: TextTrackKind, + label: DOMString, + language: DOMString, + mode: TextTrackMode, + track_list: Option<&TextTrackList>, + ) -> DomRoot<TextTrack> { + reflect_dom_object( + Box::new(TextTrack::new_inherited( + id, kind, label, language, mode, track_list, + )), + window, + ) + } + + pub fn get_cues(&self) -> DomRoot<TextTrackCueList> { + self.cue_list + .or_init(|| TextTrackCueList::new(&self.global().as_window(), &[])) + } + + pub fn id(&self) -> &str { + &self.id + } + + pub fn add_track_list(&self, track_list: &TextTrackList) { + *self.track_list.borrow_mut() = Some(Dom::from_ref(track_list)); + } + + pub fn remove_track_list(&self) { + *self.track_list.borrow_mut() = None; + } +} + +impl TextTrackMethods for TextTrack { + // https://html.spec.whatwg.org/multipage/#dom-texttrack-kind + fn Kind(&self) -> TextTrackKind { + self.kind + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrack-label + fn Label(&self) -> DOMString { + DOMString::from(self.label.clone()) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrack-language + fn Language(&self) -> DOMString { + DOMString::from(self.language.clone()) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrack-id + fn Id(&self) -> DOMString { + DOMString::from(self.id.clone()) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrack-mode + fn Mode(&self) -> TextTrackMode { + self.mode.get() + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrack-mode + fn SetMode(&self, value: TextTrackMode) { + self.mode.set(value) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrack-cues + fn GetCues(&self) -> Option<DomRoot<TextTrackCueList>> { + match self.Mode() { + TextTrackMode::Disabled => None, + _ => Some(self.get_cues()), + } + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrack-activecues + fn GetActiveCues(&self) -> Option<DomRoot<TextTrackCueList>> { + // XXX implement active cues logic + // https://github.com/servo/servo/issues/22314 + Some(TextTrackCueList::new(&self.global().as_window(), &[])) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrack-addcue + fn AddCue(&self, cue: &TextTrackCue) -> ErrorResult { + // FIXME(#22314, dlrobertson) add Step 1 & 2 + // Step 3 + if let Some(old_track) = cue.get_track() { + // gecko calls RemoveCue when the given cue + // has an associated track, but doesn't return + // the error from it, so we wont either. + if let Err(_) = old_track.RemoveCue(cue) { + warn!("Failed to remove cues for the added cue's text track"); + } + } + // Step 4 + self.get_cues().add(cue); + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrack-removecue + fn RemoveCue(&self, cue: &TextTrackCue) -> ErrorResult { + // Step 1 + let cues = self.get_cues(); + let index = match cues.find(cue) { + Some(i) => Ok(i), + None => Err(Error::NotFound), + }?; + // Step 2 + cues.remove(index); + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#handler-texttrack-oncuechange + event_handler!(cuechange, GetOncuechange, SetOncuechange); +} diff --git a/components/script/dom/texttrackcue.rs b/components/script/dom/texttrackcue.rs new file mode 100644 index 00000000000..7a219f0f417 --- /dev/null +++ b/components/script/dom/texttrackcue.rs @@ -0,0 +1,118 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::TextTrackCueBinding::TextTrackCueMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::eventtarget::EventTarget; +use crate::dom::texttrack::TextTrack; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct TextTrackCue { + eventtarget: EventTarget, + id: DomRefCell<DOMString>, + track: Option<Dom<TextTrack>>, + start_time: Cell<f64>, + end_time: Cell<f64>, + pause_on_exit: Cell<bool>, +} + +impl TextTrackCue { + pub fn new_inherited( + id: DOMString, + start_time: f64, + end_time: f64, + track: Option<&TextTrack>, + ) -> TextTrackCue { + TextTrackCue { + eventtarget: EventTarget::new_inherited(), + id: DomRefCell::new(id), + track: track.map(Dom::from_ref), + start_time: Cell::new(start_time), + end_time: Cell::new(end_time), + pause_on_exit: Cell::new(false), + } + } + + #[allow(dead_code)] + pub fn new( + window: &Window, + id: DOMString, + start_time: f64, + end_time: f64, + track: Option<&TextTrack>, + ) -> DomRoot<TextTrackCue> { + reflect_dom_object( + Box::new(TextTrackCue::new_inherited(id, start_time, end_time, track)), + window, + ) + } + + pub fn id(&self) -> DOMString { + self.id.borrow().clone() + } + + pub fn get_track(&self) -> Option<DomRoot<TextTrack>> { + self.track.as_ref().map(|t| DomRoot::from_ref(&**t)) + } +} + +impl TextTrackCueMethods for TextTrackCue { + // https://html.spec.whatwg.org/multipage/#dom-texttrackcue-id + fn Id(&self) -> DOMString { + self.id() + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcue-id + fn SetId(&self, value: DOMString) { + *self.id.borrow_mut() = value; + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcue-track + fn GetTrack(&self) -> Option<DomRoot<TextTrack>> { + self.get_track() + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcue-starttime + fn StartTime(&self) -> Finite<f64> { + Finite::wrap(self.start_time.get()) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcue-starttime + fn SetStartTime(&self, value: Finite<f64>) { + self.start_time.set(*value); + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcue-endtime + fn EndTime(&self) -> Finite<f64> { + Finite::wrap(self.end_time.get()) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcue-endtime + fn SetEndTime(&self, value: Finite<f64>) { + self.end_time.set(*value); + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcue-pauseonexit + fn PauseOnExit(&self) -> bool { + self.pause_on_exit.get() + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcue-pauseonexit + fn SetPauseOnExit(&self, value: bool) { + self.pause_on_exit.set(value); + } + + // https://html.spec.whatwg.org/multipage/#handler-texttrackcue-onenter + event_handler!(enter, GetOnenter, SetOnenter); + + // https://html.spec.whatwg.org/multipage/#handler-texttrackcue-onexit + event_handler!(exit, GetOnexit, SetOnexit); +} diff --git a/components/script/dom/texttrackcuelist.rs b/components/script/dom/texttrackcuelist.rs new file mode 100644 index 00000000000..a51b95a965f --- /dev/null +++ b/components/script/dom/texttrackcuelist.rs @@ -0,0 +1,85 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::TextTrackCueListBinding::TextTrackCueListMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::texttrackcue::TextTrackCue; +use crate::dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct TextTrackCueList { + reflector_: Reflector, + dom_cues: DomRefCell<Vec<Dom<TextTrackCue>>>, +} + +impl TextTrackCueList { + pub fn new_inherited(cues: &[&TextTrackCue]) -> TextTrackCueList { + TextTrackCueList { + reflector_: Reflector::new(), + dom_cues: DomRefCell::new(cues.iter().map(|g| Dom::from_ref(&**g)).collect()), + } + } + + pub fn new(window: &Window, cues: &[&TextTrackCue]) -> DomRoot<TextTrackCueList> { + reflect_dom_object(Box::new(TextTrackCueList::new_inherited(cues)), window) + } + + pub fn item(&self, idx: usize) -> Option<DomRoot<TextTrackCue>> { + self.dom_cues + .borrow() + .get(idx) + .map(|t| DomRoot::from_ref(&**t)) + } + + pub fn find(&self, cue: &TextTrackCue) -> Option<usize> { + self.dom_cues + .borrow() + .iter() + .enumerate() + .filter(|(_, c)| **c == cue) + .next() + .map(|(i, _)| i) + } + + pub fn add(&self, cue: &TextTrackCue) { + // Only add a cue if it does not exist in the list + if self.find(cue).is_none() { + self.dom_cues.borrow_mut().push(Dom::from_ref(cue)); + } + } + + pub fn remove(&self, idx: usize) { + self.dom_cues.borrow_mut().remove(idx); + } +} + +impl TextTrackCueListMethods for TextTrackCueList { + // https://html.spec.whatwg.org/multipage/#dom-texttrackcuelist-length + fn Length(&self) -> u32 { + self.dom_cues.borrow().len() as u32 + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcuelist-item + fn IndexedGetter(&self, idx: u32) -> Option<DomRoot<TextTrackCue>> { + self.item(idx as usize) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttrackcuelist-getcuebyid + fn GetCueById(&self, id: DOMString) -> Option<DomRoot<TextTrackCue>> { + if id.is_empty() { + None + } else { + self.dom_cues + .borrow() + .iter() + .filter(|cue| cue.id() == id) + .next() + .map(|t| DomRoot::from_ref(&**t)) + } + } +} diff --git a/components/script/dom/texttracklist.rs b/components/script/dom/texttracklist.rs new file mode 100644 index 00000000000..faeb9f0cd6d --- /dev/null +++ b/components/script/dom/texttracklist.rs @@ -0,0 +1,140 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::TextTrackListBinding::TextTrackListMethods; +use crate::dom::bindings::codegen::UnionTypes::VideoTrackOrAudioTrackOrTextTrack; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::texttrack::TextTrack; +use crate::dom::trackevent::TrackEvent; +use crate::dom::window::Window; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct TextTrackList { + eventtarget: EventTarget, + dom_tracks: DomRefCell<Vec<Dom<TextTrack>>>, +} + +impl TextTrackList { + pub fn new_inherited(tracks: &[&TextTrack]) -> TextTrackList { + TextTrackList { + eventtarget: EventTarget::new_inherited(), + dom_tracks: DomRefCell::new(tracks.iter().map(|g| Dom::from_ref(&**g)).collect()), + } + } + + pub fn new(window: &Window, tracks: &[&TextTrack]) -> DomRoot<TextTrackList> { + reflect_dom_object(Box::new(TextTrackList::new_inherited(tracks)), window) + } + + pub fn item(&self, idx: usize) -> Option<DomRoot<TextTrack>> { + self.dom_tracks + .borrow() + .get(idx as usize) + .map(|t| DomRoot::from_ref(&**t)) + } + + pub fn find(&self, track: &TextTrack) -> Option<usize> { + self.dom_tracks + .borrow() + .iter() + .enumerate() + .filter(|(_, t)| **t == track) + .next() + .map(|(i, _)| i) + } + + pub fn add(&self, track: &TextTrack) { + // Only add a track if it does not exist in the list + if self.find(track).is_none() { + self.dom_tracks.borrow_mut().push(Dom::from_ref(track)); + + let this = Trusted::new(self); + let (source, canceller) = &self + .global() + .as_window() + .task_manager() + .media_element_task_source_with_canceller(); + + let idx = match self.find(&track) { + Some(t) => t, + None => return, + }; + + let _ = source.queue_with_canceller( + task!(track_event_queue: move || { + let this = this.root(); + + if let Some(track) = this.item(idx) { + let event = TrackEvent::new( + &this.global(), + atom!("addtrack"), + false, + false, + &Some(VideoTrackOrAudioTrackOrTextTrack::TextTrack( + DomRoot::from_ref(&track) + )), + ); + + event.upcast::<Event>().fire(this.upcast::<EventTarget>()); + } + }), + &canceller, + ); + track.add_track_list(self); + } + } + + // FIXME(#22314, dlrobertson) allow TextTracks to be + // removed from the TextTrackList. + #[allow(dead_code)] + pub fn remove(&self, idx: usize) { + if let Some(track) = self.dom_tracks.borrow().get(idx) { + track.remove_track_list(); + } + self.dom_tracks.borrow_mut().remove(idx); + self.upcast::<EventTarget>() + .fire_event(atom!("removetrack")); + } +} + +impl TextTrackListMethods for TextTrackList { + // https://html.spec.whatwg.org/multipage/#dom-texttracklist-length + fn Length(&self) -> u32 { + self.dom_tracks.borrow().len() as u32 + } + + // https://html.spec.whatwg.org/multipage/#dom-texttracklist-item + fn IndexedGetter(&self, idx: u32) -> Option<DomRoot<TextTrack>> { + self.item(idx as usize) + } + + // https://html.spec.whatwg.org/multipage/#dom-texttracklist-gettrackbyid + fn GetTrackById(&self, id: DOMString) -> Option<DomRoot<TextTrack>> { + let id_str = String::from(id.clone()); + self.dom_tracks + .borrow() + .iter() + .filter(|track| track.id() == &id_str) + .next() + .map(|t| DomRoot::from_ref(&**t)) + } + + // https://html.spec.whatwg.org/multipage/#handler-texttracklist-onchange + event_handler!(change, GetOnchange, SetOnchange); + + // https://html.spec.whatwg.org/multipage/#handler-texttracklist-onaddtrack + event_handler!(addtrack, GetOnaddtrack, SetOnaddtrack); + + // https://html.spec.whatwg.org/multipage/#handler-texttracklist-onremovetrack + event_handler!(removetrack, GetOnremovetrack, SetOnremovetrack); +} diff --git a/components/script/dom/timeranges.rs b/components/script/dom/timeranges.rs new file mode 100644 index 00000000000..7a60210b0e0 --- /dev/null +++ b/components/script/dom/timeranges.rs @@ -0,0 +1,165 @@ +/* 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 crate::dom::bindings::codegen::Bindings::TimeRangesBinding::TimeRangesMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use std::fmt; + +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct TimeRange { + start: f64, + end: f64, +} + +impl TimeRange { + pub fn union(&mut self, other: &TimeRange) { + self.start = f64::min(self.start, other.start); + self.end = f64::max(self.end, other.end); + } + + fn contains(&self, time: f64) -> bool { + self.start <= time && time < self.end + } + + fn is_overlapping(&self, other: &TimeRange) -> bool { + // This also covers the case where `self` is entirely contained within `other`, + // for example: `self` = [2,3) and `other` = [1,4). + self.contains(other.start) || self.contains(other.end) || other.contains(self.start) + } + + fn is_contiguous(&self, other: &TimeRange) -> bool { + other.start == self.end || other.end == self.start + } + + pub fn is_before(&self, other: &TimeRange) -> bool { + other.start >= self.end + } +} + +impl fmt::Debug for TimeRange { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(fmt, "[{},{})", self.start, self.end) + } +} + +#[derive(Debug)] +pub enum TimeRangesError { + EndOlderThanStart, + OutOfRange, +} + +#[derive(Clone, Debug, JSTraceable, MallocSizeOf)] +pub struct TimeRangesContainer { + ranges: Vec<TimeRange>, +} + +impl TimeRangesContainer { + pub fn new() -> Self { + Self { ranges: Vec::new() } + } + + pub fn len(&self) -> u32 { + self.ranges.len() as u32 + } + + pub fn start(&self, index: u32) -> Result<f64, TimeRangesError> { + self.ranges + .get(index as usize) + .map(|r| r.start) + .ok_or(TimeRangesError::OutOfRange) + } + + pub fn end(&self, index: u32) -> Result<f64, TimeRangesError> { + self.ranges + .get(index as usize) + .map(|r| r.end) + .ok_or(TimeRangesError::OutOfRange) + } + + pub fn add(&mut self, start: f64, end: f64) -> Result<(), TimeRangesError> { + if start > end { + return Err(TimeRangesError::EndOlderThanStart); + } + + let mut new_range = TimeRange { start, end }; + + // For each present range check if we need to: + // - merge with the added range, in case we are overlapping or contiguous, + // - insert in place, we are completely, not overlapping and not contiguous + // in between two ranges. + let mut idx = 0; + while idx < self.ranges.len() { + if new_range.is_overlapping(&self.ranges[idx]) || + new_range.is_contiguous(&self.ranges[idx]) + { + // The ranges are either overlapping or contiguous, + // we need to merge the new range with the existing one. + new_range.union(&self.ranges[idx]); + self.ranges.remove(idx); + } else if new_range.is_before(&self.ranges[idx]) && + (idx == 0 || self.ranges[idx - 1].is_before(&new_range)) + { + // We are exactly after the current previous range and before the current + // range, while not overlapping with none of them. + // Or we are simply at the beginning. + self.ranges.insert(idx, new_range); + return Ok(()); + } else { + idx += 1; + } + } + + // Insert at the end. + self.ranges.insert(idx, new_range); + + Ok(()) + } +} + +#[dom_struct] +pub struct TimeRanges { + reflector_: Reflector, + ranges: TimeRangesContainer, +} + +impl TimeRanges { + fn new_inherited(ranges: TimeRangesContainer) -> TimeRanges { + Self { + reflector_: Reflector::new(), + ranges, + } + } + + pub fn new(window: &Window, ranges: TimeRangesContainer) -> DomRoot<TimeRanges> { + reflect_dom_object(Box::new(TimeRanges::new_inherited(ranges)), window) + } +} + +impl TimeRangesMethods for TimeRanges { + // https://html.spec.whatwg.org/multipage/#dom-timeranges-length + fn Length(&self) -> u32 { + self.ranges.len() + } + + // https://html.spec.whatwg.org/multipage/#dom-timeranges-start + fn Start(&self, index: u32) -> Fallible<Finite<f64>> { + self.ranges + .start(index) + .map(Finite::wrap) + .map_err(|_| Error::IndexSize) + } + + // https://html.spec.whatwg.org/multipage/#dom-timeranges-end + fn End(&self, index: u32) -> Fallible<Finite<f64>> { + self.ranges + .end(index) + .map(Finite::wrap) + .map_err(|_| Error::IndexSize) + } +} diff --git a/components/script/dom/touch.rs b/components/script/dom/touch.rs index 3ebfd17af12..60e69a48ca3 100644 --- a/components/script/dom/touch.rs +++ b/components/script/dom/touch.rs @@ -1,21 +1,20 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::TouchBinding; -use dom::bindings::codegen::Bindings::TouchBinding::TouchMethods; -use dom::bindings::js::{MutJS, Root}; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::eventtarget::EventTarget; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::TouchBinding::TouchMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutDom}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::window::Window; use dom_struct::dom_struct; #[dom_struct] pub struct Touch { reflector_: Reflector, identifier: i32, - target: MutJS<EventTarget>, + target: MutDom<EventTarget>, screen_x: f64, screen_y: f64, client_x: f64, @@ -25,14 +24,20 @@ pub struct Touch { } impl Touch { - fn new_inherited(identifier: i32, target: &EventTarget, - screen_x: Finite<f64>, screen_y: Finite<f64>, - client_x: Finite<f64>, client_y: Finite<f64>, - page_x: Finite<f64>, page_y: Finite<f64>) -> Touch { + fn new_inherited( + identifier: i32, + target: &EventTarget, + screen_x: Finite<f64>, + screen_y: Finite<f64>, + client_x: Finite<f64>, + client_y: Finite<f64>, + page_x: Finite<f64>, + page_y: Finite<f64>, + ) -> Touch { Touch { reflector_: Reflector::new(), identifier: identifier, - target: MutJS::new(target), + target: MutDom::new(target), screen_x: *screen_x, screen_y: *screen_y, client_x: *client_x, @@ -42,56 +47,63 @@ impl Touch { } } - pub fn new(window: &Window, identifier: i32, target: &EventTarget, - screen_x: Finite<f64>, screen_y: Finite<f64>, - client_x: Finite<f64>, client_y: Finite<f64>, - page_x: Finite<f64>, page_y: Finite<f64>) -> Root<Touch> { - reflect_dom_object(box Touch::new_inherited(identifier, target, - screen_x, screen_y, - client_x, client_y, - page_x, page_y), - window, - TouchBinding::Wrap) + pub fn new( + window: &Window, + identifier: i32, + target: &EventTarget, + screen_x: Finite<f64>, + screen_y: Finite<f64>, + client_x: Finite<f64>, + client_y: Finite<f64>, + page_x: Finite<f64>, + page_y: Finite<f64>, + ) -> DomRoot<Touch> { + reflect_dom_object( + Box::new(Touch::new_inherited( + identifier, target, screen_x, screen_y, client_x, client_y, page_x, page_y, + )), + window, + ) } } impl TouchMethods for Touch { - /// https://w3c.github.io/touch-events/#widl-Touch-identifier + /// <https://w3c.github.io/touch-events/#widl-Touch-identifier> fn Identifier(&self) -> i32 { self.identifier } - /// https://w3c.github.io/touch-events/#widl-Touch-target - fn Target(&self) -> Root<EventTarget> { + /// <https://w3c.github.io/touch-events/#widl-Touch-target> + fn Target(&self) -> DomRoot<EventTarget> { self.target.get() } - /// https://w3c.github.io/touch-events/#widl-Touch-screenX + /// <https://w3c.github.io/touch-events/#widl-Touch-screenX> fn ScreenX(&self) -> Finite<f64> { Finite::wrap(self.screen_x) } - /// https://w3c.github.io/touch-events/#widl-Touch-screenY + /// <https://w3c.github.io/touch-events/#widl-Touch-screenY> fn ScreenY(&self) -> Finite<f64> { Finite::wrap(self.screen_y) } - /// https://w3c.github.io/touch-events/#widl-Touch-clientX + /// <https://w3c.github.io/touch-events/#widl-Touch-clientX> fn ClientX(&self) -> Finite<f64> { Finite::wrap(self.client_x) } - /// https://w3c.github.io/touch-events/#widl-Touch-clientY + /// <https://w3c.github.io/touch-events/#widl-Touch-clientY> fn ClientY(&self) -> Finite<f64> { Finite::wrap(self.client_y) } - /// https://w3c.github.io/touch-events/#widl-Touch-clientX + /// <https://w3c.github.io/touch-events/#widl-Touch-clientX> fn PageX(&self) -> Finite<f64> { Finite::wrap(self.page_x) } - /// https://w3c.github.io/touch-events/#widl-Touch-clientY + /// <https://w3c.github.io/touch-events/#widl-Touch-clientY> fn PageY(&self) -> Finite<f64> { Finite::wrap(self.page_y) } diff --git a/components/script/dom/touchevent.rs b/components/script/dom/touchevent.rs index 30044094fcd..4dfbed55147 100644 --- a/components/script/dom/touchevent.rs +++ b/components/script/dom/touchevent.rs @@ -1,27 +1,26 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::TouchEventBinding; -use dom::bindings::codegen::Bindings::TouchEventBinding::TouchEventMethods; -use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutJS, Root}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::{EventBubbles, EventCancelable}; -use dom::touchlist::TouchList; -use dom::uievent::UIEvent; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::TouchEventBinding::TouchEventMethods; +use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{DomRoot, MutDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{EventBubbles, EventCancelable}; +use crate::dom::touchlist::TouchList; +use crate::dom::uievent::UIEvent; +use crate::dom::window::Window; use dom_struct::dom_struct; use std::cell::Cell; #[dom_struct] pub struct TouchEvent { uievent: UIEvent, - touches: MutJS<TouchList>, - target_touches: MutJS<TouchList>, - changed_touches: MutJS<TouchList>, + touches: MutDom<TouchList>, + target_touches: MutDom<TouchList>, + changed_touches: MutDom<TouchList>, alt_key: Cell<bool>, meta_key: Cell<bool>, ctrl_key: Cell<bool>, @@ -29,14 +28,16 @@ pub struct TouchEvent { } impl TouchEvent { - fn new_inherited(touches: &TouchList, - changed_touches: &TouchList, - target_touches: &TouchList) -> TouchEvent { + fn new_inherited( + touches: &TouchList, + changed_touches: &TouchList, + target_touches: &TouchList, + ) -> TouchEvent { TouchEvent { uievent: UIEvent::new_inherited(), - touches: MutJS::new(touches), - target_touches: MutJS::new(target_touches), - changed_touches: MutJS::new(changed_touches), + touches: MutDom::new(touches), + target_touches: MutDom::new(target_touches), + changed_touches: MutDom::new(changed_touches), ctrl_key: Cell::new(false), shift_key: Cell::new(false), alt_key: Cell::new(false), @@ -44,33 +45,45 @@ impl TouchEvent { } } - pub fn new_uninitialized(window: &Window, - touches: &TouchList, - changed_touches: &TouchList, - target_touches: &TouchList) -> Root<TouchEvent> { - reflect_dom_object(box TouchEvent::new_inherited(touches, changed_touches, target_touches), - window, - TouchEventBinding::Wrap) + pub fn new_uninitialized( + window: &Window, + touches: &TouchList, + changed_touches: &TouchList, + target_touches: &TouchList, + ) -> DomRoot<TouchEvent> { + reflect_dom_object( + Box::new(TouchEvent::new_inherited( + touches, + changed_touches, + target_touches, + )), + window, + ) } - pub fn new(window: &Window, - type_: DOMString, - can_bubble: EventBubbles, - cancelable: EventCancelable, - view: Option<&Window>, - detail: i32, - touches: &TouchList, - changed_touches: &TouchList, - target_touches: &TouchList, - ctrl_key: bool, - alt_key: bool, - shift_key: bool, - meta_key: bool) -> Root<TouchEvent> { + pub fn new( + window: &Window, + type_: DOMString, + can_bubble: EventBubbles, + cancelable: EventCancelable, + view: Option<&Window>, + detail: i32, + touches: &TouchList, + changed_touches: &TouchList, + target_touches: &TouchList, + ctrl_key: bool, + alt_key: bool, + shift_key: bool, + meta_key: bool, + ) -> DomRoot<TouchEvent> { let ev = TouchEvent::new_uninitialized(window, touches, changed_touches, target_touches); - ev.upcast::<UIEvent>().InitUIEvent(type_, - bool::from(can_bubble), - bool::from(cancelable), - view, detail); + ev.upcast::<UIEvent>().InitUIEvent( + type_, + bool::from(can_bubble), + bool::from(cancelable), + view, + detail, + ); ev.ctrl_key.set(ctrl_key); ev.alt_key.set(alt_key); ev.shift_key.set(shift_key); @@ -80,42 +93,42 @@ impl TouchEvent { } impl<'a> TouchEventMethods for &'a TouchEvent { - /// https://w3c.github.io/touch-events/#widl-TouchEvent-ctrlKey + /// <https://w3c.github.io/touch-events/#widl-TouchEvent-ctrlKey> fn CtrlKey(&self) -> bool { self.ctrl_key.get() } - /// https://w3c.github.io/touch-events/#widl-TouchEvent-shiftKey + /// <https://w3c.github.io/touch-events/#widl-TouchEvent-shiftKey> fn ShiftKey(&self) -> bool { self.shift_key.get() } - /// https://w3c.github.io/touch-events/#widl-TouchEvent-altKey + /// <https://w3c.github.io/touch-events/#widl-TouchEvent-altKey> fn AltKey(&self) -> bool { self.alt_key.get() } - /// https://w3c.github.io/touch-events/#widl-TouchEvent-metaKey + /// <https://w3c.github.io/touch-events/#widl-TouchEvent-metaKey> fn MetaKey(&self) -> bool { self.meta_key.get() } - /// https://w3c.github.io/touch-events/#widl-TouchEventInit-touches - fn Touches(&self) -> Root<TouchList> { + /// <https://w3c.github.io/touch-events/#widl-TouchEventInit-touches> + fn Touches(&self) -> DomRoot<TouchList> { self.touches.get() } - /// https://w3c.github.io/touch-events/#widl-TouchEvent-targetTouches - fn TargetTouches(&self) -> Root<TouchList> { + /// <https://w3c.github.io/touch-events/#widl-TouchEvent-targetTouches> + fn TargetTouches(&self) -> DomRoot<TouchList> { self.target_touches.get() } - /// https://w3c.github.io/touch-events/#widl-TouchEvent-changedTouches - fn ChangedTouches(&self) -> Root<TouchList> { + /// <https://w3c.github.io/touch-events/#widl-TouchEvent-changedTouches> + fn ChangedTouches(&self) -> DomRoot<TouchList> { self.changed_touches.get() } - /// https://dom.spec.whatwg.org/#dom-event-istrusted + /// <https://dom.spec.whatwg.org/#dom-event-istrusted> fn IsTrusted(&self) -> bool { self.uievent.IsTrusted() } diff --git a/components/script/dom/touchlist.rs b/components/script/dom/touchlist.rs index 829b6f0d5a6..46400df8ca1 100644 --- a/components/script/dom/touchlist.rs +++ b/components/script/dom/touchlist.rs @@ -1,48 +1,48 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::TouchListBinding; -use dom::bindings::codegen::Bindings::TouchListBinding::TouchListMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::touch::Touch; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::TouchListBinding::TouchListMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::touch::Touch; +use crate::dom::window::Window; use dom_struct::dom_struct; #[dom_struct] pub struct TouchList { reflector_: Reflector, - touches: Vec<JS<Touch>>, + touches: Vec<Dom<Touch>>, } impl TouchList { fn new_inherited(touches: &[&Touch]) -> TouchList { TouchList { reflector_: Reflector::new(), - touches: touches.iter().map(|touch| JS::from_ref(*touch)).collect(), + touches: touches.iter().map(|touch| Dom::from_ref(*touch)).collect(), } } - pub fn new(window: &Window, touches: &[&Touch]) -> Root<TouchList> { - reflect_dom_object(box TouchList::new_inherited(touches), - window, TouchListBinding::Wrap) + pub fn new(window: &Window, touches: &[&Touch]) -> DomRoot<TouchList> { + reflect_dom_object(Box::new(TouchList::new_inherited(touches)), window) } } impl TouchListMethods for TouchList { - /// https://w3c.github.io/touch-events/#widl-TouchList-length + /// <https://w3c.github.io/touch-events/#widl-TouchList-length> fn Length(&self) -> u32 { self.touches.len() as u32 } - /// https://w3c.github.io/touch-events/#widl-TouchList-item-getter-Touch-unsigned-long-index - fn Item(&self, index: u32) -> Option<Root<Touch>> { - self.touches.get(index as usize).map(|js| Root::from_ref(&**js)) + /// <https://w3c.github.io/touch-events/#widl-TouchList-item-getter-Touch-unsigned-long-index> + fn Item(&self, index: u32) -> Option<DomRoot<Touch>> { + self.touches + .get(index as usize) + .map(|js| DomRoot::from_ref(&**js)) } - /// https://w3c.github.io/touch-events/#widl-TouchList-item-getter-Touch-unsigned-long-index - fn IndexedGetter(&self, index: u32) -> Option<Root<Touch>> { + /// <https://w3c.github.io/touch-events/#widl-TouchList-item-getter-Touch-unsigned-long-index> + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Touch>> { self.Item(index) } } diff --git a/components/script/dom/trackevent.rs b/components/script/dom/trackevent.rs new file mode 100644 index 00000000000..34148f26527 --- /dev/null +++ b/components/script/dom/trackevent.rs @@ -0,0 +1,112 @@ +/* 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 crate::dom::audiotrack::AudioTrack; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::TrackEventBinding; +use crate::dom::bindings::codegen::Bindings::TrackEventBinding::TrackEventMethods; +use crate::dom::bindings::codegen::UnionTypes::VideoTrackOrAudioTrackOrTextTrack; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::texttrack::TextTrack; +use crate::dom::videotrack::VideoTrack; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +enum MediaTrack { + Video(Dom<VideoTrack>), + Audio(Dom<AudioTrack>), + Text(Dom<TextTrack>), +} + +#[dom_struct] +pub struct TrackEvent { + event: Event, + track: Option<MediaTrack>, +} + +#[allow(non_snake_case)] +impl TrackEvent { + #[allow(unrooted_must_root)] + fn new_inherited(track: &Option<VideoTrackOrAudioTrackOrTextTrack>) -> TrackEvent { + let media_track = match track { + Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(VideoTrack)) => { + Some(MediaTrack::Video(Dom::from_ref(VideoTrack))) + }, + Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(AudioTrack)) => { + Some(MediaTrack::Audio(Dom::from_ref(AudioTrack))) + }, + Some(VideoTrackOrAudioTrackOrTextTrack::TextTrack(TextTrack)) => { + Some(MediaTrack::Text(Dom::from_ref(TextTrack))) + }, + None => None, + }; + + TrackEvent { + event: Event::new_inherited(), + track: media_track, + } + } + + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + track: &Option<VideoTrackOrAudioTrackOrTextTrack>, + ) -> DomRoot<TrackEvent> { + let te = reflect_dom_object(Box::new(TrackEvent::new_inherited(&track)), global); + { + let event = te.upcast::<Event>(); + event.init_event(type_, bubbles, cancelable); + } + te + } + + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &TrackEventBinding::TrackEventInit, + ) -> Fallible<DomRoot<TrackEvent>> { + Ok(TrackEvent::new( + &window.global(), + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + &init.track, + )) + } +} + +#[allow(non_snake_case)] +impl TrackEventMethods for TrackEvent { + // https://html.spec.whatwg.org/multipage/#dom-trackevent-track + fn GetTrack(&self) -> Option<VideoTrackOrAudioTrackOrTextTrack> { + match &self.track { + Some(MediaTrack::Video(VideoTrack)) => Some( + VideoTrackOrAudioTrackOrTextTrack::VideoTrack(DomRoot::from_ref(VideoTrack)), + ), + Some(MediaTrack::Audio(AudioTrack)) => Some( + VideoTrackOrAudioTrackOrTextTrack::AudioTrack(DomRoot::from_ref(AudioTrack)), + ), + Some(MediaTrack::Text(TextTrack)) => Some( + VideoTrackOrAudioTrackOrTextTrack::TextTrack(DomRoot::from_ref(TextTrack)), + ), + None => None, + } + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/transitionevent.rs b/components/script/dom/transitionevent.rs index 28aec7097f8..2305a9489ff 100644 --- a/components/script/dom/transitionevent.rs +++ b/components/script/dom/transitionevent.rs @@ -1,18 +1,19 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::TransitionEventBinding; -use dom::bindings::codegen::Bindings::TransitionEventBinding::{TransitionEventInit, TransitionEventMethods}; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::num::Finite; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::TransitionEventBinding::{ + TransitionEventInit, TransitionEventMethods, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; @@ -30,16 +31,16 @@ impl TransitionEvent { event: Event::new_inherited(), property_name: Atom::from(init.propertyName.clone()), elapsed_time: init.elapsedTime.clone(), - pseudo_element: init.pseudoElement.clone() + pseudo_element: init.pseudoElement.clone(), } } - pub fn new(window: &Window, - type_: Atom, - init: &TransitionEventInit) -> Root<TransitionEvent> { - let ev = reflect_dom_object(box TransitionEvent::new_inherited(init), - window, - TransitionEventBinding::Wrap); + pub fn new( + window: &Window, + type_: Atom, + init: &TransitionEventInit, + ) -> DomRoot<TransitionEvent> { + let ev = reflect_dom_object(Box::new(TransitionEvent::new_inherited(init)), window); { let event = ev.upcast::<Event>(); event.init_event(type_, init.parent.bubbles, init.parent.cancelable); @@ -47,9 +48,12 @@ impl TransitionEvent { ev } - pub fn Constructor(window: &Window, - type_: DOMString, - init: &TransitionEventInit) -> Fallible<Root<TransitionEvent>> { + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &TransitionEventInit, + ) -> Fallible<DomRoot<TransitionEvent>> { Ok(TransitionEvent::new(window, Atom::from(type_), init)) } } diff --git a/components/script/dom/treewalker.rs b/components/script/dom/treewalker.rs index 2409d65af4f..86aa97466a7 100644 --- a/components/script/dom/treewalker.rs +++ b/components/script/dom/treewalker.rs @@ -1,62 +1,66 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::callback::ExceptionHandling::Rethrow; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter; -use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilterConstants; -use dom::bindings::codegen::Bindings::TreeWalkerBinding; -use dom::bindings::codegen::Bindings::TreeWalkerBinding::TreeWalkerMethods; -use dom::bindings::error::Fallible; -use dom::bindings::js::{JS, MutJS}; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::document::Document; -use dom::node::Node; +use crate::dom::bindings::callback::ExceptionHandling::Rethrow; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter; +use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilterConstants; +use crate::dom::bindings::codegen::Bindings::TreeWalkerBinding::TreeWalkerMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutDom}; +use crate::dom::document::Document; +use crate::dom::node::Node; use dom_struct::dom_struct; +use std::cell::Cell; use std::rc::Rc; // https://dom.spec.whatwg.org/#interface-treewalker #[dom_struct] pub struct TreeWalker { reflector_: Reflector, - root_node: JS<Node>, - current_node: MutJS<Node>, + root_node: Dom<Node>, + current_node: MutDom<Node>, what_to_show: u32, - #[ignore_heap_size_of = "function pointers and Rc<T> are hard"] - filter: Filter + #[ignore_malloc_size_of = "function pointers and Rc<T> are hard"] + filter: Filter, + active: Cell<bool>, } impl TreeWalker { - fn new_inherited(root_node: &Node, - what_to_show: u32, - filter: Filter) -> TreeWalker { + fn new_inherited(root_node: &Node, what_to_show: u32, filter: Filter) -> TreeWalker { TreeWalker { reflector_: Reflector::new(), - root_node: JS::from_ref(root_node), - current_node: MutJS::new(root_node), + root_node: Dom::from_ref(root_node), + current_node: MutDom::new(root_node), what_to_show: what_to_show, - filter: filter + filter: filter, + active: Cell::new(false), } } - pub fn new_with_filter(document: &Document, - root_node: &Node, - what_to_show: u32, - filter: Filter) -> Root<TreeWalker> { - reflect_dom_object(box TreeWalker::new_inherited(root_node, what_to_show, filter), - document.window(), - TreeWalkerBinding::Wrap) + pub fn new_with_filter( + document: &Document, + root_node: &Node, + what_to_show: u32, + filter: Filter, + ) -> DomRoot<TreeWalker> { + reflect_dom_object( + Box::new(TreeWalker::new_inherited(root_node, what_to_show, filter)), + document.window(), + ) } - pub fn new(document: &Document, - root_node: &Node, - what_to_show: u32, - node_filter: Option<Rc<NodeFilter>>) -> Root<TreeWalker> { + pub fn new( + document: &Document, + root_node: &Node, + what_to_show: u32, + node_filter: Option<Rc<NodeFilter>>, + ) -> DomRoot<TreeWalker> { let filter = match node_filter { None => Filter::None, - Some(jsfilter) => Filter::JS(jsfilter) + Some(jsfilter) => Filter::Dom(jsfilter), }; TreeWalker::new_with_filter(document, root_node, what_to_show, filter) } @@ -64,8 +68,8 @@ impl TreeWalker { impl TreeWalkerMethods for TreeWalker { // https://dom.spec.whatwg.org/#dom-treewalker-root - fn Root(&self) -> Root<Node> { - Root::from_ref(&*self.root_node) + fn Root(&self) -> DomRoot<Node> { + DomRoot::from_ref(&*self.root_node) } // https://dom.spec.whatwg.org/#dom-treewalker-whattoshow @@ -77,13 +81,12 @@ impl TreeWalkerMethods for TreeWalker { fn GetFilter(&self) -> Option<Rc<NodeFilter>> { match self.filter { Filter::None => None, - Filter::JS(ref nf) => Some(nf.clone()), - Filter::Native(_) => panic!("Cannot convert native node filter to DOM NodeFilter") + Filter::Dom(ref nf) => Some(nf.clone()), } } // https://dom.spec.whatwg.org/#dom-treewalker-currentnode - fn CurrentNode(&self) -> Root<Node> { + fn CurrentNode(&self) -> DomRoot<Node> { self.current_node.get() } @@ -93,7 +96,7 @@ impl TreeWalkerMethods for TreeWalker { } // https://dom.spec.whatwg.org/#dom-treewalker-parentnode - fn ParentNode(&self) -> Fallible<Option<Root<Node>>> { + fn ParentNode(&self) -> Fallible<Option<DomRoot<Node>>> { // "1. Let node be the value of the currentNode attribute." let mut node = self.current_node.get(); // "2. While node is not null and is not root, run these substeps:" @@ -104,9 +107,9 @@ impl TreeWalkerMethods for TreeWalker { node = n; // "2. If node is not null and filtering node returns FILTER_ACCEPT, // then set the currentNode attribute to node, return node." - if NodeFilterConstants::FILTER_ACCEPT == try!(self.accept_node(&node)) { + if NodeFilterConstants::FILTER_ACCEPT == self.accept_node(&node)? { self.current_node.set(&node); - return Ok(Some(node)) + return Ok(Some(node)); } }, None => break, @@ -117,35 +120,31 @@ impl TreeWalkerMethods for TreeWalker { } // https://dom.spec.whatwg.org/#dom-treewalker-firstchild - fn FirstChild(&self) -> Fallible<Option<Root<Node>>> { + fn FirstChild(&self) -> Fallible<Option<DomRoot<Node>>> { // "The firstChild() method must traverse children of type first." - self.traverse_children(|node| node.GetFirstChild(), - |node| node.GetNextSibling()) + self.traverse_children(|node| node.GetFirstChild(), |node| node.GetNextSibling()) } // https://dom.spec.whatwg.org/#dom-treewalker-lastchild - fn LastChild(&self) -> Fallible<Option<Root<Node>>> { + fn LastChild(&self) -> Fallible<Option<DomRoot<Node>>> { // "The lastChild() method must traverse children of type last." - self.traverse_children(|node| node.GetLastChild(), - |node| node.GetPreviousSibling()) + self.traverse_children(|node| node.GetLastChild(), |node| node.GetPreviousSibling()) } // https://dom.spec.whatwg.org/#dom-treewalker-previoussibling - fn PreviousSibling(&self) -> Fallible<Option<Root<Node>>> { + fn PreviousSibling(&self) -> Fallible<Option<DomRoot<Node>>> { // "The nextSibling() method must traverse siblings of type next." - self.traverse_siblings(|node| node.GetLastChild(), - |node| node.GetPreviousSibling()) + self.traverse_siblings(|node| node.GetLastChild(), |node| node.GetPreviousSibling()) } // https://dom.spec.whatwg.org/#dom-treewalker-nextsibling - fn NextSibling(&self) -> Fallible<Option<Root<Node>>> { + fn NextSibling(&self) -> Fallible<Option<DomRoot<Node>>> { // "The previousSibling() method must traverse siblings of type previous." - self.traverse_siblings(|node| node.GetFirstChild(), - |node| node.GetNextSibling()) + self.traverse_siblings(|node| node.GetFirstChild(), |node| node.GetNextSibling()) } // https://dom.spec.whatwg.org/#dom-treewalker-previousnode - fn PreviousNode(&self) -> Fallible<Option<Root<Node>>> { + fn PreviousNode(&self) -> Fallible<Option<DomRoot<Node>>> { // "1. Let node be the value of the currentNode attribute." let mut node = self.current_node.get(); // "2. While node is not root, run these substeps:" @@ -163,16 +162,15 @@ impl TreeWalkerMethods for TreeWalker { // "4. If result is FILTER_ACCEPT, then // set the currentNode attribute to node and return node." loop { - let result = try!(self.accept_node(&node)); + let result = self.accept_node(&node)?; match result { NodeFilterConstants::FILTER_REJECT => break, - _ if node.GetFirstChild().is_some() => - node = node.GetLastChild().unwrap(), + _ if node.GetFirstChild().is_some() => node = node.GetLastChild().unwrap(), NodeFilterConstants::FILTER_ACCEPT => { self.current_node.set(&node); - return Ok(Some(node)) + return Ok(Some(node)); }, - _ => break + _ => break, } } // "5. Set sibling to the previous sibling of node." @@ -180,21 +178,23 @@ impl TreeWalkerMethods for TreeWalker { } // "3. If node is root or node's parent is null, return null." if self.is_root_node(&node) || node.GetParentNode().is_none() { - return Ok(None) + return Ok(None); } // "4. Set node to its parent." match node.GetParentNode() { None => - // This can happen if the user set the current node to somewhere - // outside of the tree rooted at the original root. - return Ok(None), - Some(n) => node = n + // This can happen if the user set the current node to somewhere + // outside of the tree rooted at the original root. + { + return Ok(None); + } + Some(n) => node = n, } // "5. Filter node and if the return value is FILTER_ACCEPT, then // set the currentNode attribute to node and return node." - if NodeFilterConstants::FILTER_ACCEPT == try!(self.accept_node(&node)) { + if NodeFilterConstants::FILTER_ACCEPT == self.accept_node(&node)? { self.current_node.set(&node); - return Ok(Some(node)) + return Ok(Some(node)); } } // "6. Return null." @@ -202,7 +202,7 @@ impl TreeWalkerMethods for TreeWalker { } // https://dom.spec.whatwg.org/#dom-treewalker-nextnode - fn NextNode(&self) -> Fallible<Option<Root<Node>>> { + fn NextNode(&self) -> Fallible<Option<DomRoot<Node>>> { // "1. Let node be the value of the currentNode attribute." let mut node = self.current_node.get(); // "2. Let result be FILTER_ACCEPT." @@ -220,14 +220,14 @@ impl TreeWalkerMethods for TreeWalker { // "1. Set node to its first child." node = child; // "2. Filter node and set result to the return value." - result = try!(self.accept_node(&node)); + result = self.accept_node(&node)?; // "3. If result is FILTER_ACCEPT, then // set the currentNode attribute to node and return node." if NodeFilterConstants::FILTER_ACCEPT == result { self.current_node.set(&node); - return Ok(Some(node)) + return Ok(Some(node)); } - } + }, } } // "2. If a node is following node and is not following root, @@ -238,14 +238,14 @@ impl TreeWalkerMethods for TreeWalker { Some(n) => { node = n; // "3. Filter node and set result to the return value." - result = try!(self.accept_node(&node)); + result = self.accept_node(&node)?; // "4. If result is FILTER_ACCEPT, then // set the currentNode attribute to node and return node." if NodeFilterConstants::FILTER_ACCEPT == result { self.current_node.set(&node); - return Ok(Some(node)) + return Ok(Some(node)); } - } + }, } // "5. Run these substeps again." } @@ -254,12 +254,14 @@ impl TreeWalkerMethods for TreeWalker { impl TreeWalker { // https://dom.spec.whatwg.org/#concept-traverse-children - fn traverse_children<F, G>(&self, - next_child: F, - next_sibling: G) - -> Fallible<Option<Root<Node>>> - where F: Fn(&Node) -> Option<Root<Node>>, - G: Fn(&Node) -> Option<Root<Node>> + fn traverse_children<F, G>( + &self, + next_child: F, + next_sibling: G, + ) -> Fallible<Option<DomRoot<Node>>> + where + F: Fn(&Node) -> Option<DomRoot<Node>>, + G: Fn(&Node) -> Option<DomRoot<Node>>, { // "To **traverse children** of type *type*, run these steps:" // "1. Let node be the value of the currentNode attribute." @@ -275,13 +277,13 @@ impl TreeWalker { // 4. Main: Repeat these substeps: 'main: loop { // "1. Filter node and let result be the return value." - let result = try!(self.accept_node(&node)); + let result = self.accept_node(&node)?; match result { // "2. If result is FILTER_ACCEPT, then set the currentNode // attribute to node and return node." NodeFilterConstants::FILTER_ACCEPT => { self.current_node.set(&node); - return Ok(Some(Root::from_ref(&node))) + return Ok(Some(DomRoot::from_ref(&node))); }, // "3. If result is FILTER_SKIP, run these subsubsteps:" NodeFilterConstants::FILTER_SKIP => { @@ -290,10 +292,10 @@ impl TreeWalker { if let Some(child) = next_child(&node) { // "2. If child is not null, set node to child and goto Main." node = child; - continue 'main + continue 'main; } }, - _ => {} + _ => {}, } // "4. Repeat these subsubsteps:" loop { @@ -304,7 +306,7 @@ impl TreeWalker { // set node to sibling and goto Main." Some(sibling) => { node = sibling; - continue 'main + continue 'main; }, None => { // "3. Let parent be node's parent." @@ -313,32 +315,36 @@ impl TreeWalker { // or parent is currentNode attribute's value, // return null." None => return Ok(None), - Some(ref parent) if self.is_root_node(&parent) - || self.is_current_node(&parent) => - return Ok(None), + Some(ref parent) + if self.is_root_node(&parent) || self.is_current_node(&parent) => + { + return Ok(None); + } // "5. Otherwise, set node to parent." - Some(parent) => node = parent + Some(parent) => node = parent, } - } + }, } } } } // https://dom.spec.whatwg.org/#concept-traverse-siblings - fn traverse_siblings<F, G>(&self, - next_child: F, - next_sibling: G) - -> Fallible<Option<Root<Node>>> - where F: Fn(&Node) -> Option<Root<Node>>, - G: Fn(&Node) -> Option<Root<Node>> + fn traverse_siblings<F, G>( + &self, + next_child: F, + next_sibling: G, + ) -> Fallible<Option<DomRoot<Node>>> + where + F: Fn(&Node) -> Option<DomRoot<Node>>, + G: Fn(&Node) -> Option<DomRoot<Node>>, { // "To **traverse siblings** of type *type* run these steps:" // "1. Let node be the value of the currentNode attribute." let mut node = self.current_node.get(); // "2. If node is root, return null." if self.is_root_node(&node) { - return Ok(None) + return Ok(None); } // "3. Run these substeps:" loop { @@ -350,12 +356,12 @@ impl TreeWalker { // "1. Set node to sibling." node = sibling_op.unwrap(); // "2. Filter node and let result be the return value." - let result = try!(self.accept_node(&node)); + let result = self.accept_node(&node)?; // "3. If result is FILTER_ACCEPT, then set the currentNode // attribute to node and return node." if NodeFilterConstants::FILTER_ACCEPT == result { self.current_node.set(&node); - return Ok(Some(node)) + return Ok(Some(node)); } // "4. Set sibling to node's first child if type is next, @@ -365,9 +371,10 @@ impl TreeWalker { // then set sibling to node's next sibling if type is next, // and node's previous sibling if type is previous." match (result, &sibling_op) { - (NodeFilterConstants::FILTER_REJECT, _) - | (_, &None) => sibling_op = next_sibling(&node), - _ => {} + (NodeFilterConstants::FILTER_REJECT, _) | (_, &None) => { + sibling_op = next_sibling(&node) + }, + _ => {}, } } // "3. Set node to its parent." @@ -378,31 +385,26 @@ impl TreeWalker { // "5. Filter node and if the return value is FILTER_ACCEPT, then return null." Some(n) => { node = n; - if NodeFilterConstants::FILTER_ACCEPT == try!(self.accept_node(&node)) { - return Ok(None) + if NodeFilterConstants::FILTER_ACCEPT == self.accept_node(&node)? { + return Ok(None); } - } + }, } // "6. Run these substeps again." } } // https://dom.spec.whatwg.org/#concept-tree-following - fn first_following_node_not_following_root(&self, node: &Node) - -> Option<Root<Node>> { + fn first_following_node_not_following_root(&self, node: &Node) -> Option<DomRoot<Node>> { // "An object A is following an object B if A and B are in the same tree // and A comes after B in tree order." match node.GetNextSibling() { None => { - let mut candidate = Root::from_ref(node); + let mut candidate = DomRoot::from_ref(node); while !self.is_root_node(&candidate) && candidate.GetNextSibling().is_none() { - match candidate.GetParentNode() { - None => - // This can happen if the user set the current node to somewhere - // outside of the tree rooted at the original root. - return None, - Some(n) => candidate = n - } + // This can return None if the user set the current node to somewhere + // outside of the tree rooted at the original root. + candidate = candidate.GetParentNode()?; } if self.is_root_node(&candidate) { None @@ -410,33 +412,40 @@ impl TreeWalker { candidate.GetNextSibling() } }, - it => it + it => it, } } // https://dom.spec.whatwg.org/#concept-node-filter fn accept_node(&self, node: &Node) -> Fallible<u16> { - // "To filter node run these steps:" - // "1. Let n be node's nodeType attribute value minus 1." + // Step 1. + if self.active.get() { + return Err(Error::InvalidState); + } + // Step 2. let n = node.NodeType() - 1; - // "2. If the nth bit (where 0 is the least significant bit) of whatToShow is not set, - // return FILTER_SKIP." + // Step 3. if (self.what_to_show & (1 << n)) == 0 { - return Ok(NodeFilterConstants::FILTER_SKIP) + return Ok(NodeFilterConstants::FILTER_SKIP); } - // "3. If filter is null, return FILTER_ACCEPT." - // "4. Let result be the return value of invoking filter." - // "5. If an exception was thrown, re-throw the exception." - // "6. Return result." match self.filter { + // Step 4. Filter::None => Ok(NodeFilterConstants::FILTER_ACCEPT), - Filter::Native(f) => Ok((f)(node)), - Filter::JS(ref callback) => callback.AcceptNode_(self, node, Rethrow) + Filter::Dom(ref callback) => { + // Step 5. + self.active.set(true); + // Step 6. + let result = callback.AcceptNode_(self, node, Rethrow); + // Step 7. + self.active.set(false); + // Step 8. + result + }, } } fn is_root_node(&self, node: &Node) -> bool { - JS::from_ref(node) == self.root_node + Dom::from_ref(node) == self.root_node } fn is_current_node(&self, node: &Node) -> bool { @@ -445,18 +454,20 @@ impl TreeWalker { } impl<'a> Iterator for &'a TreeWalker { - type Item = Root<Node>; + type Item = DomRoot<Node>; - fn next(&mut self) -> Option<Root<Node>> { + fn next(&mut self) -> Option<DomRoot<Node>> { match self.NextNode() { Ok(node) => node, Err(_) => - // The Err path happens only when a JavaScript - // NodeFilter throws an exception. This iterator - // is meant for internal use from Rust code, which - // will probably be using a native Rust filter, - // which cannot produce an Err result. + // The Err path happens only when a JavaScript + // NodeFilter throws an exception. This iterator + // is meant for internal use from Rust code, which + // will probably be using a native Rust filter, + // which cannot produce an Err result. + { unreachable!() + } } } } @@ -464,6 +475,5 @@ impl<'a> Iterator for &'a TreeWalker { #[derive(JSTraceable)] pub enum Filter { None, - Native(fn (node: &Node) -> u16), - JS(Rc<NodeFilter>) + Dom(Rc<NodeFilter>), } diff --git a/components/script/dom/uievent.rs b/components/script/dom/uievent.rs index f3f77953c29..c54265f4fb5 100644 --- a/components/script/dom/uievent.rs +++ b/components/script/dom/uievent.rs @@ -1,17 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::UIEventBinding; -use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root, RootedReference}; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::UIEventBinding; +use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; use std::cell::Cell; @@ -21,8 +21,8 @@ use std::default::Default; #[dom_struct] pub struct UIEvent { event: Event, - view: MutNullableJS<Window>, - detail: Cell<i32> + view: MutNullableDom<Window>, + detail: Cell<i32>, } impl UIEvent { @@ -34,39 +34,52 @@ impl UIEvent { } } - pub fn new_uninitialized(window: &Window) -> Root<UIEvent> { - reflect_dom_object(box UIEvent::new_inherited(), - window, - UIEventBinding::Wrap) + pub fn new_uninitialized(window: &Window) -> DomRoot<UIEvent> { + reflect_dom_object(Box::new(UIEvent::new_inherited()), window) } - pub fn new(window: &Window, - type_: DOMString, - can_bubble: EventBubbles, - cancelable: EventCancelable, - view: Option<&Window>, - detail: i32) -> Root<UIEvent> { + pub fn new( + window: &Window, + type_: DOMString, + can_bubble: EventBubbles, + cancelable: EventCancelable, + view: Option<&Window>, + detail: i32, + ) -> DomRoot<UIEvent> { let ev = UIEvent::new_uninitialized(window); - ev.InitUIEvent(type_, bool::from(can_bubble), bool::from(cancelable), view, detail); + ev.InitUIEvent( + type_, + bool::from(can_bubble), + bool::from(cancelable), + view, + detail, + ); ev } - pub fn Constructor(window: &Window, - type_: DOMString, - init: &UIEventBinding::UIEventInit) -> Fallible<Root<UIEvent>> { + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &UIEventBinding::UIEventInit, + ) -> Fallible<DomRoot<UIEvent>> { let bubbles = EventBubbles::from(init.parent.bubbles); let cancelable = EventCancelable::from(init.parent.cancelable); - let event = UIEvent::new(window, - type_, - bubbles, cancelable, - init.view.r(), init.detail); + let event = UIEvent::new( + window, + type_, + bubbles, + cancelable, + init.view.as_deref(), + init.detail, + ); Ok(event) } } impl UIEventMethods for UIEvent { // https://w3c.github.io/uievents/#widl-UIEvent-view - fn GetView(&self) -> Option<Root<Window>> { + fn GetView(&self) -> Option<DomRoot<Window>> { self.view.get() } @@ -76,12 +89,14 @@ impl UIEventMethods for UIEvent { } // https://w3c.github.io/uievents/#widl-UIEvent-initUIEvent - fn InitUIEvent(&self, - type_: DOMString, - can_bubble: bool, - cancelable: bool, - view: Option<&Window>, - detail: i32) { + fn InitUIEvent( + &self, + type_: DOMString, + can_bubble: bool, + cancelable: bool, + view: Option<&Window>, + detail: i32, + ) { let event = self.upcast::<Event>(); if event.dispatching() { return; diff --git a/components/script/dom/url.rs b/components/script/dom/url.rs index a02a735638f..056f6aa9643 100644 --- a/components/script/dom/url.rs +++ b/components/script/dom/url.rs @@ -1,22 +1,22 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::URLBinding::{self, URLMethods}; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::blob::Blob; -use dom::globalscope::GlobalScope; -use dom::urlhelper::UrlHelper; -use dom::urlsearchparams::URLSearchParams; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::URLBinding::URLMethods; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::blob::Blob; +use crate::dom::globalscope::GlobalScope; +use crate::dom::urlhelper::UrlHelper; +use crate::dom::urlsearchparams::URLSearchParams; use dom_struct::dom_struct; -use ipc_channel::ipc; -use net_traits::{CoreResourceMsg, IpcSend}; use net_traits::blob_url_store::{get_blob_origin, parse_blob_url}; use net_traits::filemanager_thread::FileManagerThreadMsg; +use net_traits::{CoreResourceMsg, IpcSend}; +use profile_traits::ipc; use servo_url::ServoUrl; use std::default::Default; use uuid::Uuid; @@ -27,55 +27,72 @@ pub struct URL { reflector_: Reflector, // https://url.spec.whatwg.org/#concept-url-url - url: DOMRefCell<ServoUrl>, + url: DomRefCell<ServoUrl>, // https://url.spec.whatwg.org/#dom-url-searchparams - search_params: MutNullableJS<URLSearchParams>, + search_params: MutNullableDom<URLSearchParams>, } impl URL { fn new_inherited(url: ServoUrl) -> URL { URL { reflector_: Reflector::new(), - url: DOMRefCell::new(url), + url: DomRefCell::new(url), search_params: Default::default(), } } - pub fn new(global: &GlobalScope, url: ServoUrl) -> Root<URL> { - reflect_dom_object(box URL::new_inherited(url), - global, URLBinding::Wrap) + pub fn new(global: &GlobalScope, url: ServoUrl) -> DomRoot<URL> { + reflect_dom_object(Box::new(URL::new_inherited(url)), global) } pub fn query_pairs(&self) -> Vec<(String, String)> { - self.url.borrow().as_url().query_pairs().into_owned().collect() + self.url + .borrow() + .as_url() + .query_pairs() + .into_owned() + .collect() } pub fn set_query_pairs(&self, pairs: &[(String, String)]) { let mut url = self.url.borrow_mut(); - url.as_mut_url().query_pairs_mut().clear().extend_pairs(pairs); + + if pairs.is_empty() { + url.as_mut_url().set_query(None); + } else { + url.as_mut_url() + .query_pairs_mut() + .clear() + .extend_pairs(pairs); + } } } +#[allow(non_snake_case)] impl URL { // https://url.spec.whatwg.org/#constructors - pub fn Constructor(global: &GlobalScope, url: USVString, - base: Option<USVString>) - -> Fallible<Root<URL>> { + pub fn Constructor( + global: &GlobalScope, + url: USVString, + base: Option<USVString>, + ) -> Fallible<DomRoot<URL>> { let parsed_base = match base { None => { // Step 1. None }, Some(base) => - // Step 2.1. + // Step 2.1. + { match ServoUrl::parse(&base.0) { Ok(base) => Some(base), Err(error) => { // Step 2.2. return Err(Error::Type(format!("could not parse base: {}", error))); - } + }, } + } }; // Step 3. let parsed_url = match ServoUrl::parse_with_base(parsed_base.as_ref(), &url.0) { @@ -83,7 +100,7 @@ impl URL { Err(error) => { // Step 4. return Err(Error::Type(format!("could not parse URL: {}", error))); - } + }, }; // Step 5: Skip (see step 8 below). // Steps 6-7. @@ -96,8 +113,8 @@ impl URL { // https://w3c.github.io/FileAPI/#dfn-createObjectURL pub fn CreateObjectURL(global: &GlobalScope, blob: &Blob) -> DOMString { - /// XXX: Second field is an unicode-serialized Origin, it is a temporary workaround - /// and should not be trusted. See issue https://github.com/servo/servo/issues/11722 + // XXX: Second field is an unicode-serialized Origin, it is a temporary workaround + // and should not be trusted. See issue https://github.com/servo/servo/issues/11722 let origin = get_blob_origin(&global.get_url()); let id = blob.get_blob_url_id(); @@ -107,22 +124,21 @@ impl URL { // https://w3c.github.io/FileAPI/#dfn-revokeObjectURL pub fn RevokeObjectURL(global: &GlobalScope, url: DOMString) { - /* - If the value provided for the url argument is not a Blob URL OR - if the value provided for the url argument does not have an entry in the Blob URL Store, - - this method call does nothing. User agents may display a message on the error console. - */ + // If the value provided for the url argument is not a Blob URL OR + // if the value provided for the url argument does not have an entry in the Blob URL Store, + // this method call does nothing. User agents may display a message on the error console. let origin = get_blob_origin(&global.get_url()); if let Ok(url) = ServoUrl::parse(&url) { - if let Ok((id, _)) = parse_blob_url(&url) { - let resource_threads = global.resource_threads(); - let (tx, rx) = ipc::channel().unwrap(); - let msg = FileManagerThreadMsg::RevokeBlobURL(id, origin, tx); - let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg)); - - let _ = rx.recv().unwrap(); + if url.fragment().is_none() && origin == get_blob_origin(&url) { + if let Ok((id, _)) = parse_blob_url(&url) { + let resource_threads = global.resource_threads(); + let (tx, rx) = ipc::channel(global.time_profiler_chan().clone()).unwrap(); + let msg = FileManagerThreadMsg::RevokeBlobURL(id, origin, tx); + let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg)); + + let _ = rx.recv().unwrap(); + } } } } @@ -139,7 +155,7 @@ impl URL { result.push('/'); // Step 5 - result.push_str(&id.simple().to_string()); + result.push_str(&id.to_string()); result } @@ -186,12 +202,10 @@ impl URLMethods for URL { match ServoUrl::parse(&value.0) { Ok(url) => { *self.url.borrow_mut() = url; - self.search_params.set(None); // To be re-initialized in the SearchParams getter. + self.search_params.set(None); // To be re-initialized in the SearchParams getter. Ok(()) }, - Err(error) => { - Err(Error::Type(format!("could not parse URL: {}", error))) - }, + Err(error) => Err(Error::Type(format!("could not parse URL: {}", error))), } } @@ -254,15 +268,9 @@ impl URLMethods for URL { } // https://url.spec.whatwg.org/#dom-url-searchparams - fn SearchParams(&self) -> Root<URLSearchParams> { - self.search_params.or_init(|| { - URLSearchParams::new(&self.global(), Some(self)) - }) - } - - // https://url.spec.whatwg.org/#dom-url-href - fn Stringifier(&self) -> DOMString { - DOMString::from(self.Href().0) + fn SearchParams(&self) -> DomRoot<URLSearchParams> { + self.search_params + .or_init(|| URLSearchParams::new(&self.global(), Some(self))) } // https://url.spec.whatwg.org/#dom-url-username @@ -274,4 +282,9 @@ impl URLMethods for URL { fn SetUsername(&self, value: USVString) { UrlHelper::SetUsername(&mut self.url.borrow_mut(), value); } + + // https://url.spec.whatwg.org/#dom-url-tojson + fn ToJSON(&self) -> USVString { + self.Href() + } } diff --git a/components/script/dom/urlhelper.rs b/components/script/dom/urlhelper.rs index d00189156f0..834d7e20d18 100644 --- a/components/script/dom/urlhelper.rs +++ b/components/script/dom/urlhelper.rs @@ -1,15 +1,16 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::str::USVString; +use crate::dom::bindings::str::USVString; use servo_url::ServoUrl; use std::borrow::ToOwned; use url::quirks; -#[derive(HeapSizeOf)] +#[derive(MallocSizeOf)] pub struct UrlHelper; +#[allow(non_snake_case)] impl UrlHelper { pub fn Origin(url: &ServoUrl) -> USVString { USVString(quirks::origin(url.as_url()).to_owned()) @@ -71,18 +72,4 @@ impl UrlHelper { pub fn SetUsername(url: &mut ServoUrl, value: USVString) { let _ = quirks::set_username(url.as_mut_url(), &value.0); } - // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy - pub fn is_origin_trustworthy(url: &ServoUrl) -> bool { - // Step 3 - if url.scheme() == "http" || url.scheme() == "wss" { - true - // Step 4 - } else if url.host().is_some() { - let host = url.host_str().unwrap(); - host == "127.0.0.0/8" || host == "::1/128" - // Step 5 - } else { - url.scheme() == "file" - } - } } diff --git a/components/script/dom/urlsearchparams.rs b/components/script/dom/urlsearchparams.rs index 58391d65151..ef27b969990 100644 --- a/components/script/dom/urlsearchparams.rs +++ b/components/script/dom/urlsearchparams.rs @@ -1,21 +1,19 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::URLSearchParamsBinding::URLSearchParamsMethods; -use dom::bindings::codegen::Bindings::URLSearchParamsBinding::URLSearchParamsWrap; -use dom::bindings::codegen::UnionTypes::USVStringOrURLSearchParams; -use dom::bindings::error::Fallible; -use dom::bindings::iterable::Iterable; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::bindings::weakref::MutableWeakRef; -use dom::globalscope::GlobalScope; -use dom::url::URL; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::URLSearchParamsBinding::URLSearchParamsMethods; +use crate::dom::bindings::codegen::UnionTypes::USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::iterable::Iterable; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::weakref::MutableWeakRef; +use crate::dom::globalscope::GlobalScope; +use crate::dom::url::URL; use dom_struct::dom_struct; -use encoding::types::EncodingRef; use url::form_urlencoded; // https://url.spec.whatwg.org/#interface-urlsearchparams @@ -23,7 +21,7 @@ use url::form_urlencoded; pub struct URLSearchParams { reflector_: Reflector, // https://url.spec.whatwg.org/#concept-urlsearchparams-list - list: DOMRefCell<Vec<(String, String)>>, + list: DomRefCell<Vec<(String, String)>>, // https://url.spec.whatwg.org/#concept-urlsearchparams-url-object url: MutableWeakRef<URL>, } @@ -32,34 +30,58 @@ impl URLSearchParams { fn new_inherited(url: Option<&URL>) -> URLSearchParams { URLSearchParams { reflector_: Reflector::new(), - list: DOMRefCell::new(url.map_or(Vec::new(), |url| url.query_pairs())), + list: DomRefCell::new(url.map_or(Vec::new(), |url| url.query_pairs())), url: MutableWeakRef::new(url), } } - pub fn new(global: &GlobalScope, url: Option<&URL>) -> Root<URLSearchParams> { - reflect_dom_object(box URLSearchParams::new_inherited(url), global, - URLSearchParamsWrap) + pub fn new(global: &GlobalScope, url: Option<&URL>) -> DomRoot<URLSearchParams> { + reflect_dom_object(Box::new(URLSearchParams::new_inherited(url)), global) } // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams - pub fn Constructor(global: &GlobalScope, init: Option<USVStringOrURLSearchParams>) -> - Fallible<Root<URLSearchParams>> { + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + init: USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString, + ) -> Fallible<DomRoot<URLSearchParams>> { // Step 1. let query = URLSearchParams::new(global, None); match init { - Some(USVStringOrURLSearchParams::USVString(init)) => { + USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString::USVStringSequenceSequence(init) => { // Step 2. - *query.list.borrow_mut() = form_urlencoded::parse(init.0.as_bytes()) - .into_owned().collect(); + + // Step 2-1. + if init.iter().any(|pair| pair.len() != 2) { + return Err(Error::Type("Sequence initializer must only contain pair elements.".to_string())); + } + + // Step 2-2. + *query.list.borrow_mut() = + init.iter().map(|pair| (pair[0].to_string(), pair[1].to_string())).collect::<Vec<_>>(); }, - Some(USVStringOrURLSearchParams::URLSearchParams(init)) => { + USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString::USVStringUSVStringRecord(init) => { // Step 3. - *query.list.borrow_mut() = init.list.borrow().clone(); + *query.list.borrow_mut() = + (*init).iter().map(|(name, value)| (name.to_string(), value.to_string())).collect::<Vec<_>>(); }, - None => {} + USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString::USVString(init) => { + // Step 4. + let init_bytes = match init.0.chars().next() { + Some(first_char) if first_char == '?' => { + let (_, other_bytes) = init.0.as_bytes().split_at(1); + + other_bytes + }, + _ => init.0.as_bytes(), + }; + + *query.list.borrow_mut() = + form_urlencoded::parse(init_bytes).into_owned().collect(); + } } - // Step 4. + + // Step 5. Ok(query) } @@ -88,20 +110,23 @@ impl URLSearchParamsMethods for URLSearchParams { // https://url.spec.whatwg.org/#dom-urlsearchparams-get fn Get(&self, name: USVString) -> Option<USVString> { let list = self.list.borrow(); - list.iter().find(|&kv| kv.0 == name.0) + list.iter() + .find(|&kv| kv.0 == name.0) .map(|ref kv| USVString(kv.1.clone())) } // https://url.spec.whatwg.org/#dom-urlsearchparams-getall fn GetAll(&self, name: USVString) -> Vec<USVString> { let list = self.list.borrow(); - list.iter().filter_map(|&(ref k, ref v)| { - if k == &name.0 { - Some(USVString(v.clone())) - } else { - None - } - }).collect() + list.iter() + .filter_map(|&(ref k, ref v)| { + if k == &name.0 { + Some(USVString(v.clone())) + } else { + None + } + }) + .collect() } // https://url.spec.whatwg.org/#dom-urlsearchparams-has @@ -133,31 +158,37 @@ impl URLSearchParamsMethods for URLSearchParams { Some(index) => list[index].1 = value.0, None => list.push((name.0, value.0)), // Step 2. }; - } // Un-borrow self.list - // Step 3. + } // Un-borrow self.list + // Step 3. + self.update_steps(); + } + + // https://url.spec.whatwg.org/#dom-urlsearchparams-sort + fn Sort(&self) { + // Step 1. + self.list + .borrow_mut() + .sort_by(|(a, _), (b, _)| a.encode_utf16().cmp(b.encode_utf16())); + + // Step 2. self.update_steps(); } // https://url.spec.whatwg.org/#stringification-behavior fn Stringifier(&self) -> DOMString { - DOMString::from(self.serialize(None)) + DOMString::from(self.serialize_utf8()) } } - impl URLSearchParams { // https://url.spec.whatwg.org/#concept-urlencoded-serializer - pub fn serialize(&self, encoding: Option<EncodingRef>) -> String { + pub fn serialize_utf8(&self) -> String { let list = self.list.borrow(); form_urlencoded::Serializer::new(String::new()) - .encoding_override(encoding) .extend_pairs(&*list) .finish() } -} - -impl URLSearchParams { // https://url.spec.whatwg.org/#concept-urlsearchparams-update fn update_steps(&self) { if let Some(url) = self.url.root() { @@ -166,7 +197,6 @@ impl URLSearchParams { } } - impl Iterable for URLSearchParams { type Key = USVString; type Value = USVString; diff --git a/components/script/dom/userscripts.rs b/components/script/dom/userscripts.rs index fd5ae5f023e..047b3345b0c 100644 --- a/components/script/dom/userscripts.rs +++ b/components/script/dom/userscripts.rs @@ -1,41 +1,39 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::inheritance::Castable; -use dom::globalscope::GlobalScope; -use dom::htmlheadelement::HTMLHeadElement; -use dom::node::Node; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlheadelement::HTMLHeadElement; +use crate::dom::htmlscriptelement::SourceCode; +use crate::dom::node::document_from_node; +use crate::script_module::ScriptFetchOptions; use js::jsval::UndefinedValue; -use servo_config::opts; -use servo_config::resource_files::resources_dir_path; -use std::fs::{File, read_dir}; +use std::fs::{read_dir, File}; use std::io::Read; use std::path::PathBuf; - +use std::rc::Rc; pub fn load_script(head: &HTMLHeadElement) { - if let Some(ref path_str) = opts::get().userscripts { - let node = head.upcast::<Node>(); - let doc = node.owner_doc(); - let win = doc.window(); + let doc = document_from_node(head); + let path_str = match doc.window().get_userscripts_path() { + Some(p) => p, + None => return, + }; + let win = Trusted::new(doc.window()); + doc.add_delayed_task(task!(UserScriptExecute: move || { + let win = win.root(); let cx = win.get_cx(); - rooted!(in(cx) let mut rval = UndefinedValue()); - - let path = if &**path_str == "" { - if let Ok(mut p) = resources_dir_path() { - p.push("user-agent-js"); - p - } else { - return - } - } else { - PathBuf::from(path_str) - }; + rooted!(in(*cx) let mut rval = UndefinedValue()); - let mut files = read_dir(&path).expect("Bad path passed to --userscripts") - .filter_map(|e| e.ok()) - .map(|e| e.path()).collect::<Vec<_>>(); + let path = PathBuf::from(&path_str); + let mut files = read_dir(&path) + .expect("Bad path passed to --userscripts") + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .collect::<Vec<_>>(); files.sort(); @@ -43,8 +41,18 @@ pub fn load_script(head: &HTMLHeadElement) { let mut f = File::open(&file).unwrap(); let mut contents = vec![]; f.read_to_end(&mut contents).unwrap(); - let script_text = String::from_utf8_lossy(&contents); - win.upcast::<GlobalScope>().evaluate_js_on_global_with_result(&script_text, rval.handle_mut()); + let script_text = SourceCode::Text( + Rc::new(DOMString::from_string(String::from_utf8_lossy(&contents).to_string())) + ); + let global = win.upcast::<GlobalScope>(); + global.evaluate_script_on_global_with_result( + &script_text, + &file.to_string_lossy(), + rval.handle_mut(), + 1, + ScriptFetchOptions::default_classic_script(&global), + global.api_base_url(), + ); } - } + })); } diff --git a/components/script/dom/validation.rs b/components/script/dom/validation.rs index 7ba915092a2..e6b36ec809d 100755 --- a/components/script/dom/validation.rs +++ b/components/script/dom/validation.rs @@ -1,9 +1,115 @@ /* 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 http://mozilla.org/MPL/2.0/. */ -use dom::validitystate::ValidationFlags; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::element::Element; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmldatalistelement::HTMLDataListElement; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::Node; +use crate::dom::validitystate::{ValidationFlags, ValidityState}; +/// Trait for elements with constraint validation support pub trait Validatable { - fn is_instance_validatable(&self) -> bool { true } - fn validate(&self, _validate_flags: ValidationFlags) -> bool { true } + fn as_element(&self) -> ∈ + + // https://html.spec.whatwg.org/multipage/#dom-cva-validity + fn validity_state(&self) -> DomRoot<ValidityState>; + + // https://html.spec.whatwg.org/multipage/#candidate-for-constraint-validation + fn is_instance_validatable(&self) -> bool; + + // Check if element satisfies its constraints, excluding custom errors + fn perform_validation(&self, _validate_flags: ValidationFlags) -> ValidationFlags { + ValidationFlags::empty() + } + + // https://html.spec.whatwg.org/multipage/#concept-fv-valid + fn validate(&self, validate_flags: ValidationFlags) -> ValidationFlags { + let mut failed_flags = self.perform_validation(validate_flags); + + // https://html.spec.whatwg.org/multipage/#suffering-from-a-custom-error + if validate_flags.contains(ValidationFlags::CUSTOM_ERROR) { + if !self.validity_state().custom_error_message().is_empty() { + failed_flags.insert(ValidationFlags::CUSTOM_ERROR); + } + } + + failed_flags + } + + // https://html.spec.whatwg.org/multipage/#check-validity-steps + fn check_validity(&self) -> bool { + if self.is_instance_validatable() && !self.validate(ValidationFlags::all()).is_empty() { + self.as_element() + .upcast::<EventTarget>() + .fire_cancelable_event(atom!("invalid")); + false + } else { + true + } + } + + // https://html.spec.whatwg.org/multipage/#report-validity-steps + fn report_validity(&self) -> bool { + // Step 1. + if !self.is_instance_validatable() { + return true; + } + + let flags = self.validate(ValidationFlags::all()); + if flags.is_empty() { + return true; + } + + // Step 1.1. + let event = self + .as_element() + .upcast::<EventTarget>() + .fire_cancelable_event(atom!("invalid")); + + // Step 1.2. + if !event.DefaultPrevented() { + println!( + "Validation error: {}", + validation_message_for_flags(&self.validity_state(), flags) + ); + if let Some(html_elem) = self.as_element().downcast::<HTMLElement>() { + html_elem.Focus(); + } + } + + // Step 1.3. + false + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage + fn validation_message(&self) -> DOMString { + if self.is_instance_validatable() { + let flags = self.validate(ValidationFlags::all()); + validation_message_for_flags(&self.validity_state(), flags) + } else { + DOMString::new() + } + } +} + +// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation +pub fn is_barred_by_datalist_ancestor(elem: &Node) -> bool { + elem.upcast::<Node>() + .ancestors() + .any(|node| node.is::<HTMLDataListElement>()) +} + +// Get message for given validation flags or custom error message +fn validation_message_for_flags(state: &ValidityState, failed_flags: ValidationFlags) -> DOMString { + if failed_flags.contains(ValidationFlags::CUSTOM_ERROR) { + state.custom_error_message().clone() + } else { + DOMString::from(failed_flags.to_string()) + } } diff --git a/components/script/dom/validitystate.rs b/components/script/dom/validitystate.rs index 879438aa389..b7f4da79530 100755 --- a/components/script/dom/validitystate.rs +++ b/components/script/dom/validitystate.rs @@ -1,44 +1,60 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::ValidityStateBinding; -use dom::bindings::codegen::Bindings::ValidityStateBinding::ValidityStateMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::element::Element; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::ValidityStateBinding::ValidityStateMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::element::Element; +use crate::dom::window::Window; use dom_struct::dom_struct; +use itertools::Itertools; +use std::fmt; // https://html.spec.whatwg.org/multipage/#validity-states -#[derive(JSTraceable, HeapSizeOf)] -#[allow(dead_code)] -pub enum ValidityStatus { - ValueMissing, - TypeMismatch, - PatternMismatch, - TooLong, - TooShort, - RangeUnderflow, - RangeOverflow, - StepMismatch, - BadInput, - CustomError, - Valid +bitflags! { + pub struct ValidationFlags: u32 { + const VALUE_MISSING = 0b0000000001; + const TYPE_MISMATCH = 0b0000000010; + const PATTERN_MISMATCH = 0b0000000100; + const TOO_LONG = 0b0000001000; + const TOO_SHORT = 0b0000010000; + const RANGE_UNDERFLOW = 0b0000100000; + const RANGE_OVERFLOW = 0b0001000000; + const STEP_MISMATCH = 0b0010000000; + const BAD_INPUT = 0b0100000000; + const CUSTOM_ERROR = 0b1000000000; + } } -bitflags!{ - pub flags ValidationFlags: u32 { - const VALUE_MISSING = 0b0000000001, - const TYPE_MISMATCH = 0b0000000010, - const PATTERN_MISMATCH = 0b0000000100, - const TOO_LONG = 0b0000001000, - const TOO_SHORT = 0b0000010000, - const RANGE_UNDERFLOW = 0b0000100000, - const RANGE_OVERFLOW = 0b0001000000, - const STEP_MISMATCH = 0b0010000000, - const BAD_INPUT = 0b0100000000, - const CUSTOM_ERROR = 0b1000000000, +impl fmt::Display for ValidationFlags { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let flag_to_message = [ + (ValidationFlags::VALUE_MISSING, "Value missing"), + (ValidationFlags::TYPE_MISMATCH, "Type mismatch"), + (ValidationFlags::PATTERN_MISMATCH, "Pattern mismatch"), + (ValidationFlags::TOO_LONG, "Too long"), + (ValidationFlags::TOO_SHORT, "Too short"), + (ValidationFlags::RANGE_UNDERFLOW, "Range underflow"), + (ValidationFlags::RANGE_OVERFLOW, "Range overflow"), + (ValidationFlags::STEP_MISMATCH, "Step mismatch"), + (ValidationFlags::BAD_INPUT, "Bad input"), + (ValidationFlags::CUSTOM_ERROR, "Custom error"), + ]; + + flag_to_message + .iter() + .filter_map(|&(flag, flag_str)| { + if self.contains(flag) { + Some(flag_str) + } else { + None + } + }) + .format(", ") + .fmt(formatter) } } @@ -46,80 +62,109 @@ bitflags!{ #[dom_struct] pub struct ValidityState { reflector_: Reflector, - element: JS<Element>, - state: ValidityStatus + element: Dom<Element>, + custom_error_message: DomRefCell<DOMString>, } - impl ValidityState { fn new_inherited(element: &Element) -> ValidityState { ValidityState { reflector_: Reflector::new(), - element: JS::from_ref(element), - state: ValidityStatus::Valid + element: Dom::from_ref(element), + custom_error_message: DomRefCell::new(DOMString::new()), } } - pub fn new(window: &Window, element: &Element) -> Root<ValidityState> { - reflect_dom_object(box ValidityState::new_inherited(element), - window, - ValidityStateBinding::Wrap) + pub fn new(window: &Window, element: &Element) -> DomRoot<ValidityState> { + reflect_dom_object(Box::new(ValidityState::new_inherited(element)), window) + } + + // https://html.spec.whatwg.org/multipage/#custom-validity-error-message + pub fn custom_error_message(&self) -> Ref<DOMString> { + self.custom_error_message.borrow() + } + + // https://html.spec.whatwg.org/multipage/#custom-validity-error-message + pub fn set_custom_error_message(&self, error: DOMString) { + *self.custom_error_message.borrow_mut() = error; } } impl ValidityStateMethods for ValidityState { // https://html.spec.whatwg.org/multipage/#dom-validitystate-valuemissing fn ValueMissing(&self) -> bool { - false + self.element.as_maybe_validatable().map_or(false, |e| { + !e.validate(ValidationFlags::VALUE_MISSING).is_empty() + }) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-typemismatch fn TypeMismatch(&self) -> bool { - false + self.element.as_maybe_validatable().map_or(false, |e| { + !e.validate(ValidationFlags::TYPE_MISMATCH).is_empty() + }) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-patternmismatch fn PatternMismatch(&self) -> bool { - false + self.element.as_maybe_validatable().map_or(false, |e| { + !e.validate(ValidationFlags::PATTERN_MISMATCH).is_empty() + }) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-toolong fn TooLong(&self) -> bool { - false + self.element + .as_maybe_validatable() + .map_or(false, |e| !e.validate(ValidationFlags::TOO_LONG).is_empty()) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-tooshort fn TooShort(&self) -> bool { - false + self.element.as_maybe_validatable().map_or(false, |e| { + !e.validate(ValidationFlags::TOO_SHORT).is_empty() + }) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeunderflow fn RangeUnderflow(&self) -> bool { - false + self.element.as_maybe_validatable().map_or(false, |e| { + !e.validate(ValidationFlags::RANGE_UNDERFLOW).is_empty() + }) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-rangeoverflow fn RangeOverflow(&self) -> bool { - false + self.element.as_maybe_validatable().map_or(false, |e| { + !e.validate(ValidationFlags::RANGE_OVERFLOW).is_empty() + }) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-stepmismatch fn StepMismatch(&self) -> bool { - false + self.element.as_maybe_validatable().map_or(false, |e| { + !e.validate(ValidationFlags::STEP_MISMATCH).is_empty() + }) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-badinput fn BadInput(&self) -> bool { - false + self.element.as_maybe_validatable().map_or(false, |e| { + !e.validate(ValidationFlags::BAD_INPUT).is_empty() + }) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-customerror fn CustomError(&self) -> bool { - false + self.element.as_maybe_validatable().map_or(false, |e| { + !e.validate(ValidationFlags::CUSTOM_ERROR).is_empty() + }) } // https://html.spec.whatwg.org/multipage/#dom-validitystate-valid fn Valid(&self) -> bool { - false + self.element + .as_maybe_validatable() + .map_or(true, |e| e.validate(ValidationFlags::all()).is_empty()) } } diff --git a/components/script/dom/values.rs b/components/script/dom/values.rs index 95bea039a97..4b87c1b1c8c 100644 --- a/components/script/dom/values.rs +++ b/components/script/dom/values.rs @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage#reflecting-content-attributes-in-idl-attributes:idl-unsigned-long // https://html.spec.whatwg.org/multipage#limited-to-only-non-negative-numbers-greater-than-zero diff --git a/components/script/dom/vertexarrayobject.rs b/components/script/dom/vertexarrayobject.rs new file mode 100644 index 00000000000..fcf6461e2a8 --- /dev/null +++ b/components/script/dom/vertexarrayobject.rs @@ -0,0 +1,315 @@ +/* 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 crate::dom::bindings::cell::{ref_filter_map, 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}; +use crate::dom::webglbuffer::WebGLBuffer; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use canvas_traits::webgl::{ + ActiveAttribInfo, WebGLCommand, WebGLError, WebGLResult, WebGLVersion, WebGLVertexArrayId, +}; +use std::cell::Cell; + +#[derive(JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct VertexArrayObject { + context: Dom<WebGLRenderingContext>, + id: Option<WebGLVertexArrayId>, + ever_bound: Cell<bool>, + is_deleted: Cell<bool>, + vertex_attribs: DomRefCell<Box<[VertexAttribData]>>, + element_array_buffer: MutNullableDom<WebGLBuffer>, +} + +impl VertexArrayObject { + pub fn new(context: &WebGLRenderingContext, id: Option<WebGLVertexArrayId>) -> Self { + let max_vertex_attribs = context.limits().max_vertex_attribs as usize; + Self { + context: Dom::from_ref(context), + id, + ever_bound: Default::default(), + is_deleted: Default::default(), + vertex_attribs: DomRefCell::new(vec![Default::default(); max_vertex_attribs].into()), + element_array_buffer: Default::default(), + } + } + + pub fn id(&self) -> Option<WebGLVertexArrayId> { + self.id + } + + pub fn is_deleted(&self) -> bool { + self.is_deleted.get() + } + + pub fn delete(&self, operation_fallibility: Operation) { + assert!(self.id.is_some()); + if self.is_deleted.get() { + return; + } + self.is_deleted.set(true); + let cmd = WebGLCommand::DeleteVertexArray(self.id.unwrap()); + match operation_fallibility { + Operation::Fallible => self.context.send_command_ignored(cmd), + Operation::Infallible => self.context.send_command(cmd), + } + + for attrib_data in &**self.vertex_attribs.borrow() { + if let Some(buffer) = attrib_data.buffer() { + buffer.decrement_attached_counter(operation_fallibility); + } + } + if let Some(buffer) = self.element_array_buffer.get() { + buffer.decrement_attached_counter(operation_fallibility); + } + } + + pub fn ever_bound(&self) -> bool { + return self.ever_bound.get(); + } + + pub fn set_ever_bound(&self) { + self.ever_bound.set(true); + } + + pub fn element_array_buffer(&self) -> &MutNullableDom<WebGLBuffer> { + &self.element_array_buffer + } + + pub fn get_vertex_attrib(&self, index: u32) -> Option<Ref<VertexAttribData>> { + ref_filter_map(self.vertex_attribs.borrow(), |attribs| { + attribs.get(index as usize) + }) + } + + pub fn set_vertex_attrib_type(&self, index: u32, type_: u32) { + self.vertex_attribs.borrow_mut()[index as usize].type_ = type_; + } + + pub fn vertex_attrib_pointer( + &self, + index: u32, + size: i32, + type_: u32, + normalized: bool, + stride: i32, + offset: i64, + ) -> WebGLResult<()> { + let mut attribs = self.vertex_attribs.borrow_mut(); + let data = attribs + .get_mut(index as usize) + .ok_or(WebGLError::InvalidValue)?; + + if size < 1 || size > 4 { + return Err(WebGLError::InvalidValue); + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#BUFFER_OFFSET_AND_STRIDE + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#VERTEX_STRIDE + if stride < 0 || stride > 255 || offset < 0 { + return Err(WebGLError::InvalidValue); + } + + let is_webgl2 = match self.context.webgl_version() { + WebGLVersion::WebGL2 => true, + _ => false, + }; + + let bytes_per_component: i32 = match type_ { + constants::BYTE | constants::UNSIGNED_BYTE => 1, + constants::SHORT | constants::UNSIGNED_SHORT => 2, + constants::FLOAT => 4, + constants::INT | constants::UNSIGNED_INT if is_webgl2 => 4, + constants2::HALF_FLOAT if is_webgl2 => 2, + sparkle::gl::FIXED if is_webgl2 => 4, + constants2::INT_2_10_10_10_REV | constants2::UNSIGNED_INT_2_10_10_10_REV + if is_webgl2 && size == 4 => + { + 4 + }, + _ => return Err(WebGLError::InvalidEnum), + }; + + if offset % bytes_per_component as i64 > 0 || stride % bytes_per_component > 0 { + return Err(WebGLError::InvalidOperation); + } + + let buffer = self.context.array_buffer(); + match buffer { + Some(ref buffer) => buffer.increment_attached_counter(), + None if offset != 0 => { + // https://github.com/KhronosGroup/WebGL/pull/2228 + return Err(WebGLError::InvalidOperation); + }, + _ => {}, + } + self.context.send_command(WebGLCommand::VertexAttribPointer( + index, + size, + type_, + normalized, + stride, + offset as u32, + )); + if let Some(old) = data.buffer() { + old.decrement_attached_counter(Operation::Infallible); + } + + *data = VertexAttribData { + enabled_as_array: data.enabled_as_array, + size: size as u8, + type_, + bytes_per_vertex: size as u8 * bytes_per_component as u8, + normalized, + stride: stride as u8, + offset: offset as u32, + buffer: buffer.map(|b| Dom::from_ref(&*b)), + divisor: data.divisor, + }; + + Ok(()) + } + + pub fn vertex_attrib_divisor(&self, index: u32, value: u32) { + self.vertex_attribs.borrow_mut()[index as usize].divisor = value; + } + + pub fn enabled_vertex_attrib_array(&self, index: u32, value: bool) { + self.vertex_attribs.borrow_mut()[index as usize].enabled_as_array = value; + } + + pub fn unbind_buffer(&self, buffer: &WebGLBuffer) { + for attrib in &mut **self.vertex_attribs.borrow_mut() { + if let Some(b) = attrib.buffer() { + if b.id() != buffer.id() { + continue; + } + b.decrement_attached_counter(Operation::Infallible); + } + attrib.buffer = None; + } + if self + .element_array_buffer + .get() + .map_or(false, |b| buffer == &*b) + { + buffer.decrement_attached_counter(Operation::Infallible); + self.element_array_buffer.set(None); + } + } + + pub fn validate_for_draw( + &self, + required_len: u32, + instance_count: u32, + active_attribs: &[ActiveAttribInfo], + ) -> WebGLResult<()> { + // TODO(nox): Cache limits per VAO. + let attribs = self.vertex_attribs.borrow(); + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.2 + if attribs + .iter() + .any(|data| data.enabled_as_array && data.buffer.is_none()) + { + return Err(WebGLError::InvalidOperation); + } + let mut has_active_attrib = false; + let mut has_divisor_0 = false; + for active_info in active_attribs { + if active_info.location < 0 { + continue; + } + has_active_attrib = true; + let attrib = &attribs[active_info.location as usize]; + if attrib.divisor == 0 { + has_divisor_0 = true; + } + if !attrib.enabled_as_array { + continue; + } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.6 + if required_len > 0 && instance_count > 0 { + let max_vertices = attrib.max_vertices(); + if attrib.divisor == 0 { + if max_vertices < required_len { + return Err(WebGLError::InvalidOperation); + } + } else if max_vertices + .checked_mul(attrib.divisor) + .map_or(false, |v| v < instance_count) + { + return Err(WebGLError::InvalidOperation); + } + } + } + if has_active_attrib && !has_divisor_0 { + return Err(WebGLError::InvalidOperation); + } + Ok(()) + } +} + +impl Drop for VertexArrayObject { + fn drop(&mut self) { + if self.id.is_some() { + self.delete(Operation::Fallible); + } + } +} + +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[unrooted_must_root_lint::must_root] +pub struct VertexAttribData { + pub enabled_as_array: bool, + pub size: u8, + pub type_: u32, + bytes_per_vertex: u8, + pub normalized: bool, + pub stride: u8, + pub offset: u32, + pub buffer: Option<Dom<WebGLBuffer>>, + pub divisor: u32, +} + +impl Default for VertexAttribData { + #[allow(unrooted_must_root)] + fn default() -> Self { + Self { + enabled_as_array: false, + size: 4, + type_: constants::FLOAT, + bytes_per_vertex: 16, + normalized: false, + stride: 0, + offset: 0, + buffer: None, + divisor: 0, + } + } +} + +impl VertexAttribData { + pub fn buffer(&self) -> Option<&WebGLBuffer> { + self.buffer.as_ref().map(|b| &**b) + } + + pub fn max_vertices(&self) -> u32 { + let capacity = (self.buffer().unwrap().capacity() as u32).saturating_sub(self.offset); + if capacity < self.bytes_per_vertex as u32 { + 0 + } else if self.stride == 0 { + capacity / self.bytes_per_vertex as u32 + } else if self.stride < self.bytes_per_vertex { + (capacity - (self.bytes_per_vertex - self.stride) as u32) / self.stride as u32 + } else { + let mut max = capacity / self.stride as u32; + if capacity % self.stride as u32 >= self.bytes_per_vertex as u32 { + max += 1; + } + max + } + } +} diff --git a/components/script/dom/videotrack.rs b/components/script/dom/videotrack.rs new file mode 100644 index 00000000000..8f904284fd9 --- /dev/null +++ b/components/script/dom/videotrack.rs @@ -0,0 +1,121 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::VideoTrackBinding::VideoTrackMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::videotracklist::VideoTrackList; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct VideoTrack { + reflector_: Reflector, + id: DOMString, + kind: DOMString, + label: DOMString, + language: DOMString, + selected: Cell<bool>, + track_list: DomRefCell<Option<Dom<VideoTrackList>>>, +} + +impl VideoTrack { + pub fn new_inherited( + id: DOMString, + kind: DOMString, + label: DOMString, + language: DOMString, + track_list: Option<&VideoTrackList>, + ) -> VideoTrack { + VideoTrack { + reflector_: Reflector::new(), + id: id.into(), + kind: kind.into(), + label: label.into(), + language: language.into(), + selected: Cell::new(false), + track_list: DomRefCell::new(track_list.map(|t| Dom::from_ref(t))), + } + } + + pub fn new( + window: &Window, + id: DOMString, + kind: DOMString, + label: DOMString, + language: DOMString, + track_list: Option<&VideoTrackList>, + ) -> DomRoot<VideoTrack> { + reflect_dom_object( + Box::new(VideoTrack::new_inherited( + id, kind, label, language, track_list, + )), + window, + ) + } + + pub fn id(&self) -> DOMString { + self.id.clone() + } + + pub fn kind(&self) -> DOMString { + self.kind.clone() + } + + pub fn selected(&self) -> bool { + self.selected.get().clone() + } + + pub fn set_selected(&self, value: bool) { + self.selected.set(value); + } + + pub fn add_track_list(&self, track_list: &VideoTrackList) { + *self.track_list.borrow_mut() = Some(Dom::from_ref(track_list)); + } + + pub fn remove_track_list(&self) { + *self.track_list.borrow_mut() = None; + } +} + +impl VideoTrackMethods for VideoTrack { + // https://html.spec.whatwg.org/multipage/#dom-videotrack-id + fn Id(&self) -> DOMString { + self.id() + } + + // https://html.spec.whatwg.org/multipage/#dom-videotrack-kind + fn Kind(&self) -> DOMString { + self.kind() + } + + // https://html.spec.whatwg.org/multipage/#dom-videotrack-label + fn Label(&self) -> DOMString { + self.label.clone() + } + + // https://html.spec.whatwg.org/multipage/#dom-videotrack-language + fn Language(&self) -> DOMString { + self.language.clone() + } + + // https://html.spec.whatwg.org/multipage/#dom-videotrack-selected + fn Selected(&self) -> bool { + self.selected() + } + + // https://html.spec.whatwg.org/multipage/#dom-videotrack-selected + fn SetSelected(&self, value: bool) { + if let Some(list) = self.track_list.borrow().as_ref() { + if let Some(idx) = list.find(self) { + list.set_selected(idx, value); + } + } + self.set_selected(value); + } +} diff --git a/components/script/dom/videotracklist.rs b/components/script/dom/videotracklist.rs new file mode 100644 index 00000000000..a56e29d1470 --- /dev/null +++ b/components/script/dom/videotracklist.rs @@ -0,0 +1,163 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::VideoTrackListBinding::VideoTrackListMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::videotrack::VideoTrack; +use crate::dom::window::Window; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct VideoTrackList { + eventtarget: EventTarget, + tracks: DomRefCell<Vec<Dom<VideoTrack>>>, + media_element: Option<Dom<HTMLMediaElement>>, +} + +impl VideoTrackList { + pub fn new_inherited( + tracks: &[&VideoTrack], + media_element: Option<&HTMLMediaElement>, + ) -> VideoTrackList { + VideoTrackList { + eventtarget: EventTarget::new_inherited(), + tracks: DomRefCell::new(tracks.iter().map(|track| Dom::from_ref(&**track)).collect()), + media_element: media_element.map(|m| Dom::from_ref(m)), + } + } + + pub fn new( + window: &Window, + tracks: &[&VideoTrack], + media_element: Option<&HTMLMediaElement>, + ) -> DomRoot<VideoTrackList> { + reflect_dom_object( + Box::new(VideoTrackList::new_inherited(tracks, media_element)), + window, + ) + } + + pub fn len(&self) -> usize { + self.tracks.borrow().len() + } + + pub fn find(&self, track: &VideoTrack) -> Option<usize> { + self.tracks.borrow().iter().position(|t| &**t == track) + } + + pub fn item(&self, idx: usize) -> Option<DomRoot<VideoTrack>> { + self.tracks + .borrow() + .get(idx) + .map(|track| DomRoot::from_ref(&**track)) + } + + pub fn selected_index(&self) -> Option<usize> { + self.tracks + .borrow() + .iter() + .position(|track| track.selected()) + } + + pub fn set_selected(&self, idx: usize, value: bool) { + let track = match self.item(idx) { + Some(t) => t, + None => return, + }; + + // If the chosen tracks selected status is the same as the new status, return early. + if track.selected() == value { + return; + } + + let global = &self.global(); + let this = Trusted::new(self); + let (source, canceller) = global + .as_window() + .task_manager() + .media_element_task_source_with_canceller(); + + if let Some(current) = self.selected_index() { + self.tracks.borrow()[current].set_selected(false); + } + + track.set_selected(value); + if let Some(media_element) = self.media_element.as_ref() { + media_element.set_video_track(idx, value); + } + + let _ = source.queue_with_canceller( + task!(media_track_change: move || { + let this = this.root(); + this.upcast::<EventTarget>().fire_event(atom!("change")); + }), + &canceller, + ); + } + + pub fn add(&self, track: &VideoTrack) { + self.tracks.borrow_mut().push(Dom::from_ref(track)); + if track.selected() { + if let Some(idx) = self.selected_index() { + self.set_selected(idx, false); + } + } + track.add_track_list(self); + } + + pub fn clear(&self) { + self.tracks + .borrow() + .iter() + .for_each(|t| t.remove_track_list()); + self.tracks.borrow_mut().clear(); + } +} + +impl VideoTrackListMethods for VideoTrackList { + // https://html.spec.whatwg.org/multipage/#dom-videotracklist-length + fn Length(&self) -> u32 { + self.len() as u32 + } + + // https://html.spec.whatwg.org/multipage/#dom-tracklist-item + fn IndexedGetter(&self, idx: u32) -> Option<DomRoot<VideoTrack>> { + self.item(idx as usize) + } + + // https://html.spec.whatwg.org/multipage/#dom-videotracklist-gettrackbyid + fn GetTrackById(&self, id: DOMString) -> Option<DomRoot<VideoTrack>> { + self.tracks + .borrow() + .iter() + .find(|track| track.id() == id) + .map(|track| DomRoot::from_ref(&**track)) + } + + // https://html.spec.whatwg.org/multipage/#dom-videotrack-selected + fn SelectedIndex(&self) -> i32 { + if let Some(idx) = self.selected_index() { + return idx as i32; + } + return -1; + } + + // https://html.spec.whatwg.org/multipage/#handler-tracklist-onchange + event_handler!(change, GetOnchange, SetOnchange); + + // https://html.spec.whatwg.org/multipage/#handler-tracklist-onaddtrack + event_handler!(addtrack, GetOnaddtrack, SetOnaddtrack); + + // https://html.spec.whatwg.org/multipage/#handler-tracklist-onremovetrack + event_handler!(removetrack, GetOnremovetrack, SetOnremovetrack); +} diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index 8ff47f9ced5..9f175d8d621 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -1,57 +1,60 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::attr::Attr; -use dom::bindings::inheritance::Castable; -use dom::bindings::inheritance::ElementTypeId; -use dom::bindings::inheritance::HTMLElementTypeId; -use dom::bindings::inheritance::NodeTypeId; -use dom::bindings::inheritance::SVGElementTypeId; -use dom::bindings::inheritance::SVGGraphicsElementTypeId; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::event::Event; -use dom::htmlanchorelement::HTMLAnchorElement; -use dom::htmlappletelement::HTMLAppletElement; -use dom::htmlareaelement::HTMLAreaElement; -use dom::htmlbaseelement::HTMLBaseElement; -use dom::htmlbodyelement::HTMLBodyElement; -use dom::htmlbuttonelement::HTMLButtonElement; -use dom::htmlcanvaselement::HTMLCanvasElement; -use dom::htmldetailselement::HTMLDetailsElement; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlfontelement::HTMLFontElement; -use dom::htmlformelement::HTMLFormElement; -use dom::htmlheadelement::HTMLHeadElement; -use dom::htmlhrelement::HTMLHRElement; -use dom::htmliframeelement::HTMLIFrameElement; -use dom::htmlimageelement::HTMLImageElement; -use dom::htmlinputelement::HTMLInputElement; -use dom::htmllabelelement::HTMLLabelElement; -use dom::htmllielement::HTMLLIElement; -use dom::htmllinkelement::HTMLLinkElement; -use dom::htmlmediaelement::HTMLMediaElement; -use dom::htmlmetaelement::HTMLMetaElement; -use dom::htmlobjectelement::HTMLObjectElement; -use dom::htmloptgroupelement::HTMLOptGroupElement; -use dom::htmloptionelement::HTMLOptionElement; -use dom::htmloutputelement::HTMLOutputElement; -use dom::htmlscriptelement::HTMLScriptElement; -use dom::htmlselectelement::HTMLSelectElement; -use dom::htmlstyleelement::HTMLStyleElement; -use dom::htmltablecellelement::HTMLTableCellElement; -use dom::htmltableelement::HTMLTableElement; -use dom::htmltablerowelement::HTMLTableRowElement; -use dom::htmltablesectionelement::HTMLTableSectionElement; -use dom::htmltemplateelement::HTMLTemplateElement; -use dom::htmltextareaelement::HTMLTextAreaElement; -use dom::htmltitleelement::HTMLTitleElement; -use dom::node::{ChildrenMutation, CloneChildrenFlag, Node, UnbindContext}; -use dom::svgsvgelement::SVGSVGElement; -use html5ever_atoms::LocalName; +use crate::dom::attr::Attr; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::inheritance::ElementTypeId; +use crate::dom::bindings::inheritance::HTMLElementTypeId; +use crate::dom::bindings::inheritance::HTMLMediaElementTypeId; +use crate::dom::bindings::inheritance::NodeTypeId; +use crate::dom::bindings::inheritance::SVGElementTypeId; +use crate::dom::bindings::inheritance::SVGGraphicsElementTypeId; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::event::Event; +use crate::dom::htmlanchorelement::HTMLAnchorElement; +use crate::dom::htmlareaelement::HTMLAreaElement; +use crate::dom::htmlbaseelement::HTMLBaseElement; +use crate::dom::htmlbodyelement::HTMLBodyElement; +use crate::dom::htmlbuttonelement::HTMLButtonElement; +use crate::dom::htmlcanvaselement::HTMLCanvasElement; +use crate::dom::htmldetailselement::HTMLDetailsElement; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlfontelement::HTMLFontElement; +use crate::dom::htmlformelement::HTMLFormElement; +use crate::dom::htmlheadelement::HTMLHeadElement; +use crate::dom::htmlhrelement::HTMLHRElement; +use crate::dom::htmliframeelement::HTMLIFrameElement; +use crate::dom::htmlimageelement::HTMLImageElement; +use crate::dom::htmlinputelement::HTMLInputElement; +use crate::dom::htmllabelelement::HTMLLabelElement; +use crate::dom::htmllielement::HTMLLIElement; +use crate::dom::htmllinkelement::HTMLLinkElement; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::htmlmetaelement::HTMLMetaElement; +use crate::dom::htmlobjectelement::HTMLObjectElement; +use crate::dom::htmloptgroupelement::HTMLOptGroupElement; +use crate::dom::htmloptionelement::HTMLOptionElement; +use crate::dom::htmloutputelement::HTMLOutputElement; +use crate::dom::htmlscriptelement::HTMLScriptElement; +use crate::dom::htmlselectelement::HTMLSelectElement; +use crate::dom::htmlsourceelement::HTMLSourceElement; +use crate::dom::htmlstyleelement::HTMLStyleElement; +use crate::dom::htmltablecellelement::HTMLTableCellElement; +use crate::dom::htmltableelement::HTMLTableElement; +use crate::dom::htmltablerowelement::HTMLTableRowElement; +use crate::dom::htmltablesectionelement::HTMLTableSectionElement; +use crate::dom::htmltemplateelement::HTMLTemplateElement; +use crate::dom::htmltextareaelement::HTMLTextAreaElement; +use crate::dom::htmltitleelement::HTMLTitleElement; +use crate::dom::htmlvideoelement::HTMLVideoElement; +use crate::dom::node::{BindContext, ChildrenMutation, CloneChildrenFlag, Node, UnbindContext}; +use crate::dom::svgelement::SVGElement; +use crate::dom::svgsvgelement::SVGSVGElement; +use html5ever::LocalName; use style::attr::AttrValue; /// Trait to allow DOM nodes to opt-in to overriding (or adding to) common @@ -59,17 +62,26 @@ use style::attr::AttrValue; pub trait VirtualMethods { /// Returns self as the superclass of the implementation for this trait, /// if any. - fn super_type(&self) -> Option<&VirtualMethods>; + fn super_type(&self) -> Option<&dyn VirtualMethods>; /// Called when attributes of a node are mutated. - /// https://dom.spec.whatwg.org/#attribute-is-set - /// https://dom.spec.whatwg.org/#attribute-is-removed + /// <https://dom.spec.whatwg.org/#attribute-is-set> + /// <https://dom.spec.whatwg.org/#attribute-is-removed> fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { if let Some(s) = self.super_type() { s.attribute_mutated(attr, mutation); } } + /// Returns `true` if given attribute `attr` affects style of the + /// given element. + fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool { + match self.super_type() { + Some(s) => s.attribute_affects_presentational_hints(attr), + None => false, + } + } + /// Returns the right AttrValue variant for the attribute with name `name` /// on this element. fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { @@ -79,18 +91,18 @@ pub trait VirtualMethods { } } - /// Called when a Node is appended to a tree, where 'tree_in_doc' indicates + /// Called when a Node is appended to a tree, where 'tree_connected' indicates /// whether the tree is part of a Document. - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } } - /// Called when a Node is removed from a tree, where 'tree_in_doc' + /// Called when a Node is removed from a tree, where 'tree_connected' /// indicates whether the tree is part of a Document. /// Implements removing steps: - /// https://dom.spec.whatwg.org/#concept-node-remove-ext + /// <https://dom.spec.whatwg.org/#concept-node-remove-ext> fn unbind_from_tree(&self, context: &UnbindContext) { if let Some(ref s) = self.super_type() { s.unbind_from_tree(context); @@ -111,16 +123,20 @@ pub trait VirtualMethods { } } - /// https://dom.spec.whatwg.org/#concept-node-adopt-ext + /// <https://dom.spec.whatwg.org/#concept-node-adopt-ext> fn adopting_steps(&self, old_doc: &Document) { if let Some(ref s) = self.super_type() { s.adopting_steps(old_doc); } } - /// https://dom.spec.whatwg.org/#concept-node-clone-ext - fn cloning_steps(&self, copy: &Node, maybe_doc: Option<&Document>, - clone_children: CloneChildrenFlag) { + /// <https://dom.spec.whatwg.org/#concept-node-clone-ext> + fn cloning_steps( + &self, + copy: &Node, + maybe_doc: Option<&Document>, + clone_children: CloneChildrenFlag, + ) { if let Some(ref s) = self.super_type() { s.cloning_steps(copy, maybe_doc, clone_children); } @@ -139,126 +155,128 @@ pub trait VirtualMethods { /// method call on the trait object will invoke the corresponding method on the /// concrete type, propagating up the parent hierarchy unless otherwise /// interrupted. -pub fn vtable_for(node: &Node) -> &VirtualMethods { +pub fn vtable_for(node: &Node) -> &dyn VirtualMethods { match node.type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) => { - node.downcast::<HTMLAnchorElement>().unwrap() as &VirtualMethods - } - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAppletElement)) => { - node.downcast::<HTMLAppletElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLAnchorElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) => { - node.downcast::<HTMLAreaElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLAreaElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBaseElement)) => { - node.downcast::<HTMLBaseElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLBaseElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement)) => { - node.downcast::<HTMLBodyElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLBodyElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) => { - node.downcast::<HTMLButtonElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLButtonElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement)) => { - node.downcast::<HTMLCanvasElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLCanvasElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLDetailsElement)) => { - node.downcast::<HTMLDetailsElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLDetailsElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFieldSetElement)) => { - node.downcast::<HTMLFieldSetElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLFieldSetElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFontElement)) => { - node.downcast::<HTMLFontElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLFontElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFormElement)) => { - node.downcast::<HTMLFormElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLFormElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLHeadElement)) => { - node.downcast::<HTMLHeadElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLHeadElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLHRElement)) => { - node.downcast::<HTMLHRElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLHRElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement)) => { - node.downcast::<HTMLImageElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLImageElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement)) => { - node.downcast::<HTMLIFrameElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLIFrameElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { - node.downcast::<HTMLInputElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLInputElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => { - node.downcast::<HTMLLabelElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLLabelElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLIElement)) => { - node.downcast::<HTMLLIElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLLIElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => { - node.downcast::<HTMLLinkElement>().unwrap() as &VirtualMethods - } - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLMediaElement(_))) => { - node.downcast::<HTMLMediaElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLLinkElement>().unwrap() as &dyn VirtualMethods + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLMediaElement( + media_el, + ))) => match media_el { + HTMLMediaElementTypeId::HTMLVideoElement => { + node.downcast::<HTMLVideoElement>().unwrap() as &dyn VirtualMethods + }, + _ => node.downcast::<HTMLMediaElement>().unwrap() as &dyn VirtualMethods, + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLMetaElement)) => { - node.downcast::<HTMLMetaElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLMetaElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { - node.downcast::<HTMLObjectElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLObjectElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptGroupElement)) => { - node.downcast::<HTMLOptGroupElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLOptGroupElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptionElement)) => { - node.downcast::<HTMLOptionElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLOptionElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { - node.downcast::<HTMLOutputElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLOutputElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLScriptElement)) => { - node.downcast::<HTMLScriptElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLScriptElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { - node.downcast::<HTMLSelectElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLSelectElement>().unwrap() as &dyn VirtualMethods + }, + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSourceElement)) => { + node.downcast::<HTMLSourceElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLStyleElement)) => { - node.downcast::<HTMLStyleElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLStyleElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableElement)) => { - node.downcast::<HTMLTableElement>().unwrap() as &VirtualMethods - } - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableCellElement(_))) => { - node.downcast::<HTMLTableCellElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLTableElement>().unwrap() as &dyn VirtualMethods + }, + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTableCellElement, + )) => node.downcast::<HTMLTableCellElement>().unwrap() as &dyn VirtualMethods, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableRowElement)) => { - node.downcast::<HTMLTableRowElement>().unwrap() as &VirtualMethods - } - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableSectionElement)) => { - node.downcast::<HTMLTableSectionElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLTableRowElement>().unwrap() as &dyn VirtualMethods + }, + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTableSectionElement, + )) => node.downcast::<HTMLTableSectionElement>().unwrap() as &dyn VirtualMethods, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTemplateElement)) => { - node.downcast::<HTMLTemplateElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLTemplateElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { - node.downcast::<HTMLTextAreaElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLTextAreaElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTitleElement)) => { - node.downcast::<HTMLTitleElement>().unwrap() as &VirtualMethods - } + node.downcast::<HTMLTitleElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::SVGElement(SVGElementTypeId::SVGGraphicsElement( - SVGGraphicsElementTypeId::SVGSVGElement - ))) => { - node.downcast::<SVGSVGElement>().unwrap() as &VirtualMethods - } + SVGGraphicsElementTypeId::SVGSVGElement, + ))) => node.downcast::<SVGSVGElement>().unwrap() as &dyn VirtualMethods, + NodeTypeId::Element(ElementTypeId::SVGElement(SVGElementTypeId::SVGElement)) => { + node.downcast::<SVGElement>().unwrap() as &dyn VirtualMethods + }, NodeTypeId::Element(ElementTypeId::Element) => { - node.downcast::<Element>().unwrap() as &VirtualMethods - } - NodeTypeId::Element(_) => { - node.downcast::<HTMLElement>().unwrap() as &VirtualMethods - } - _ => { - node as &VirtualMethods - } + node.downcast::<Element>().unwrap() as &dyn VirtualMethods + }, + NodeTypeId::Element(_) => node.downcast::<HTMLElement>().unwrap() as &dyn VirtualMethods, + _ => node as &dyn VirtualMethods, } } diff --git a/components/script/dom/vr.rs b/components/script/dom/vr.rs deleted file mode 100644 index 9497405e8c9..00000000000 --- a/components/script/dom/vr.rs +++ /dev/null @@ -1,251 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::VRBinding; -use dom::bindings::codegen::Bindings::VRBinding::VRMethods; -use dom::bindings::codegen::Bindings::VRDisplayBinding::VRDisplayMethods; -use dom::bindings::error::Error; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::gamepad::Gamepad; -use dom::gamepadevent::GamepadEventType; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; -use dom::vrdisplay::VRDisplay; -use dom::vrdisplayevent::VRDisplayEvent; -use dom_struct::dom_struct; -use ipc_channel::ipc; -use ipc_channel::ipc::IpcSender; -use std::rc::Rc; -use webvr_traits::{WebVRDisplayData, WebVRDisplayEvent, WebVREvent, WebVRMsg}; -use webvr_traits::{WebVRGamepadData, WebVRGamepadEvent, WebVRGamepadState}; - -#[dom_struct] -pub struct VR { - eventtarget: EventTarget, - displays: DOMRefCell<Vec<JS<VRDisplay>>>, - gamepads: DOMRefCell<Vec<JS<Gamepad>>> -} - -impl VR { - fn new_inherited() -> VR { - VR { - eventtarget: EventTarget::new_inherited(), - displays: DOMRefCell::new(Vec::new()), - gamepads: DOMRefCell::new(Vec::new()), - } - } - - pub fn new(global: &GlobalScope) -> Root<VR> { - let root = reflect_dom_object(box VR::new_inherited(), - global, - VRBinding::Wrap); - root.register(); - root - } -} - -impl Drop for VR { - fn drop(&mut self) { - self.unregister(); - } -} - -impl VRMethods for VR { - #[allow(unrooted_must_root)] - // https://w3c.github.io/webvr/#interface-navigator - fn GetDisplays(&self) -> Rc<Promise> { - let promise = Promise::new(&self.global()); - - if let Some(webvr_thread) = self.webvr_thread() { - let (sender, receiver) = ipc::channel().unwrap(); - webvr_thread.send(WebVRMsg::GetDisplays(sender)).unwrap(); - match receiver.recv().unwrap() { - Ok(displays) => { - // Sync displays - for display in displays { - self.sync_display(&display); - } - }, - Err(e) => { - promise.reject_native(promise.global().get_cx(), &e); - return promise; - } - } - } else { - // WebVR spec: The Promise MUST be rejected if WebVR is not enabled/supported. - promise.reject_error(promise.global().get_cx(), Error::Security); - return promise; - } - - // convert from JS to Root - let displays: Vec<Root<VRDisplay>> = self.displays.borrow().iter() - .map(|d| Root::from_ref(&**d)) - .collect(); - promise.resolve_native(promise.global().get_cx(), &displays); - - promise - } -} - - -impl VR { - fn webvr_thread(&self) -> Option<IpcSender<WebVRMsg>> { - self.global().as_window().webvr_thread() - } - - fn find_display(&self, display_id: u32) -> Option<Root<VRDisplay>> { - self.displays.borrow() - .iter() - .find(|d| d.DisplayId() == display_id) - .map(|d| Root::from_ref(&**d)) - } - - fn register(&self) { - if let Some(webvr_thread) = self.webvr_thread() { - let msg = WebVRMsg::RegisterContext(self.global().pipeline_id()); - webvr_thread.send(msg).unwrap(); - } - } - - fn unregister(&self) { - if let Some(webvr_thread) = self.webvr_thread() { - let msg = WebVRMsg::UnregisterContext(self.global().pipeline_id()); - webvr_thread.send(msg).unwrap(); - } - } - - fn sync_display(&self, display: &WebVRDisplayData) -> Root<VRDisplay> { - if let Some(existing) = self.find_display(display.display_id) { - existing.update_display(&display); - existing - } else { - let root = VRDisplay::new(&self.global(), display.clone()); - self.displays.borrow_mut().push(JS::from_ref(&*root)); - root - } - } - - fn handle_display_event(&self, event: WebVRDisplayEvent) { - match event { - WebVRDisplayEvent::Connect(ref display) => { - let display = self.sync_display(&display); - display.handle_webvr_event(&event); - self.notify_display_event(&display, &event); - }, - WebVRDisplayEvent::Disconnect(id) => { - if let Some(display) = self.find_display(id) { - display.handle_webvr_event(&event); - self.notify_display_event(&display, &event); - } - }, - WebVRDisplayEvent::Activate(ref display, _) | - WebVRDisplayEvent::Deactivate(ref display, _) | - WebVRDisplayEvent::Blur(ref display) | - WebVRDisplayEvent::Focus(ref display) | - WebVRDisplayEvent::PresentChange(ref display, _) | - WebVRDisplayEvent::Change(ref display) => { - let display = self.sync_display(&display); - display.handle_webvr_event(&event); - } - }; - } - - fn handle_gamepad_event(&self, event: WebVRGamepadEvent) { - match event { - WebVRGamepadEvent::Connect(data, state) => { - if let Some(gamepad) = self.find_gamepad(state.gamepad_id) { - gamepad.update_from_vr(&state); - } else { - // new gamepad - self.sync_gamepad(Some(data), &state); - } - }, - WebVRGamepadEvent::Disconnect(id) => { - if let Some(gamepad) = self.find_gamepad(id) { - gamepad.update_connected(false); - } - } - }; - } - - pub fn handle_webvr_event(&self, event: WebVREvent) { - match event { - WebVREvent::Display(event) => { - self.handle_display_event(event); - }, - WebVREvent::Gamepad(event) => { - self.handle_gamepad_event(event); - } - }; - } - - pub fn handle_webvr_events(&self, events: Vec<WebVREvent>) { - for event in events { - self.handle_webvr_event(event); - } - } - - fn notify_display_event(&self, display: &VRDisplay, event: &WebVRDisplayEvent) { - let event = VRDisplayEvent::new_from_webvr(&self.global(), &display, &event); - event.upcast::<Event>().fire(self.upcast()); - } -} - -// Gamepad -impl VR { - fn find_gamepad(&self, gamepad_id: u32) -> Option<Root<Gamepad>> { - self.gamepads.borrow() - .iter() - .find(|g| g.gamepad_id() == gamepad_id) - .map(|g| Root::from_ref(&**g)) - } - - fn sync_gamepad(&self, data: Option<WebVRGamepadData>, state: &WebVRGamepadState) { - if let Some(existing) = self.find_gamepad(state.gamepad_id) { - existing.update_from_vr(&state); - } else { - let index = self.gamepads.borrow().len(); - let data = data.unwrap_or_default(); - let root = Gamepad::new_from_vr(&self.global(), - index as i32, - &data, - &state); - self.gamepads.borrow_mut().push(JS::from_ref(&*root)); - if state.connected { - root.notify_event(GamepadEventType::Connected); - } - } - } - - // Gamepads are synced immediately in response to the API call. - // The current approach allows the to sample gamepad state multiple times per frame. This - // guarantees that the gamepads always have a valid state and can be very useful for - // motion capture or drawing applications. - pub fn get_gamepads(&self) -> Vec<Root<Gamepad>> { - if let Some(wevbr_sender) = self.webvr_thread() { - let (sender, receiver) = ipc::channel().unwrap(); - let synced_ids = self.gamepads.borrow().iter().map(|g| g.gamepad_id()).collect(); - wevbr_sender.send(WebVRMsg::GetGamepads(synced_ids, sender)).unwrap(); - match receiver.recv().unwrap() { - Ok(gamepads) => { - // Sync displays - for gamepad in gamepads { - self.sync_gamepad(gamepad.0, &gamepad.1); - } - }, - Err(_) => {} - } - } - - // We can add other not VR related gamepad providers here - self.gamepads.borrow().iter() - .map(|g| Root::from_ref(&**g)) - .collect() - } -} diff --git a/components/script/dom/vrdisplay.rs b/components/script/dom/vrdisplay.rs deleted file mode 100644 index b9b65264c44..00000000000 --- a/components/script/dom/vrdisplay.rs +++ /dev/null @@ -1,613 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use canvas_traits::CanvasMsg; -use core::ops::Deref; -use dom::bindings::callback::ExceptionHandling; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceBinding::PerformanceMethods; -use dom::bindings::codegen::Bindings::VRDisplayBinding; -use dom::bindings::codegen::Bindings::VRDisplayBinding::VRDisplayMethods; -use dom::bindings::codegen::Bindings::VRDisplayBinding::VREye; -use dom::bindings::codegen::Bindings::VRLayerBinding::VRLayer; -use dom::bindings::codegen::Bindings::WindowBinding::FrameRequestCallback; -use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, MutJS, Root}; -use dom::bindings::num::Finite; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; -use dom::vrdisplaycapabilities::VRDisplayCapabilities; -use dom::vrdisplayevent::VRDisplayEvent; -use dom::vreyeparameters::VREyeParameters; -use dom::vrframedata::VRFrameData; -use dom::vrpose::VRPose; -use dom::vrstageparameters::VRStageParameters; -use dom::webglrenderingcontext::WebGLRenderingContext; -use dom_struct::dom_struct; -use ipc_channel::ipc; -use ipc_channel::ipc::{IpcSender, IpcReceiver}; -use js::jsapi::JSContext; -use script_runtime::CommonScriptMsg; -use script_runtime::ScriptThreadEventCategory::WebVREvent; -use script_thread::Runnable; -use std::cell::Cell; -use std::mem; -use std::rc::Rc; -use std::sync::mpsc; -use std::thread; -use webrender_traits::VRCompositorCommand; -use webvr_traits::{WebVRDisplayData, WebVRDisplayEvent, WebVRFrameData, WebVRLayer, WebVRMsg}; - -#[dom_struct] -pub struct VRDisplay { - eventtarget: EventTarget, - #[ignore_heap_size_of = "Defined in rust-webvr"] - display: DOMRefCell<WebVRDisplayData>, - depth_near: Cell<f64>, - depth_far: Cell<f64>, - presenting: Cell<bool>, - left_eye_params: MutJS<VREyeParameters>, - right_eye_params: MutJS<VREyeParameters>, - capabilities: MutJS<VRDisplayCapabilities>, - stage_params: MutNullableJS<VRStageParameters>, - #[ignore_heap_size_of = "Defined in rust-webvr"] - frame_data: DOMRefCell<WebVRFrameData>, - #[ignore_heap_size_of = "Defined in rust-webvr"] - layer: DOMRefCell<WebVRLayer>, - layer_ctx: MutNullableJS<WebGLRenderingContext>, - #[ignore_heap_size_of = "Defined in rust-webvr"] - next_raf_id: Cell<u32>, - /// List of request animation frame callbacks - #[ignore_heap_size_of = "closures are hard"] - raf_callback_list: DOMRefCell<Vec<(u32, Option<Rc<FrameRequestCallback>>)>>, - // Compositor VRFrameData synchonization - frame_data_status: Cell<VRFrameDataStatus>, - #[ignore_heap_size_of = "channels are hard"] - frame_data_receiver: DOMRefCell<Option<IpcReceiver<Result<Vec<u8>, ()>>>>, - running_display_raf: Cell<bool> -} - -unsafe_no_jsmanaged_fields!(WebVRDisplayData); -unsafe_no_jsmanaged_fields!(WebVRFrameData); -unsafe_no_jsmanaged_fields!(WebVRLayer); - -#[derive(Clone, Copy, PartialEq, Eq, HeapSizeOf)] -enum VRFrameDataStatus { - Waiting, - Synced, - Exit -} - -unsafe_no_jsmanaged_fields!(VRFrameDataStatus); - -impl VRDisplay { - fn new_inherited(global: &GlobalScope, display: WebVRDisplayData) -> VRDisplay { - let stage = match display.stage_parameters { - Some(ref params) => Some(VRStageParameters::new(params.clone(), &global)), - None => None - }; - - VRDisplay { - eventtarget: EventTarget::new_inherited(), - display: DOMRefCell::new(display.clone()), - depth_near: Cell::new(0.01), - depth_far: Cell::new(10000.0), - presenting: Cell::new(false), - left_eye_params: MutJS::new(&*VREyeParameters::new(display.left_eye_parameters.clone(), &global)), - right_eye_params: MutJS::new(&*VREyeParameters::new(display.right_eye_parameters.clone(), &global)), - capabilities: MutJS::new(&*VRDisplayCapabilities::new(display.capabilities.clone(), &global)), - stage_params: MutNullableJS::new(stage.as_ref().map(|v| v.deref())), - frame_data: DOMRefCell::new(Default::default()), - layer: DOMRefCell::new(Default::default()), - layer_ctx: MutNullableJS::default(), - next_raf_id: Cell::new(1), - raf_callback_list: DOMRefCell::new(vec![]), - frame_data_status: Cell::new(VRFrameDataStatus::Waiting), - frame_data_receiver: DOMRefCell::new(None), - running_display_raf: Cell::new(false), - } - } - - pub fn new(global: &GlobalScope, display: WebVRDisplayData) -> Root<VRDisplay> { - reflect_dom_object(box VRDisplay::new_inherited(&global, display), - global, - VRDisplayBinding::Wrap) - } -} - -impl Drop for VRDisplay { - fn drop(&mut self) { - if self.presenting.get() { - self.force_stop_present(); - } - } -} - -impl VRDisplayMethods for VRDisplay { - // https://w3c.github.io/webvr/#dom-vrdisplay-isconnected - fn IsConnected(&self) -> bool { - self.display.borrow().connected - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-ispresenting - fn IsPresenting(&self) -> bool { - self.presenting.get() - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-capabilities - fn Capabilities(&self) -> Root<VRDisplayCapabilities> { - Root::from_ref(&*self.capabilities.get()) - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-stageparameters - fn GetStageParameters(&self) -> Option<Root<VRStageParameters>> { - self.stage_params.get().map(|s| Root::from_ref(&*s)) - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-geteyeparameters - fn GetEyeParameters(&self, eye: VREye) -> Root<VREyeParameters> { - match eye { - VREye::Left => Root::from_ref(&*self.left_eye_params.get()), - VREye::Right => Root::from_ref(&*self.right_eye_params.get()) - } - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-displayid - fn DisplayId(&self) -> u32 { - self.display.borrow().display_id - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-displayname - fn DisplayName(&self) -> DOMString { - DOMString::from(self.display.borrow().display_name.clone()) - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-getframedata-framedata-framedata - fn GetFrameData(&self, frameData: &VRFrameData) -> bool { - // If presenting we use a synced data with compositor for the whole frame. - // Frame data is only synced with compositor when GetFrameData is called from - // inside the VRDisplay.requestAnimationFrame. This is checked using the running_display_raf property. - // This check avoids data race conditions when calling GetFrameData from outside of the - // VRDisplay.requestAnimationFrame callbacks and fixes a possible deadlock during the interval - // when the requestAnimationFrame is moved from window to VRDisplay. - if self.presenting.get() && self.running_display_raf.get() { - if self.frame_data_status.get() == VRFrameDataStatus::Waiting { - self.sync_frame_data(); - } - frameData.update(& self.frame_data.borrow()); - return true; - } - - // If not presenting we fetch inmediante VRFrameData - let (sender, receiver) = ipc::channel().unwrap(); - self.webvr_thread().send(WebVRMsg::GetFrameData(self.global().pipeline_id(), - self.DisplayId(), - self.depth_near.get(), - self.depth_far.get(), - sender)).unwrap(); - return match receiver.recv().unwrap() { - Ok(data) => { - frameData.update(&data); - true - }, - Err(e) => { - error!("WebVR::GetFrameData: {:?}", e); - false - } - }; - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-getpose - fn GetPose(&self) -> Root<VRPose> { - VRPose::new(&self.global(), &self.frame_data.borrow().pose) - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-resetpose - fn ResetPose(&self) { - let (sender, receiver) = ipc::channel().unwrap(); - self.webvr_thread().send(WebVRMsg::ResetPose(self.global().pipeline_id(), - self.DisplayId(), - sender)).unwrap(); - if let Ok(data) = receiver.recv().unwrap() { - // Some VRDisplay data might change after calling ResetPose() - *self.display.borrow_mut() = data; - } - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-depthnear - fn DepthNear(&self) -> Finite<f64> { - Finite::wrap(self.depth_near.get()) - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-depthnear - fn SetDepthNear(&self, value: Finite<f64>) { - self.depth_near.set(*value.deref()); - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-depthfar - fn DepthFar(&self) -> Finite<f64> { - Finite::wrap(self.depth_far.get()) - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-depthfar - fn SetDepthFar(&self, value: Finite<f64>) { - self.depth_far.set(*value.deref()); - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-requestanimationframe - fn RequestAnimationFrame(&self, callback: Rc<FrameRequestCallback>) -> u32 { - if self.presenting.get() { - let raf_id = self.next_raf_id.get(); - self.next_raf_id.set(raf_id + 1); - self.raf_callback_list.borrow_mut().push((raf_id, Some(callback))); - raf_id - } else { - // WebVR spec: When a VRDisplay is not presenting it should - // fallback to window.requestAnimationFrame. - self.global().as_window().RequestAnimationFrame(callback) - } - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-cancelanimationframe - fn CancelAnimationFrame(&self, handle: u32) { - if self.presenting.get() { - let mut list = self.raf_callback_list.borrow_mut(); - if let Some(mut pair) = list.iter_mut().find(|pair| pair.0 == handle) { - pair.1 = None; - } - } else { - // WebVR spec: When a VRDisplay is not presenting it should - // fallback to window.cancelAnimationFrame. - self.global().as_window().CancelAnimationFrame(handle); - } - } - - #[allow(unrooted_must_root)] - // https://w3c.github.io/webvr/#dom-vrdisplay-requestpresent - fn RequestPresent(&self, layers: Vec<VRLayer>) -> Rc<Promise> { - let promise = Promise::new(&self.global()); - // TODO: WebVR spec: this method must be called in response to a user gesture - - // WebVR spec: If canPresent is false the promise MUST be rejected - if !self.display.borrow().capabilities.can_present { - let msg = "VRDisplay canPresent is false".to_string(); - promise.reject_native(promise.global().get_cx(), &msg); - return promise; - } - - // Current WebVRSpec only allows 1 VRLayer if the VRDevice can present. - // Future revisions of this spec may allow multiple layers to enable more complex rendering effects - // such as compositing WebGL and DOM elements together. - // That functionality is not allowed by this revision of the spec. - if layers.len() != 1 { - let msg = "The number of layers must be 1".to_string(); - promise.reject_native(promise.global().get_cx(), &msg); - return promise; - } - - // Parse and validate received VRLayer - let layer = validate_layer(self.global().get_cx(), &layers[0]); - - let layer_bounds; - let layer_ctx; - - match layer { - Ok((bounds, ctx)) => { - layer_bounds = bounds; - layer_ctx = ctx; - }, - Err(msg) => { - let msg = msg.to_string(); - promise.reject_native(promise.global().get_cx(), &msg); - return promise; - } - }; - - // WebVR spec: Repeat calls while already presenting will update the VRLayers being displayed. - if self.presenting.get() { - *self.layer.borrow_mut() = layer_bounds; - self.layer_ctx.set(Some(&layer_ctx)); - promise.resolve_native(promise.global().get_cx(), &()); - return promise; - } - - // Request Present - let (sender, receiver) = ipc::channel().unwrap(); - self.webvr_thread().send(WebVRMsg::RequestPresent(self.global().pipeline_id(), - self.display.borrow().display_id, - sender)) - .unwrap(); - match receiver.recv().unwrap() { - Ok(()) => { - *self.layer.borrow_mut() = layer_bounds; - self.layer_ctx.set(Some(&layer_ctx)); - self.init_present(); - promise.resolve_native(promise.global().get_cx(), &()); - }, - Err(e) => { - promise.reject_native(promise.global().get_cx(), &e); - } - } - - promise - } - - #[allow(unrooted_must_root)] - // https://w3c.github.io/webvr/#dom-vrdisplay-exitpresent - fn ExitPresent(&self) -> Rc<Promise> { - let promise = Promise::new(&self.global()); - - // WebVR spec: If the VRDisplay is not presenting the promise MUST be rejected. - if !self.presenting.get() { - let msg = "VRDisplay is not presenting".to_string(); - promise.reject_native(promise.global().get_cx(), &msg); - return promise; - } - - // Exit present - let (sender, receiver) = ipc::channel().unwrap(); - self.webvr_thread().send(WebVRMsg::ExitPresent(self.global().pipeline_id(), - self.display.borrow().display_id, - Some(sender))) - .unwrap(); - match receiver.recv().unwrap() { - Ok(()) => { - self.stop_present(); - promise.resolve_native(promise.global().get_cx(), &()); - }, - Err(e) => { - promise.reject_native(promise.global().get_cx(), &e); - } - } - - promise - } - - // https://w3c.github.io/webvr/#dom-vrdisplay-submitframe - fn SubmitFrame(&self) { - if !self.presenting.get() { - warn!("VRDisplay not presenting"); - return; - } - - let api_sender = self.layer_ctx.get().unwrap().ipc_renderer(); - let display_id = self.display.borrow().display_id as u64; - let layer = self.layer.borrow(); - let msg = VRCompositorCommand::SubmitFrame(display_id, layer.left_bounds, layer.right_bounds); - api_sender.send(CanvasMsg::WebVR(msg)).unwrap(); - } -} - -impl VRDisplay { - fn webvr_thread(&self) -> IpcSender<WebVRMsg> { - self.global().as_window().webvr_thread().expect("Shouldn't arrive here with WebVR disabled") - } - - pub fn update_display(&self, display: &WebVRDisplayData) { - *self.display.borrow_mut() = display.clone(); - if let Some(ref stage) = display.stage_parameters { - if self.stage_params.get().is_none() { - let params = Some(VRStageParameters::new(stage.clone(), &self.global())); - self.stage_params.set(params.as_ref().map(|v| v.deref())); - } else { - self.stage_params.get().unwrap().update(&stage); - } - } else { - self.stage_params.set(None); - } - } - - pub fn handle_webvr_event(&self, event: &WebVRDisplayEvent) { - match *event { - WebVRDisplayEvent::Connect(ref display) => { - self.update_display(&display); - }, - WebVRDisplayEvent::Disconnect(_id) => { - self.display.borrow_mut().connected = false; - }, - WebVRDisplayEvent::Activate(ref display, _) | - WebVRDisplayEvent::Deactivate(ref display, _) | - WebVRDisplayEvent::Blur(ref display) | - WebVRDisplayEvent::Focus(ref display) => { - self.update_display(&display); - self.notify_event(&event); - }, - WebVRDisplayEvent::PresentChange(ref display, presenting) => { - self.update_display(&display); - self.presenting.set(presenting); - self.notify_event(&event); - }, - WebVRDisplayEvent::Change(ref display) => { - // Change event doesn't exist in WebVR spec. - // So we update display data but don't notify JS. - self.update_display(&display); - } - }; - } - - fn notify_event(&self, event: &WebVRDisplayEvent) { - let root = Root::from_ref(&*self); - let event = VRDisplayEvent::new_from_webvr(&self.global(), &root, &event); - event.upcast::<Event>().fire(self.upcast()); - } - - fn init_present(&self) { - self.presenting.set(true); - let (sync_sender, sync_receiver) = ipc::channel().unwrap(); - *self.frame_data_receiver.borrow_mut() = Some(sync_receiver); - - let display_id = self.display.borrow().display_id as u64; - let api_sender = self.layer_ctx.get().unwrap().ipc_renderer(); - let js_sender = self.global().script_chan(); - let address = Trusted::new(&*self); - let near_init = self.depth_near.get(); - let far_init = self.depth_far.get(); - - // The render loop at native headset frame rate is implemented using a dedicated thread. - // Every loop iteration syncs pose data with the HMD, submits the pixels to the display and waits for Vsync. - // Both the requestAnimationFrame call of a VRDisplay in the JavaScript thread and the VRSyncPoses call - // in the Webrender thread are executed in parallel. This allows to get some JavaScript code executed ahead. - // while the render thread is syncing the VRFrameData to be used for the current frame. - // This thread runs until the user calls ExitPresent, the tab is closed or some unexpected error happened. - thread::Builder::new().name("WebVR_RAF".into()).spawn(move || { - let (raf_sender, raf_receiver) = mpsc::channel(); - let mut near = near_init; - let mut far = far_init; - - // Initialize compositor - api_sender.send(CanvasMsg::WebVR(VRCompositorCommand::Create(display_id))).unwrap(); - loop { - // Run RAF callbacks on JavaScript thread - let msg = box NotifyDisplayRAF { - address: address.clone(), - sender: raf_sender.clone() - }; - js_sender.send(CommonScriptMsg::RunnableMsg(WebVREvent, msg)).unwrap(); - - // Run Sync Poses in parallell on Render thread - let msg = VRCompositorCommand::SyncPoses(display_id, near, far, sync_sender.clone()); - api_sender.send(CanvasMsg::WebVR(msg)).unwrap(); - - // Wait until both SyncPoses & RAF ends - if let Ok(depth) = raf_receiver.recv().unwrap() { - near = depth.0; - far = depth.1; - } else { - // Stop thread - // ExitPresent called or some error happened - return; - } - } - }).expect("Thread spawning failed"); - } - - fn stop_present(&self) { - self.presenting.set(false); - *self.frame_data_receiver.borrow_mut() = None; - - let api_sender = self.layer_ctx.get().unwrap().ipc_renderer(); - let display_id = self.display.borrow().display_id as u64; - let msg = VRCompositorCommand::Release(display_id); - api_sender.send(CanvasMsg::WebVR(msg)).unwrap(); - } - - // Only called when the JSContext is destroyed while presenting. - // In this case we don't want to wait for WebVR Thread response. - fn force_stop_present(&self) { - self.webvr_thread().send(WebVRMsg::ExitPresent(self.global().pipeline_id(), - self.display.borrow().display_id, - None)) - .unwrap(); - self.stop_present(); - } - - fn sync_frame_data(&self) { - let status = if let Some(receiver) = self.frame_data_receiver.borrow().as_ref() { - match receiver.recv().unwrap() { - Ok(bytes) => { - *self.frame_data.borrow_mut() = WebVRFrameData::from_bytes(&bytes[..]); - VRFrameDataStatus::Synced - }, - Err(()) => { - VRFrameDataStatus::Exit - } - } - } else { - VRFrameDataStatus::Exit - }; - - self.frame_data_status.set(status); - } - - fn handle_raf(&self, end_sender: &mpsc::Sender<Result<(f64, f64), ()>>) { - self.frame_data_status.set(VRFrameDataStatus::Waiting); - self.running_display_raf.set(true); - - let mut callbacks = mem::replace(&mut *self.raf_callback_list.borrow_mut(), vec![]); - let now = self.global().as_window().Performance().Now(); - - // Call registered VRDisplay.requestAnimationFrame callbacks. - for (_, callback) in callbacks.drain(..) { - if let Some(callback) = callback { - let _ = callback.Call__(Finite::wrap(*now), ExceptionHandling::Report); - } - } - - self.running_display_raf.set(false); - if self.frame_data_status.get() == VRFrameDataStatus::Waiting { - // User didn't call getFrameData while presenting. - // We automatically reads the pending VRFrameData to avoid overflowing the IPC-Channel buffers. - // Show a warning as the WebVR Spec recommends. - warn!("WebVR: You should call GetFrameData while presenting"); - self.sync_frame_data(); - } - - match self.frame_data_status.get() { - VRFrameDataStatus::Synced => { - // Sync succeeded. Notify RAF thread. - end_sender.send(Ok((self.depth_near.get(), self.depth_far.get()))).unwrap(); - }, - VRFrameDataStatus::Exit | VRFrameDataStatus::Waiting => { - // ExitPresent called or some error ocurred. - // Notify VRDisplay RAF thread to stop. - end_sender.send(Err(())).unwrap(); - } - } - } -} - -struct NotifyDisplayRAF { - address: Trusted<VRDisplay>, - sender: mpsc::Sender<Result<(f64, f64), ()>> -} - -impl Runnable for NotifyDisplayRAF { - fn name(&self) -> &'static str { "NotifyDisplayRAF" } - - fn handler(self: Box<Self>) { - let display = self.address.root(); - display.handle_raf(&self.sender); - } -} - - -// WebVR Spect: If the number of values in the leftBounds/rightBounds arrays -// is not 0 or 4 for any of the passed layers the promise is rejected -fn parse_bounds(src: &Option<Vec<Finite<f32>>>, dst: &mut [f32; 4]) -> Result<(), &'static str> { - match *src { - Some(ref values) => { - if values.len() == 0 { - return Ok(()) - } - if values.len() != 4 { - return Err("The number of values in the leftBounds/rightBounds arrays must be 0 or 4") - } - for i in 0..4 { - dst[i] = *values[i].deref(); - } - Ok(()) - }, - None => Ok(()) - } -} - -fn validate_layer(cx: *mut JSContext, - layer: &VRLayer) - -> Result<(WebVRLayer, Root<WebGLRenderingContext>), &'static str> { - let ctx = layer.source.as_ref().map(|ref s| s.get_or_init_webgl_context(cx, None)).unwrap_or(None); - if let Some(ctx) = ctx { - let mut data = WebVRLayer::default(); - try!(parse_bounds(&layer.leftBounds, &mut data.left_bounds)); - try!(parse_bounds(&layer.rightBounds, &mut data.right_bounds)); - Ok((data, ctx)) - } else { - Err("VRLayer source must be a WebGL Context") - } -} diff --git a/components/script/dom/vrdisplaycapabilities.rs b/components/script/dom/vrdisplaycapabilities.rs deleted file mode 100644 index 349fd272140..00000000000 --- a/components/script/dom/vrdisplaycapabilities.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::VRDisplayCapabilitiesBinding; -use dom::bindings::codegen::Bindings::VRDisplayCapabilitiesBinding::VRDisplayCapabilitiesMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; -use dom_struct::dom_struct; -use webvr_traits::WebVRDisplayCapabilities; - -#[dom_struct] -pub struct VRDisplayCapabilities { - reflector_: Reflector, - #[ignore_heap_size_of = "Defined in rust-webvr"] - capabilities: DOMRefCell<WebVRDisplayCapabilities> -} - -unsafe_no_jsmanaged_fields!(WebVRDisplayCapabilities); - -impl VRDisplayCapabilities { - fn new_inherited(capabilities: WebVRDisplayCapabilities) -> VRDisplayCapabilities { - VRDisplayCapabilities { - reflector_: Reflector::new(), - capabilities: DOMRefCell::new(capabilities) - } - } - - pub fn new(capabilities: WebVRDisplayCapabilities, global: &GlobalScope) -> Root<VRDisplayCapabilities> { - reflect_dom_object(box VRDisplayCapabilities::new_inherited(capabilities), - global, - VRDisplayCapabilitiesBinding::Wrap) - } -} - -impl VRDisplayCapabilitiesMethods for VRDisplayCapabilities { - // https://w3c.github.io/webvr/#dom-vrdisplaycapabilities-hasposition - fn HasPosition(&self) -> bool { - self.capabilities.borrow().has_position - } - - // https://w3c.github.io/webvr/#dom-vrdisplaycapabilities-hasorientation - fn HasOrientation(&self) -> bool { - self.capabilities.borrow().has_orientation - } - - // https://w3c.github.io/webvr/#dom-vrdisplaycapabilities-hasexternaldisplay - fn HasExternalDisplay(&self) -> bool { - self.capabilities.borrow().has_external_display - } - - // https://w3c.github.io/webvr/#dom-vrdisplaycapabilities-canpresent - fn CanPresent(&self) -> bool { - self.capabilities.borrow().can_present - } - - // https://w3c.github.io/webvr/#dom-vrdisplaycapabilities-maxlayers - fn MaxLayers(&self) -> u32 { - if self.CanPresent() { 1 } else { 0 } - } -} diff --git a/components/script/dom/vrdisplayevent.rs b/components/script/dom/vrdisplayevent.rs deleted file mode 100644 index 956229ead4c..00000000000 --- a/components/script/dom/vrdisplayevent.rs +++ /dev/null @@ -1,117 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::VRDisplayEventBinding; -use dom::bindings::codegen::Bindings::VRDisplayEventBinding::VRDisplayEventMethods; -use dom::bindings::codegen::Bindings::VRDisplayEventBinding::VRDisplayEventReason; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::event::Event; -use dom::globalscope::GlobalScope; -use dom::vrdisplay::VRDisplay; -use dom::window::Window; -use dom_struct::dom_struct; -use servo_atoms::Atom; -use webvr_traits::{WebVRDisplayEvent, WebVRDisplayEventReason}; - -#[dom_struct] -pub struct VRDisplayEvent { - event: Event, - display: JS<VRDisplay>, - reason: Option<VRDisplayEventReason> -} - -impl VRDisplayEvent { - fn new_inherited(display: &VRDisplay, - reason: Option<VRDisplayEventReason>) - -> VRDisplayEvent { - VRDisplayEvent { - event: Event::new_inherited(), - display: JS::from_ref(display), - reason: reason.clone() - } - } - - pub fn new(global: &GlobalScope, - type_: Atom, - bubbles: bool, - cancelable: bool, - display: &VRDisplay, - reason: Option<VRDisplayEventReason>) - -> Root<VRDisplayEvent> { - let ev = reflect_dom_object(box VRDisplayEvent::new_inherited(&display, reason), - global, - VRDisplayEventBinding::Wrap); - { - let event = ev.upcast::<Event>(); - event.init_event(type_, bubbles, cancelable); - } - ev - } - - pub fn new_from_webvr(global: &GlobalScope, - display: &VRDisplay, - event: &WebVRDisplayEvent) - -> Root<VRDisplayEvent> { - let (name, reason) = match *event { - WebVRDisplayEvent::Connect(_) => ("displayconnect", None), - WebVRDisplayEvent::Disconnect(_) => ("displaydisconnect", None), - WebVRDisplayEvent::Activate(_, reason) => ("activate", Some(reason)), - WebVRDisplayEvent::Deactivate(_, reason) => ("deactivate", Some(reason)), - WebVRDisplayEvent::Blur(_) => ("blur", None), - WebVRDisplayEvent::Focus(_) => ("focus", None), - WebVRDisplayEvent::PresentChange(_, _) => ("presentchange", None), - WebVRDisplayEvent::Change(_) => panic!("VRDisplayEvent:Change event not available in WebVR") - }; - - // map to JS enum values - let reason = reason.map(|r| { - match r { - WebVRDisplayEventReason::Navigation => VRDisplayEventReason::Navigation, - WebVRDisplayEventReason::Mounted => VRDisplayEventReason::Mounted, - WebVRDisplayEventReason::Unmounted => VRDisplayEventReason::Unmounted, - } - }); - - VRDisplayEvent::new(&global, - Atom::from(DOMString::from(name)), - false, - false, - &display, - reason) - } - - pub fn Constructor(window: &Window, - type_: DOMString, - init: &VRDisplayEventBinding::VRDisplayEventInit) - -> Fallible<Root<VRDisplayEvent>> { - Ok(VRDisplayEvent::new(&window.global(), - Atom::from(type_), - init.parent.bubbles, - init.parent.cancelable, - &init.display, - init.reason)) - } -} - -impl VRDisplayEventMethods for VRDisplayEvent { - // https://w3c.github.io/webvr/#dom-vrdisplayevent-display - fn Display(&self) -> Root<VRDisplay> { - Root::from_ref(&*self.display) - } - - // https://w3c.github.io/webvr/#enumdef-vrdisplayeventreason - fn GetReason(&self) -> Option<VRDisplayEventReason> { - self.reason - } - - // https://dom.spec.whatwg.org/#dom-event-istrusted - fn IsTrusted(&self) -> bool { - self.event.IsTrusted() - } -} diff --git a/components/script/dom/vreyeparameters.rs b/components/script/dom/vreyeparameters.rs deleted file mode 100644 index 0c429fee78a..00000000000 --- a/components/script/dom/vreyeparameters.rs +++ /dev/null @@ -1,78 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use core::nonzero::NonZero; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::VREyeParametersBinding; -use dom::bindings::codegen::Bindings::VREyeParametersBinding::VREyeParametersMethods; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; -use dom::vrfieldofview::VRFieldOfView; -use dom_struct::dom_struct; -use js::jsapi::{Heap, JSContext, JSObject}; -use js::typedarray::{Float32Array, CreateWith}; -use std::default::Default; -use webvr_traits::WebVREyeParameters; - -#[dom_struct] -pub struct VREyeParameters { - reflector_: Reflector, - #[ignore_heap_size_of = "Defined in rust-webvr"] - parameters: DOMRefCell<WebVREyeParameters>, - offset: Heap<*mut JSObject>, - fov: JS<VRFieldOfView>, -} - -unsafe_no_jsmanaged_fields!(WebVREyeParameters); - -impl VREyeParameters { - #[allow(unsafe_code)] - #[allow(unrooted_must_root)] - fn new_inherited(parameters: WebVREyeParameters, global: &GlobalScope) -> VREyeParameters { - let fov = VRFieldOfView::new(&global, parameters.field_of_view.clone()); - let result = VREyeParameters { - reflector_: Reflector::new(), - parameters: DOMRefCell::new(parameters), - offset: Heap::default(), - fov: JS::from_ref(&*fov) - }; - - unsafe { - let _ = Float32Array::create(global.get_cx(), - CreateWith::Slice(&result.parameters.borrow().offset), - result.offset.handle_mut()); - } - result - } - - pub fn new(parameters: WebVREyeParameters, global: &GlobalScope) -> Root<VREyeParameters> { - reflect_dom_object(box VREyeParameters::new_inherited(parameters, global), - global, - VREyeParametersBinding::Wrap) - } -} - -impl VREyeParametersMethods for VREyeParameters { - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vreyeparameters-offset - unsafe fn Offset(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> { - NonZero::new(self.offset.get()) - } - - // https://w3c.github.io/webvr/#dom-vreyeparameters-fieldofview - fn FieldOfView(&self) -> Root<VRFieldOfView> { - Root::from_ref(&*self.fov) - } - - // https://w3c.github.io/webvr/#dom-vreyeparameters-renderwidth - fn RenderWidth(&self) -> u32 { - self.parameters.borrow().render_width - } - - // https://w3c.github.io/webvr/#dom-vreyeparameters-renderheight - fn RenderHeight(&self) -> u32 { - self.parameters.borrow().render_height - } -} diff --git a/components/script/dom/vrfieldofview.rs b/components/script/dom/vrfieldofview.rs deleted file mode 100644 index 80a2f767d81..00000000000 --- a/components/script/dom/vrfieldofview.rs +++ /dev/null @@ -1,59 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::VRFieldOfViewBinding; -use dom::bindings::codegen::Bindings::VRFieldOfViewBinding::VRFieldOfViewMethods; -use dom::bindings::js::Root; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; -use dom_struct::dom_struct; -use webvr_traits::WebVRFieldOfView; - -#[dom_struct] -pub struct VRFieldOfView { - reflector_: Reflector, - #[ignore_heap_size_of = "Defined in rust-webvr"] - fov: DOMRefCell<WebVRFieldOfView> -} - -unsafe_no_jsmanaged_fields!(WebVRFieldOfView); - -impl VRFieldOfView { - fn new_inherited(fov: WebVRFieldOfView) -> VRFieldOfView { - VRFieldOfView { - reflector_: Reflector::new(), - fov: DOMRefCell::new(fov) - } - } - - pub fn new(global: &GlobalScope, fov: WebVRFieldOfView) -> Root<VRFieldOfView> { - reflect_dom_object(box VRFieldOfView::new_inherited(fov), - global, - VRFieldOfViewBinding::Wrap) - } -} - -impl VRFieldOfViewMethods for VRFieldOfView { - // https://w3c.github.io/webvr/#interface-interface-vrfieldofview - fn UpDegrees(&self) -> Finite<f64> { - Finite::wrap(self.fov.borrow().up_degrees) - } - - // https://w3c.github.io/webvr/#interface-interface-vrfieldofview - fn RightDegrees(&self) -> Finite<f64> { - Finite::wrap(self.fov.borrow().right_degrees) - } - - // https://w3c.github.io/webvr/#interface-interface-vrfieldofview - fn DownDegrees(&self) -> Finite<f64> { - Finite::wrap(self.fov.borrow().down_degrees) - } - - // https://w3c.github.io/webvr/#interface-interface-vrfieldofview - fn LeftDegrees(&self) -> Finite<f64> { - Finite::wrap(self.fov.borrow().left_degrees) - } -} diff --git a/components/script/dom/vrframedata.rs b/components/script/dom/vrframedata.rs deleted file mode 100644 index 5b153a2a43e..00000000000 --- a/components/script/dom/vrframedata.rs +++ /dev/null @@ -1,143 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use core::nonzero::NonZero; -use dom::bindings::codegen::Bindings::VRFrameDataBinding; -use dom::bindings::codegen::Bindings::VRFrameDataBinding::VRFrameDataMethods; -use dom::bindings::error::Fallible; -use dom::bindings::js::{JS, Root}; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; -use dom::vrpose::VRPose; -use dom::window::Window; -use dom_struct::dom_struct; -use js::jsapi::{Heap, JSContext, JSObject}; -use js::typedarray::{Float32Array, CreateWith}; -use std::cell::Cell; -use webvr_traits::WebVRFrameData; - -#[dom_struct] -pub struct VRFrameData { - reflector_: Reflector, - left_proj: Heap<*mut JSObject>, - left_view: Heap<*mut JSObject>, - right_proj: Heap<*mut JSObject>, - right_view: Heap<*mut JSObject>, - pose: JS<VRPose>, - timestamp: Cell<f64>, - first_timestamp: Cell<f64> -} - -impl VRFrameData { - #[allow(unsafe_code)] - #[allow(unrooted_must_root)] - fn new(global: &GlobalScope) -> Root<VRFrameData> { - let matrix = [1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0f32]; - let pose = VRPose::new(&global, &Default::default()); - - let framedata = VRFrameData { - reflector_: Reflector::new(), - left_proj: Heap::default(), - left_view: Heap::default(), - right_proj: Heap::default(), - right_view: Heap::default(), - pose: JS::from_ref(&*pose), - timestamp: Cell::new(0.0), - first_timestamp: Cell::new(0.0) - }; - - let root = reflect_dom_object(box framedata, - global, - VRFrameDataBinding::Wrap); - - unsafe { - let ref framedata = *root; - let _ = Float32Array::create(global.get_cx(), CreateWith::Slice(&matrix), - framedata.left_proj.handle_mut()); - let _ = Float32Array::create(global.get_cx(), CreateWith::Slice(&matrix), - framedata.left_view.handle_mut()); - let _ = Float32Array::create(global.get_cx(), CreateWith::Slice(&matrix), - framedata.right_proj.handle_mut()); - let _ = Float32Array::create(global.get_cx(), CreateWith::Slice(&matrix), - framedata.right_view.handle_mut()); - } - - root - } - - pub fn Constructor(window: &Window) -> Fallible<Root<VRFrameData>> { - Ok(VRFrameData::new(&window.global())) - } -} - - -impl VRFrameData { - #[allow(unsafe_code)] - pub fn update(&self, data: &WebVRFrameData) { - unsafe { - let cx = self.global().get_cx(); - typedarray!(in(cx) let left_proj_array: Float32Array = self.left_proj.get()); - if let Ok(mut array) = left_proj_array { - array.update(&data.left_projection_matrix); - } - typedarray!(in(cx) let left_view_array: Float32Array = self.left_view.get()); - if let Ok(mut array) = left_view_array { - array.update(&data.left_view_matrix); - } - typedarray!(in(cx) let right_proj_array: Float32Array = self.right_proj.get()); - if let Ok(mut array) = right_proj_array { - array.update(&data.right_projection_matrix); - } - typedarray!(in(cx) let right_view_array: Float32Array = self.right_view.get()); - if let Ok(mut array) = right_view_array { - array.update(&data.right_view_matrix); - } - } - self.pose.update(&data.pose); - self.timestamp.set(data.timestamp); - if self.first_timestamp.get() == 0.0 { - self.first_timestamp.set(data.timestamp); - } - } -} - -impl VRFrameDataMethods for VRFrameData { - // https://w3c.github.io/webvr/#dom-vrframedata-timestamp - fn Timestamp(&self) -> Finite<f64> { - Finite::wrap(self.timestamp.get() - self.first_timestamp.get()) - } - - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrframedata-leftprojectionmatrix - unsafe fn LeftProjectionMatrix(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> { - NonZero::new(self.left_proj.get()) - } - - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrframedata-leftviewmatrix - unsafe fn LeftViewMatrix(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> { - NonZero::new(self.left_view.get()) - } - - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrframedata-rightprojectionmatrix - unsafe fn RightProjectionMatrix(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> { - NonZero::new(self.right_proj.get()) - } - - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrframedata-rightviewmatrix - unsafe fn RightViewMatrix(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> { - NonZero::new(self.right_view.get()) - } - - // https://w3c.github.io/webvr/#dom-vrframedata-pose - fn Pose(&self) -> Root<VRPose> { - Root::from_ref(&*self.pose) - } -} diff --git a/components/script/dom/vrpose.rs b/components/script/dom/vrpose.rs deleted file mode 100644 index ee035e2a0f1..00000000000 --- a/components/script/dom/vrpose.rs +++ /dev/null @@ -1,137 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use core::nonzero::NonZero; -use dom::bindings::codegen::Bindings::VRPoseBinding; -use dom::bindings::codegen::Bindings::VRPoseBinding::VRPoseMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; -use dom_struct::dom_struct; -use js::jsapi::{Heap, JSContext, JSObject}; -use js::typedarray::{Float32Array, CreateWith}; -use std::ptr; -use webvr_traits::webvr; - -#[dom_struct] -pub struct VRPose { - reflector_: Reflector, - position: Heap<*mut JSObject>, - orientation: Heap<*mut JSObject>, - linear_vel: Heap<*mut JSObject>, - angular_vel: Heap<*mut JSObject>, - linear_acc: Heap<*mut JSObject>, - angular_acc: Heap<*mut JSObject>, -} - -#[allow(unsafe_code)] -unsafe fn update_or_create_typed_array(cx: *mut JSContext, - src: Option<&[f32]>, - dst: &Heap<*mut JSObject>) { - match src { - Some(data) => { - if dst.get().is_null() { - rooted!(in (cx) let mut array = ptr::null_mut()); - let _ = Float32Array::create(cx, CreateWith::Slice(data), array.handle_mut()); - (*dst).set(array.get()); - } else { - typedarray!(in(cx) let array: Float32Array = dst.get()); - if let Ok(mut array) = array { - array.update(data); - } - } - }, - None => { - if !dst.get().is_null() { - dst.set(ptr::null_mut()); - } - } - } -} - -#[inline] -#[allow(unsafe_code)] -fn heap_to_option(heap: &Heap<*mut JSObject>) -> Option<NonZero<*mut JSObject>> { - let js_object = heap.get(); - if js_object.is_null() { - None - } else { - unsafe { - Some(NonZero::new(js_object)) - } - } -} - -impl VRPose { - fn new_inherited() -> VRPose { - VRPose { - reflector_: Reflector::new(), - position: Heap::default(), - orientation: Heap::default(), - linear_vel: Heap::default(), - angular_vel: Heap::default(), - linear_acc: Heap::default(), - angular_acc: Heap::default(), - } - } - - pub fn new(global: &GlobalScope, pose: &webvr::VRPose) -> Root<VRPose> { - let root = reflect_dom_object(box VRPose::new_inherited(), - global, - VRPoseBinding::Wrap); - root.update(&pose); - root - } - - #[allow(unsafe_code)] - pub fn update(&self, pose: &webvr::VRPose) { - let cx = self.global().get_cx(); - unsafe { - update_or_create_typed_array(cx, pose.position.as_ref().map(|v| &v[..]), &self.position); - update_or_create_typed_array(cx, pose.orientation.as_ref().map(|v| &v[..]), &self.orientation); - update_or_create_typed_array(cx, pose.linear_velocity.as_ref().map(|v| &v[..]), &self.linear_vel); - update_or_create_typed_array(cx, pose.angular_velocity.as_ref().map(|v| &v[..]), &self.angular_vel); - update_or_create_typed_array(cx, pose.linear_acceleration.as_ref().map(|v| &v[..]), &self.linear_acc); - update_or_create_typed_array(cx, pose.angular_acceleration.as_ref().map(|v| &v[..]), &self.angular_acc); - } - } -} - -impl VRPoseMethods for VRPose { - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrpose-position - unsafe fn GetPosition(&self, _cx: *mut JSContext) -> Option<NonZero<*mut JSObject>> { - heap_to_option(&self.position) - } - - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrpose-linearvelocity - unsafe fn GetLinearVelocity(&self, _cx: *mut JSContext) -> Option<NonZero<*mut JSObject>> { - heap_to_option(&self.linear_vel) - } - - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrpose-linearacceleration - unsafe fn GetLinearAcceleration(&self, _cx: *mut JSContext) -> Option<NonZero<*mut JSObject>> { - heap_to_option(&self.linear_acc) - } - - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrpose-orientation - unsafe fn GetOrientation(&self, _cx: *mut JSContext) -> Option<NonZero<*mut JSObject>> { - heap_to_option(&self.orientation) - } - - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrpose-angularvelocity - unsafe fn GetAngularVelocity(&self, _cx: *mut JSContext) -> Option<NonZero<*mut JSObject>> { - heap_to_option(&self.angular_vel) - } - - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrpose-angularacceleration - unsafe fn GetAngularAcceleration(&self, _cx: *mut JSContext) -> Option<NonZero<*mut JSObject>> { - heap_to_option(&self.angular_acc) - } -} diff --git a/components/script/dom/vrstageparameters.rs b/components/script/dom/vrstageparameters.rs deleted file mode 100644 index 0bc319466c3..00000000000 --- a/components/script/dom/vrstageparameters.rs +++ /dev/null @@ -1,82 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -use core::nonzero::NonZero; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::VRStageParametersBinding; -use dom::bindings::codegen::Bindings::VRStageParametersBinding::VRStageParametersMethods; -use dom::bindings::js::Root; -use dom::bindings::num::Finite; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::globalscope::GlobalScope; -use dom_struct::dom_struct; -use js::jsapi::{Heap, JSContext, JSObject}; -use js::typedarray::{Float32Array, CreateWith}; -use webvr_traits::WebVRStageParameters; - -#[dom_struct] -pub struct VRStageParameters { - reflector_: Reflector, - #[ignore_heap_size_of = "Defined in rust-webvr"] - parameters: DOMRefCell<WebVRStageParameters>, - transform: Heap<*mut JSObject>, -} - -unsafe_no_jsmanaged_fields!(WebVRStageParameters); - -impl VRStageParameters { - #[allow(unsafe_code)] - #[allow(unrooted_must_root)] - fn new_inherited(parameters: WebVRStageParameters, global: &GlobalScope) -> VRStageParameters { - let stage = VRStageParameters { - reflector_: Reflector::new(), - parameters: DOMRefCell::new(parameters), - transform: Heap::default() - }; - // XXX unsound! - unsafe { - let _ = Float32Array::create(global.get_cx(), - CreateWith::Slice(&stage.parameters.borrow().sitting_to_standing_transform), - stage.transform.handle_mut()); - } - - stage - } - - pub fn new(parameters: WebVRStageParameters, global: &GlobalScope) -> Root<VRStageParameters> { - reflect_dom_object(box VRStageParameters::new_inherited(parameters, global), - global, - VRStageParametersBinding::Wrap) - } - - #[allow(unsafe_code)] - pub fn update(&self, parameters: &WebVRStageParameters) { - unsafe { - let cx = self.global().get_cx(); - typedarray!(in(cx) let array: Float32Array = self.transform.get()); - if let Ok(mut array) = array { - array.update(¶meters.sitting_to_standing_transform); - } - } - *self.parameters.borrow_mut() = parameters.clone(); - } -} - -impl VRStageParametersMethods for VRStageParameters { - #[allow(unsafe_code)] - // https://w3c.github.io/webvr/#dom-vrstageparameters-sittingtostandingtransform - unsafe fn SittingToStandingTransform(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> { - NonZero::new(self.transform.get()) - } - - // https://w3c.github.io/webvr/#dom-vrstageparameters-sizex - fn SizeX(&self) -> Finite<f32> { - Finite::wrap(self.parameters.borrow().size_x) - } - - // https://w3c.github.io/webvr/#dom-vrstageparameters-sizez - fn SizeZ(&self) -> Finite<f32> { - Finite::wrap(self.parameters.borrow().size_z) - } -} diff --git a/components/script/dom/vttcue.rs b/components/script/dom/vttcue.rs new file mode 100644 index 00000000000..28c9cd22e33 --- /dev/null +++ b/components/script/dom/vttcue.rs @@ -0,0 +1,231 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::VTTCueBinding::{ + self, AlignSetting, AutoKeyword, DirectionSetting, LineAlignSetting, PositionAlignSetting, + VTTCueMethods, +}; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::globalscope::GlobalScope; +use crate::dom::texttrackcue::TextTrackCue; +use crate::dom::vttregion::VTTRegion; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct VTTCue { + texttrackcue: TextTrackCue, + region: DomRefCell<Option<Dom<VTTRegion>>>, + vertical: Cell<DirectionSetting>, + snap_to_lines: Cell<bool>, + line: DomRefCell<LineAndPositionSetting>, + line_align: Cell<LineAlignSetting>, + position: DomRefCell<LineAndPositionSetting>, + position_align: Cell<PositionAlignSetting>, + size: Cell<f64>, + align: Cell<AlignSetting>, + text: DomRefCell<DOMString>, +} + +impl VTTCue { + pub fn new_inherited(start_time: f64, end_time: f64, text: DOMString) -> Self { + VTTCue { + texttrackcue: TextTrackCue::new_inherited( + DOMString::default(), + start_time, + end_time, + None, + ), + region: DomRefCell::new(None), + vertical: Cell::new(DirectionSetting::default()), + snap_to_lines: Cell::new(true), + line: DomRefCell::new(LineAndPositionSetting::Auto), + line_align: Cell::new(LineAlignSetting::Start), + position: DomRefCell::new(LineAndPositionSetting::Auto), + position_align: Cell::new(PositionAlignSetting::Auto), + size: Cell::new(100_f64), + align: Cell::new(AlignSetting::Center), + text: DomRefCell::new(text), + } + } + + pub fn new( + global: &GlobalScope, + start_time: f64, + end_time: f64, + text: DOMString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(Self::new_inherited(start_time, end_time, text)), + global, + ) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + start_time: Finite<f64>, + end_time: Finite<f64>, + text: DOMString, + ) -> DomRoot<Self> { + VTTCue::new(&window.global(), *start_time, *end_time, text) + } +} + +impl VTTCueMethods for VTTCue { + // https://w3c.github.io/webvtt/#dom-vttcue-region + fn GetRegion(&self) -> Option<DomRoot<VTTRegion>> { + self.region + .borrow() + .as_ref() + .map(|r| DomRoot::from_ref(&**r)) + } + + // https://w3c.github.io/webvtt/#dom-vttcue-region + fn SetRegion(&self, value: Option<&VTTRegion>) { + *self.region.borrow_mut() = value.map(|r| Dom::from_ref(r)) + } + + // https://w3c.github.io/webvtt/#dom-vttcue-vertical + fn Vertical(&self) -> DirectionSetting { + self.vertical.get() + } + + // https://w3c.github.io/webvtt/#dom-vttcue-vertical + fn SetVertical(&self, value: DirectionSetting) { + self.vertical.set(value); + } + + // https://w3c.github.io/webvtt/#dom-vttcue-snaptolines + fn SnapToLines(&self) -> bool { + self.snap_to_lines.get() + } + + // https://w3c.github.io/webvtt/#dom-vttcue-snaptolines + fn SetSnapToLines(&self, value: bool) { + self.snap_to_lines.set(value) + } + + // https://w3c.github.io/webvtt/#dom-vttcue-line + fn Line(&self) -> VTTCueBinding::LineAndPositionSetting { + VTTCueBinding::LineAndPositionSetting::from(self.line.borrow().clone()) + } + + // https://w3c.github.io/webvtt/#dom-vttcue-line + fn SetLine(&self, value: VTTCueBinding::LineAndPositionSetting) { + *self.line.borrow_mut() = value.into(); + } + + // https://w3c.github.io/webvtt/#dom-vttcue-linealign + fn LineAlign(&self) -> LineAlignSetting { + self.line_align.get() + } + + // https://w3c.github.io/webvtt/#dom-vttcue-linealign + fn SetLineAlign(&self, value: LineAlignSetting) { + self.line_align.set(value); + } + + // https://w3c.github.io/webvtt/#dom-vttcue-position + fn Position(&self) -> VTTCueBinding::LineAndPositionSetting { + VTTCueBinding::LineAndPositionSetting::from(self.position.borrow().clone()) + } + + // https://w3c.github.io/webvtt/#dom-vttcue-position + fn SetPosition(&self, value: VTTCueBinding::LineAndPositionSetting) -> ErrorResult { + if let VTTCueBinding::LineAndPositionSetting::Double(x) = value { + if *x < 0_f64 || *x > 100_f64 { + return Err(Error::IndexSize); + } + } + + *self.position.borrow_mut() = value.into(); + Ok(()) + } + + // https://w3c.github.io/webvtt/#dom-vttcue-positionalign + fn PositionAlign(&self) -> PositionAlignSetting { + self.position_align.get() + } + + // https://w3c.github.io/webvtt/#dom-vttcue-positionalign + fn SetPositionAlign(&self, value: PositionAlignSetting) { + self.position_align.set(value); + } + + // https://w3c.github.io/webvtt/#dom-vttcue-size + fn Size(&self) -> Finite<f64> { + Finite::wrap(self.size.get()) + } + + // https://w3c.github.io/webvtt/#dom-vttcue-size + fn SetSize(&self, value: Finite<f64>) -> ErrorResult { + if *value < 0_f64 || *value > 100_f64 { + return Err(Error::IndexSize); + } + + self.size.set(*value); + Ok(()) + } + + // https://w3c.github.io/webvtt/#dom-vttcue-align + fn Align(&self) -> AlignSetting { + self.align.get() + } + + // https://w3c.github.io/webvtt/#dom-vttcue-align + fn SetAlign(&self, value: AlignSetting) { + self.align.set(value); + } + + // https://w3c.github.io/webvtt/#dom-vttcue-text + fn Text(&self) -> DOMString { + self.text.borrow().clone() + } + + // https://w3c.github.io/webvtt/#dom-vttcue-text + fn SetText(&self, value: DOMString) { + *self.text.borrow_mut() = value; + } + + // https://w3c.github.io/webvtt/#dom-vttcue-getcueashtml + fn GetCueAsHTML(&self) -> DomRoot<DocumentFragment> { + todo!() + } +} + +#[derive(Clone, JSTraceable, MallocSizeOf)] +enum LineAndPositionSetting { + Double(f64), + Auto, +} + +impl From<VTTCueBinding::LineAndPositionSetting> for LineAndPositionSetting { + fn from(value: VTTCueBinding::LineAndPositionSetting) -> Self { + match value { + VTTCueBinding::LineAndPositionSetting::Double(x) => LineAndPositionSetting::Double(*x), + VTTCueBinding::LineAndPositionSetting::AutoKeyword(_) => LineAndPositionSetting::Auto, + } + } +} + +impl From<LineAndPositionSetting> for VTTCueBinding::LineAndPositionSetting { + fn from(value: LineAndPositionSetting) -> Self { + match value { + LineAndPositionSetting::Double(x) => { + VTTCueBinding::LineAndPositionSetting::Double(Finite::wrap(x)) + }, + LineAndPositionSetting::Auto => { + VTTCueBinding::LineAndPositionSetting::AutoKeyword(AutoKeyword::Auto) + }, + } + } +} diff --git a/components/script/dom/vttregion.rs b/components/script/dom/vttregion.rs new file mode 100644 index 00000000000..987af730f14 --- /dev/null +++ b/components/script/dom/vttregion.rs @@ -0,0 +1,161 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::VTTRegionBinding::{ScrollSetting, VTTRegionMethods}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct VTTRegion { + reflector_: Reflector, + id: DomRefCell<DOMString>, + width: Cell<f64>, + lines: Cell<u32>, + region_anchor_x: Cell<f64>, + region_anchor_y: Cell<f64>, + viewport_anchor_x: Cell<f64>, + viewport_anchor_y: Cell<f64>, + scroll: Cell<ScrollSetting>, +} + +impl VTTRegion { + pub fn new_inherited() -> Self { + VTTRegion { + reflector_: Reflector::new(), + id: DomRefCell::new(DOMString::default()), + width: Cell::new(100_f64), + lines: Cell::new(3), + region_anchor_x: Cell::new(0_f64), + region_anchor_y: Cell::new(100_f64), + viewport_anchor_x: Cell::new(0_f64), + viewport_anchor_y: Cell::new(100_f64), + scroll: Cell::new(Default::default()), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<Self> { + reflect_dom_object(Box::new(Self::new_inherited()), global) + } + + #[allow(non_snake_case)] + pub fn Constructor(window: &Window) -> Fallible<DomRoot<Self>> { + Ok(VTTRegion::new(&window.global())) + } +} + +impl VTTRegionMethods for VTTRegion { + // https://w3c.github.io/webvtt/#dom-vttregion-id + fn Id(&self) -> DOMString { + self.id.borrow().clone() + } + + // https://w3c.github.io/webvtt/#dom-vttregion-id + fn SetId(&self, value: DOMString) { + *self.id.borrow_mut() = value; + } + + // https://w3c.github.io/webvtt/#dom-vttregion-width + fn Width(&self) -> Finite<f64> { + Finite::wrap(self.width.get()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-width + fn SetWidth(&self, value: Finite<f64>) -> ErrorResult { + if *value < 0_f64 || *value > 100_f64 { + return Err(Error::IndexSize); + } + + self.width.set(*value); + Ok(()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-lines + fn Lines(&self) -> u32 { + self.lines.get() + } + + // https://w3c.github.io/webvtt/#dom-vttregion-lines + fn SetLines(&self, value: u32) -> ErrorResult { + self.lines.set(value); + Ok(()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-regionanchorx + fn RegionAnchorX(&self) -> Finite<f64> { + Finite::wrap(self.region_anchor_x.get()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-regionanchorx + fn SetRegionAnchorX(&self, value: Finite<f64>) -> ErrorResult { + if *value < 0_f64 || *value > 100_f64 { + return Err(Error::IndexSize); + } + + self.region_anchor_x.set(*value); + Ok(()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-regionanchory + fn RegionAnchorY(&self) -> Finite<f64> { + Finite::wrap(self.region_anchor_y.get()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-regionanchory + fn SetRegionAnchorY(&self, value: Finite<f64>) -> ErrorResult { + if *value < 0_f64 || *value > 100_f64 { + return Err(Error::IndexSize); + } + + self.region_anchor_y.set(*value); + Ok(()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-viewportanchorx + fn ViewportAnchorX(&self) -> Finite<f64> { + Finite::wrap(self.viewport_anchor_x.get()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-viewportanchorx + fn SetViewportAnchorX(&self, value: Finite<f64>) -> ErrorResult { + if *value < 0_f64 || *value > 100_f64 { + return Err(Error::IndexSize); + } + + self.viewport_anchor_x.set(*value); + Ok(()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-viewportanchory + fn ViewportAnchorY(&self) -> Finite<f64> { + Finite::wrap(self.viewport_anchor_y.get()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-viewportanchory + fn SetViewportAnchorY(&self, value: Finite<f64>) -> ErrorResult { + if *value < 0_f64 || *value > 100_f64 { + return Err(Error::IndexSize); + } + + self.viewport_anchor_y.set(*value); + Ok(()) + } + + // https://w3c.github.io/webvtt/#dom-vttregion-scroll + fn Scroll(&self) -> ScrollSetting { + self.scroll.get() + } + + // https://w3c.github.io/webvtt/#dom-vttregion-scroll + fn SetScroll(&self, value: ScrollSetting) { + self.scroll.set(value); + } +} diff --git a/components/script/dom/webgl2renderingcontext.rs b/components/script/dom/webgl2renderingcontext.rs new file mode 100644 index 00000000000..b438056e622 --- /dev/null +++ b/components/script/dom/webgl2renderingcontext.rs @@ -0,0 +1,4472 @@ +/* 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 crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextMethods; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods; +use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer; +use crate::dom::bindings::codegen::UnionTypes::Float32ArrayOrUnrestrictedFloatSequence; +use crate::dom::bindings::codegen::UnionTypes::ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement; +use crate::dom::bindings::codegen::UnionTypes::Int32ArrayOrLongSequence; +use crate::dom::bindings::codegen::UnionTypes::Uint32ArrayOrUnsignedLongSequence; +use crate::dom::bindings::error::{ErrorResult, Fallible}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutCanvasRenderingContextHelpers}; +use crate::dom::webgl_validations::tex_image_2d::{ + TexImage2DValidator, TexImage2DValidatorResult, TexStorageValidator, TexStorageValidatorResult, +}; +use crate::dom::webgl_validations::WebGLValidator; +use crate::dom::webglactiveinfo::WebGLActiveInfo; +use crate::dom::webglbuffer::WebGLBuffer; +use crate::dom::webglframebuffer::{WebGLFramebuffer, WebGLFramebufferAttachmentRoot}; +use crate::dom::webglprogram::WebGLProgram; +use crate::dom::webglquery::WebGLQuery; +use crate::dom::webglrenderbuffer::WebGLRenderbuffer; +use crate::dom::webglrenderingcontext::{ + uniform_get, uniform_typed, Operation, TexPixels, TexSource, VertexAttrib, + WebGLRenderingContext, +}; +use crate::dom::webglsampler::{WebGLSampler, WebGLSamplerValue}; +use crate::dom::webglshader::WebGLShader; +use crate::dom::webglshaderprecisionformat::WebGLShaderPrecisionFormat; +use crate::dom::webglsync::WebGLSync; +use crate::dom::webgltexture::WebGLTexture; +use crate::dom::webgltransformfeedback::WebGLTransformFeedback; +use crate::dom::webgluniformlocation::WebGLUniformLocation; +use crate::dom::webglvertexarrayobject::WebGLVertexArrayObject; +use crate::dom::window::Window; +use crate::js::conversions::ToJSValConvertible; +use crate::script_runtime::JSContext; +use canvas_traits::webgl::WebGLError::*; +use canvas_traits::webgl::{ + webgl_channel, GLContextAttributes, InternalFormatParameter, WebGLCommand, WebGLResult, + WebGLVersion, +}; +use dom_struct::dom_struct; +use euclid::default::{Point2D, Rect, Size2D}; +use ipc_channel::ipc::{self, IpcSharedMemory}; +use js::jsapi::{JSObject, Type}; +use js::jsval::{BooleanValue, DoubleValue, Int32Value, UInt32Value}; +use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue}; +use js::rust::{CustomAutoRooterGuard, HandleObject}; +use js::typedarray::{ArrayBufferView, CreateWith, Float32, Int32Array, Uint32, Uint32Array}; +use script_layout_interface::HTMLCanvasDataSource; +use servo_config::pref; +use std::cell::Cell; +use std::cmp; +use std::ptr::{self, NonNull}; +use url::Host; + +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +struct IndexedBinding { + buffer: MutNullableDom<WebGLBuffer>, + start: Cell<i64>, + size: Cell<i64>, +} + +impl IndexedBinding { + fn new() -> IndexedBinding { + IndexedBinding { + buffer: MutNullableDom::new(None), + start: Cell::new(0), + size: Cell::new(0), + } + } +} + +#[dom_struct] +pub struct WebGL2RenderingContext { + reflector_: Reflector, + base: Dom<WebGLRenderingContext>, + occlusion_query: MutNullableDom<WebGLQuery>, + primitives_query: MutNullableDom<WebGLQuery>, + samplers: Box<[MutNullableDom<WebGLSampler>]>, + bound_copy_read_buffer: MutNullableDom<WebGLBuffer>, + bound_copy_write_buffer: MutNullableDom<WebGLBuffer>, + bound_pixel_pack_buffer: MutNullableDom<WebGLBuffer>, + bound_pixel_unpack_buffer: MutNullableDom<WebGLBuffer>, + bound_transform_feedback_buffer: MutNullableDom<WebGLBuffer>, + bound_uniform_buffer: MutNullableDom<WebGLBuffer>, + indexed_uniform_buffer_bindings: Box<[IndexedBinding]>, + indexed_transform_feedback_buffer_bindings: Box<[IndexedBinding]>, + current_transform_feedback: MutNullableDom<WebGLTransformFeedback>, + texture_pack_row_length: Cell<usize>, + texture_pack_skip_pixels: Cell<usize>, + texture_pack_skip_rows: Cell<usize>, + enable_rasterizer_discard: Cell<bool>, + default_fb_readbuffer: Cell<u32>, + default_fb_drawbuffer: Cell<u32>, +} + +fn typedarray_elem_size(typeid: Type) -> usize { + match typeid { + Type::Int8 | Type::Uint8 | Type::Uint8Clamped => 1, + Type::Int16 | Type::Uint16 => 2, + Type::Int32 | Type::Uint32 | Type::Float32 => 4, + Type::Int64 | Type::Float64 => 8, + Type::BigInt64 | Type::BigUint64 => 8, + Type::Simd128 | Type::MaxTypedArrayViewType => unreachable!(), + } +} + +struct ReadPixelsAllowedFormats<'a> { + array_types: &'a [Type], + channels: usize, +} + +struct ReadPixelsSizes { + row_stride: usize, + skipped_bytes: usize, + size: usize, +} + +impl WebGL2RenderingContext { + fn new_inherited( + window: &Window, + canvas: &HTMLCanvasElement, + size: Size2D<u32>, + attrs: GLContextAttributes, + ) -> Option<WebGL2RenderingContext> { + let base = WebGLRenderingContext::new(window, canvas, WebGLVersion::WebGL2, size, attrs)?; + + let samplers = (0..base.limits().max_combined_texture_image_units) + .map(|_| Default::default()) + .collect::<Vec<_>>() + .into(); + let indexed_uniform_buffer_bindings = (0..base.limits().max_uniform_buffer_bindings) + .map(|_| IndexedBinding::new()) + .collect::<Vec<_>>() + .into(); + let indexed_transform_feedback_buffer_bindings = + (0..base.limits().max_transform_feedback_separate_attribs) + .map(|_| IndexedBinding::new()) + .collect::<Vec<_>>() + .into(); + + Some(WebGL2RenderingContext { + reflector_: Reflector::new(), + base: Dom::from_ref(&*base), + occlusion_query: MutNullableDom::new(None), + primitives_query: MutNullableDom::new(None), + samplers: samplers, + bound_copy_read_buffer: MutNullableDom::new(None), + bound_copy_write_buffer: MutNullableDom::new(None), + bound_pixel_pack_buffer: MutNullableDom::new(None), + bound_pixel_unpack_buffer: MutNullableDom::new(None), + bound_transform_feedback_buffer: MutNullableDom::new(None), + bound_uniform_buffer: MutNullableDom::new(None), + indexed_uniform_buffer_bindings, + indexed_transform_feedback_buffer_bindings, + current_transform_feedback: MutNullableDom::new(None), + texture_pack_row_length: Cell::new(0), + texture_pack_skip_pixels: Cell::new(0), + texture_pack_skip_rows: Cell::new(0), + enable_rasterizer_discard: Cell::new(false), + default_fb_readbuffer: Cell::new(constants::BACK), + default_fb_drawbuffer: Cell::new(constants::BACK), + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + canvas: &HTMLCanvasElement, + size: Size2D<u32>, + attrs: GLContextAttributes, + ) -> Option<DomRoot<WebGL2RenderingContext>> { + WebGL2RenderingContext::new_inherited(window, canvas, size, attrs) + .map(|ctx| reflect_dom_object(Box::new(ctx), window)) + } + + #[allow(unsafe_code)] + pub fn is_webgl2_enabled(_cx: JSContext, global: HandleObject) -> bool { + if pref!(dom.webgl2.enabled) { + return true; + } + + let global = unsafe { GlobalScope::from_object(global.get()) }; + let origin = global.origin(); + let host = origin.host(); + WEBGL2_ORIGINS + .iter() + .any(|origin| host == Host::parse(origin).ok().as_ref()) + } +} + +/// List of domains for which WebGL 2 is enabled automatically, regardless +/// of the status of the dom.webgl2.enabled preference. +static WEBGL2_ORIGINS: &[&str] = &["www.servoexperiments.com"]; + +impl WebGL2RenderingContext { + pub fn recreate(&self, size: Size2D<u32>) { + self.base.recreate(size) + } + + pub fn current_vao(&self) -> DomRoot<WebGLVertexArrayObject> { + self.base.current_vao_webgl2() + } + + pub fn validate_uniform_block_for_draw(&self) { + let program = match self.base.current_program() { + Some(program) => program, + None => return, + }; + for uniform_block in program.active_uniform_blocks().iter() { + let data_size = uniform_block.size as usize; + for block in program.active_uniforms().iter() { + let index = match block.bind_index { + Some(index) => index, + None => continue, + }; + let indexed = &self.indexed_uniform_buffer_bindings[index as usize]; + let buffer = match indexed.buffer.get() { + Some(buffer) => buffer, + None => { + self.base.webgl_error(InvalidOperation); + return; + }, + }; + if indexed.size.get() == 0 { + if data_size > buffer.capacity() { + self.base.webgl_error(InvalidOperation); + return; + } + } else { + let start = indexed.start.get() as usize; + let mut size = indexed.size.get() as usize; + if start >= size { + self.base.webgl_error(InvalidOperation); + return; + } + size -= start; + if data_size > size { + self.base.webgl_error(InvalidOperation); + return; + } + } + } + } + } + + fn validate_vertex_attribs_for_draw(&self) { + let program = match self.base.current_program() { + Some(program) => program, + None => return, + }; + let groups = [ + [ + constants::INT, + constants::INT_VEC2, + constants::INT_VEC3, + constants::INT_VEC4, + ], + [ + constants::UNSIGNED_INT, + constants::UNSIGNED_INT_VEC2, + constants::UNSIGNED_INT_VEC3, + constants::UNSIGNED_INT_VEC4, + ], + [ + constants::FLOAT, + constants::FLOAT_VEC2, + constants::FLOAT_VEC3, + constants::FLOAT_VEC4, + ], + ]; + let vao = self.current_vao(); + for prog_attrib in program.active_attribs().iter() { + let attrib = handle_potential_webgl_error!( + self.base, + vao.get_vertex_attrib(prog_attrib.location as u32) + .ok_or(InvalidOperation), + return + ); + + let current_vertex_attrib = + self.base.current_vertex_attribs()[prog_attrib.location as usize]; + let attrib_data_base_type = if !attrib.enabled_as_array { + match current_vertex_attrib { + VertexAttrib::Int(_, _, _, _) => constants::INT, + VertexAttrib::Uint(_, _, _, _) => constants::UNSIGNED_INT, + VertexAttrib::Float(_, _, _, _) => constants::FLOAT, + } + } else { + attrib.type_ + }; + + let contains = groups + .iter() + .find(|g| g.contains(&attrib_data_base_type) && g.contains(&prog_attrib.type_)); + if contains.is_none() { + self.base.webgl_error(InvalidOperation); + return; + } + } + } + + pub fn base_context(&self) -> DomRoot<WebGLRenderingContext> { + DomRoot::from_ref(&*self.base) + } + + fn bound_buffer(&self, target: u32) -> WebGLResult<Option<DomRoot<WebGLBuffer>>> { + match target { + constants::COPY_READ_BUFFER => Ok(self.bound_copy_read_buffer.get()), + constants::COPY_WRITE_BUFFER => Ok(self.bound_copy_write_buffer.get()), + constants::PIXEL_PACK_BUFFER => Ok(self.bound_pixel_pack_buffer.get()), + constants::PIXEL_UNPACK_BUFFER => Ok(self.bound_pixel_unpack_buffer.get()), + constants::TRANSFORM_FEEDBACK_BUFFER => Ok(self.bound_transform_feedback_buffer.get()), + constants::UNIFORM_BUFFER => Ok(self.bound_uniform_buffer.get()), + constants::ELEMENT_ARRAY_BUFFER => Ok(self.current_vao().element_array_buffer().get()), + _ => self.base.bound_buffer(target), + } + } + + pub fn buffer_usage(&self, usage: u32) -> WebGLResult<u32> { + match usage { + constants::STATIC_READ | + constants::DYNAMIC_READ | + constants::STREAM_READ | + constants::STATIC_COPY | + constants::DYNAMIC_COPY | + constants::STREAM_COPY => Ok(usage), + _ => self.base.buffer_usage(usage), + } + } + + fn unbind_from(&self, slot: &MutNullableDom<WebGLBuffer>, buffer: &WebGLBuffer) { + if slot.get().map_or(false, |b| buffer == &*b) { + buffer.decrement_attached_counter(Operation::Infallible); + slot.set(None); + } + } + + fn calc_read_pixel_formats( + &self, + pixel_type: u32, + format: u32, + ) -> WebGLResult<ReadPixelsAllowedFormats> { + let array_types = match pixel_type { + constants::BYTE => &[Type::Int8][..], + constants::SHORT => &[Type::Int16][..], + constants::INT => &[Type::Int32][..], + constants::UNSIGNED_BYTE => &[Type::Uint8, Type::Uint8Clamped][..], + constants::UNSIGNED_SHORT | + constants::UNSIGNED_SHORT_4_4_4_4 | + constants::UNSIGNED_SHORT_5_5_5_1 | + constants::UNSIGNED_SHORT_5_6_5 => &[Type::Uint16][..], + constants::UNSIGNED_INT | + constants::UNSIGNED_INT_2_10_10_10_REV | + constants::UNSIGNED_INT_10F_11F_11F_REV | + constants::UNSIGNED_INT_5_9_9_9_REV => &[Type::Uint32][..], + constants::FLOAT => &[Type::Float32][..], + constants::HALF_FLOAT => &[Type::Uint16][..], + _ => return Err(InvalidEnum), + }; + let channels = match format { + constants::ALPHA | constants::RED | constants::RED_INTEGER => 1, + constants::RG | constants::RG_INTEGER => 2, + constants::RGB | constants::RGB_INTEGER => 3, + constants::RGBA | constants::RGBA_INTEGER => 4, + _ => return Err(InvalidEnum), + }; + Ok(ReadPixelsAllowedFormats { + array_types, + channels, + }) + } + + fn calc_read_pixel_sizes( + &self, + width: i32, + height: i32, + bytes_per_pixel: usize, + ) -> WebGLResult<ReadPixelsSizes> { + if width < 0 || height < 0 { + return Err(InvalidValue); + } + + // See also https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.36 + let pixels_per_row = if self.texture_pack_row_length.get() > 0 { + self.texture_pack_row_length.get() + } else { + width as usize + }; + if self.texture_pack_skip_pixels.get() + width as usize > pixels_per_row { + return Err(InvalidOperation); + } + + let bytes_per_row = pixels_per_row + .checked_mul(bytes_per_pixel) + .ok_or(InvalidOperation)?; + let row_padding_bytes = { + let pack_alignment = self.base.get_texture_packing_alignment() as usize; + match bytes_per_row % pack_alignment { + 0 => 0, + remainder => pack_alignment - remainder, + } + }; + let row_stride = bytes_per_row + row_padding_bytes; + let size = if width == 0 || height == 0 { + 0 + } else { + let full_row_bytes = row_stride + .checked_mul(height as usize - 1) + .ok_or(InvalidOperation)?; + let last_row_bytes = bytes_per_pixel + .checked_mul(width as usize) + .ok_or(InvalidOperation)?; + let result = full_row_bytes + .checked_add(last_row_bytes) + .ok_or(InvalidOperation)?; + result + }; + let skipped_bytes = { + let skipped_row_bytes = self + .texture_pack_skip_rows + .get() + .checked_mul(row_stride) + .ok_or(InvalidOperation)?; + let skipped_pixel_bytes = self + .texture_pack_skip_pixels + .get() + .checked_mul(bytes_per_pixel) + .ok_or(InvalidOperation)?; + let result = skipped_row_bytes + .checked_add(skipped_pixel_bytes) + .ok_or(InvalidOperation)?; + result + }; + Ok(ReadPixelsSizes { + row_stride, + skipped_bytes, + size, + }) + } + + #[allow(unsafe_code)] + fn read_pixels_into( + &self, + x: i32, + y: i32, + width: i32, + height: i32, + format: u32, + pixel_type: u32, + dst: &mut ArrayBufferView, + dst_elem_offset: u32, + ) { + handle_potential_webgl_error!(self.base, self.base.validate_framebuffer(), return); + + if self.bound_pixel_pack_buffer.get().is_some() { + return self.base.webgl_error(InvalidOperation); + } + + let fb_slot = self.base.get_draw_framebuffer_slot(); + let fb_readbuffer_valid = match fb_slot.get() { + Some(fb) => fb.attachment(fb.read_buffer()).is_some(), + None => self.default_fb_readbuffer.get() != constants::NONE, + }; + if !fb_readbuffer_valid { + return self.base.webgl_error(InvalidOperation); + } + + let dst_byte_offset = { + let dst_elem_size = typedarray_elem_size(dst.get_array_type()); + dst_elem_offset as usize * dst_elem_size + }; + if dst_byte_offset > dst.len() { + return self.base.webgl_error(InvalidValue); + } + + let dst_array_type = dst.get_array_type(); + let ReadPixelsAllowedFormats { + array_types: allowed_array_types, + channels, + } = match self.calc_read_pixel_formats(pixel_type, format) { + Ok(result) => result, + Err(error) => return self.base.webgl_error(error), + }; + if !allowed_array_types.contains(&dst_array_type) { + return self.base.webgl_error(InvalidOperation); + } + if format != constants::RGBA || pixel_type != constants::UNSIGNED_BYTE { + return self.base.webgl_error(InvalidOperation); + } + + let bytes_per_pixel = typedarray_elem_size(dst_array_type) * channels; + let ReadPixelsSizes { + row_stride, + skipped_bytes, + size, + } = match self.calc_read_pixel_sizes(width, height, bytes_per_pixel) { + Ok(result) => result, + Err(error) => return self.base.webgl_error(error), + }; + let dst_end = dst_byte_offset + skipped_bytes + size; + let dst_pixels = unsafe { dst.as_mut_slice() }; + if dst_pixels.len() < dst_end { + return self.base.webgl_error(InvalidOperation); + } + + let dst_byte_offset = { + let margin_left = cmp::max(0, -x) as usize; + let margin_top = cmp::max(0, -y) as usize; + dst_byte_offset + + skipped_bytes + + margin_left * bytes_per_pixel + + margin_top * row_stride + }; + let src_rect = { + let (fb_width, fb_height) = handle_potential_webgl_error!( + self.base, + self.base + .get_current_framebuffer_size() + .ok_or(InvalidOperation), + return + ); + let src_origin = Point2D::new(x, y); + let src_size = Size2D::new(width as u32, height as u32); + let fb_size = Size2D::new(fb_width as u32, fb_height as u32); + match pixels::clip(src_origin, src_size.to_u64(), fb_size.to_u64()) { + Some(rect) => rect.to_u32(), + None => return, + } + }; + let src_row_bytes = handle_potential_webgl_error!( + self.base, + src_rect + .size + .width + .checked_mul(bytes_per_pixel as u32) + .ok_or(InvalidOperation), + return + ); + + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.base.send_command(WebGLCommand::ReadPixels( + src_rect, format, pixel_type, sender, + )); + let src = receiver.recv().unwrap(); + + for i in 0..src_rect.size.height as usize { + let src_start = i * src_row_bytes as usize; + let dst_start = dst_byte_offset + i * row_stride; + dst_pixels[dst_start..dst_start + src_row_bytes as usize] + .copy_from_slice(&src[src_start..src_start + src_row_bytes as usize]); + } + } + + fn uniform_vec_section_uint( + &self, + vec: Uint32ArrayOrUnsignedLongSequence, + offset: u32, + length: u32, + uniform_size: usize, + uniform_location: &WebGLUniformLocation, + ) -> WebGLResult<Vec<u32>> { + let vec = match vec { + Uint32ArrayOrUnsignedLongSequence::Uint32Array(v) => v.to_vec(), + Uint32ArrayOrUnsignedLongSequence::UnsignedLongSequence(v) => v, + }; + self.base + .uniform_vec_section::<u32>(vec, offset, length, uniform_size, uniform_location) + } + + #[allow(unsafe_code)] + fn get_default_fb_attachment_param(&self, attachment: u32, pname: u32) -> WebGLResult<JSVal> { + match attachment { + constants::BACK | constants::DEPTH | constants::STENCIL => {}, + _ => return Err(InvalidEnum), + } + + if pname == constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME { + return Ok(NullValue()); + } + + let attrs = self + .GetContextAttributes() + .unwrap_or_else(WebGLContextAttributes::empty); + + let intval = match pname { + constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE => match attachment { + constants::DEPTH if !attrs.depth => constants::NONE as _, + constants::STENCIL if !attrs.stencil => constants::NONE as _, + _ => constants::FRAMEBUFFER_DEFAULT as _, + }, + constants::FRAMEBUFFER_ATTACHMENT_RED_SIZE | + constants::FRAMEBUFFER_ATTACHMENT_GREEN_SIZE | + constants::FRAMEBUFFER_ATTACHMENT_BLUE_SIZE => match attachment { + constants::BACK => 8, + _ => 0, + }, + constants::FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE => match attachment { + constants::BACK if attrs.alpha => 8, + constants::BACK => return Err(InvalidOperation), + _ => 0, + }, + constants::FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE => match attachment { + constants::DEPTH if attrs.depth => 24, + constants::DEPTH => return Err(InvalidOperation), + _ => 0, + }, + constants::FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE => match attachment { + constants::STENCIL if attrs.stencil => 8, + constants::STENCIL => return Err(InvalidOperation), + _ => 0, + }, + constants::FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE => match attachment { + constants::DEPTH if attrs.depth => constants::UNSIGNED_NORMALIZED as _, + constants::STENCIL if attrs.stencil => constants::UNSIGNED_INT as _, + constants::DEPTH => return Err(InvalidOperation), + constants::STENCIL => return Err(InvalidOperation), + _ => constants::UNSIGNED_NORMALIZED as _, + }, + constants::FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING => match attachment { + constants::DEPTH if !attrs.depth => return Err(InvalidOperation), + constants::STENCIL if !attrs.stencil => return Err(InvalidOperation), + _ => constants::LINEAR as _, + }, + _ => return Err(InvalidEnum), + }; + Ok(Int32Value(intval)) + } + + #[allow(unsafe_code)] + fn get_specific_fb_attachment_param( + &self, + cx: JSContext, + fb: &WebGLFramebuffer, + target: u32, + attachment: u32, + pname: u32, + ) -> WebGLResult<JSVal> { + use crate::dom::webglframebuffer::WebGLFramebufferAttachmentRoot::{Renderbuffer, Texture}; + + match attachment { + constants::DEPTH_ATTACHMENT | constants::STENCIL_ATTACHMENT => {}, + constants::DEPTH_STENCIL_ATTACHMENT => { + if pname == constants::FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE { + return Err(InvalidOperation); + } + + let a = fb.attachment(constants::DEPTH_ATTACHMENT); + let b = fb.attachment(constants::STENCIL_ATTACHMENT); + match (a, b) { + (Some(Renderbuffer(ref a)), Some(Renderbuffer(ref b))) if a.id() == b.id() => { + }, + (Some(Texture(ref a)), Some(Texture(ref b))) if a.id() == b.id() => {}, + _ => return Err(InvalidOperation), + } + }, + constants::COLOR_ATTACHMENT0..=constants::COLOR_ATTACHMENT15 => { + let last_slot = + constants::COLOR_ATTACHMENT0 + self.base.limits().max_color_attachments - 1; + if last_slot < attachment { + return Err(InvalidEnum); + } + }, + _ => return Err(InvalidEnum), + } + + let attachment = match attachment { + constants::DEPTH_STENCIL_ATTACHMENT => constants::DEPTH_ATTACHMENT, + _ => attachment, + }; + + if pname == constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME { + rooted!(in(*cx) let mut rval = NullValue()); + match fb.attachment(attachment) { + Some(Renderbuffer(rb)) => unsafe { + rb.to_jsval(*cx, rval.handle_mut()); + }, + Some(Texture(texture)) => unsafe { + texture.to_jsval(*cx, rval.handle_mut()); + }, + _ => {}, + } + return Ok(rval.get()); + } + + match pname { + constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE => {}, + _ => match fb.attachment(attachment) { + Some(webgl_attachment) => match pname { + constants::FRAMEBUFFER_ATTACHMENT_RED_SIZE | + constants::FRAMEBUFFER_ATTACHMENT_GREEN_SIZE | + constants::FRAMEBUFFER_ATTACHMENT_BLUE_SIZE | + constants::FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE | + constants::FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE | + constants::FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE | + constants::FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE | + constants::FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING => {}, + _ => match webgl_attachment { + WebGLFramebufferAttachmentRoot::Renderbuffer(_) => return Err(InvalidEnum), + WebGLFramebufferAttachmentRoot::Texture(_) => match pname { + constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL | + constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE | + constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER => {}, + _ => return Err(InvalidEnum), + }, + }, + }, + None => return Err(InvalidOperation), + }, + } + + let (sender, receiver) = webgl_channel().unwrap(); + self.base + .send_command(WebGLCommand::GetFramebufferAttachmentParameter( + target, attachment, pname, sender, + )); + + let retval = receiver.recv().unwrap(); + Ok(Int32Value(retval)) + } + + fn clearbuffer_array_size(&self, buffer: u32, draw_buffer: i32) -> WebGLResult<usize> { + match buffer { + constants::COLOR => { + if draw_buffer < 0 || draw_buffer as u32 >= self.base.limits().max_draw_buffers { + return Err(InvalidValue); + } + Ok(4) + }, + constants::DEPTH | constants::STENCIL | constants::DEPTH_STENCIL => { + if draw_buffer != 0 { + return Err(InvalidValue); + } + Ok(1) + }, + _ => unreachable!(), + } + } + + fn clear_buffer<T: Clone>( + &self, + buffer: u32, + draw_buffer: i32, + valid_buffers: &[u32], + src_offset: u32, + array: Vec<T>, + msg: fn(u32, i32, Vec<T>) -> WebGLCommand, + ) { + if !valid_buffers.contains(&buffer) { + return self.base.webgl_error(InvalidEnum); + } + + let array_size = handle_potential_webgl_error!( + self.base, + self.clearbuffer_array_size(buffer, draw_buffer), + return + ); + let src_offset = src_offset as usize; + + if array.len() < src_offset + array_size { + return self.base.webgl_error(InvalidValue); + } + let array = array[src_offset..src_offset + array_size].to_vec(); + + self.base.send_command(msg(buffer, draw_buffer, array)); + } + + fn valid_fb_attachment_values(&self, target: u32, attachments: &[u32]) -> bool { + let fb_slot = match target { + constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => { + self.base.get_draw_framebuffer_slot() + }, + constants::READ_FRAMEBUFFER => self.base.get_read_framebuffer_slot(), + _ => { + self.base.webgl_error(InvalidEnum); + return false; + }, + }; + + if let Some(fb) = fb_slot.get() { + if fb.check_status() != constants::FRAMEBUFFER_COMPLETE { + return false; + } + + for &attachment in attachments { + match attachment { + constants::DEPTH_ATTACHMENT | + constants::STENCIL_ATTACHMENT | + constants::DEPTH_STENCIL_ATTACHMENT => {}, + constants::COLOR_ATTACHMENT0..=constants::COLOR_ATTACHMENT15 => { + let last_slot = constants::COLOR_ATTACHMENT0 + + self.base.limits().max_color_attachments - + 1; + if last_slot < attachment { + return false; + } + }, + _ => return false, + } + } + } else { + for &attachment in attachments { + match attachment { + constants::COLOR | constants::DEPTH | constants::STENCIL => {}, + _ => return false, + } + } + } + + true + } + + fn vertex_attrib_i(&self, index: u32, x: i32, y: i32, z: i32, w: i32) { + if index >= self.base.limits().max_vertex_attribs { + return self.base.webgl_error(InvalidValue); + } + self.base.current_vertex_attribs()[index as usize] = VertexAttrib::Int(x, y, z, w); + self.current_vao() + .set_vertex_attrib_type(index, constants::INT); + self.base + .send_command(WebGLCommand::VertexAttribI(index, x, y, z, w)); + } + + fn vertex_attrib_u(&self, index: u32, x: u32, y: u32, z: u32, w: u32) { + if index >= self.base.limits().max_vertex_attribs { + return self.base.webgl_error(InvalidValue); + } + self.base.current_vertex_attribs()[index as usize] = VertexAttrib::Uint(x, y, z, w); + self.current_vao() + .set_vertex_attrib_type(index, constants::UNSIGNED_INT); + self.base + .send_command(WebGLCommand::VertexAttribU(index, x, y, z, w)); + } + + fn tex_storage( + &self, + dimensions: u8, + target: u32, + levels: i32, + internal_format: u32, + width: i32, + height: i32, + depth: i32, + ) { + let expected_dimensions = match target { + constants::TEXTURE_2D | constants::TEXTURE_CUBE_MAP => 2, + constants::TEXTURE_3D | constants::TEXTURE_2D_ARRAY => 3, + _ => return self.base.webgl_error(InvalidEnum), + }; + if dimensions != expected_dimensions { + return self.base.webgl_error(InvalidEnum); + } + + let validator = TexStorageValidator::new( + &self.base, + dimensions, + target, + levels, + internal_format, + width, + height, + depth, + ); + let TexStorageValidatorResult { + texture, + target, + levels, + internal_format, + width, + height, + depth, + } = match validator.validate() { + Ok(result) => result, + Err(_) => return, // NB: The validator sets the correct error for us. + }; + + handle_potential_webgl_error!( + self.base, + texture.storage(target, levels, internal_format, width, height, depth), + return + ); + } +} + +impl WebGL2RenderingContextMethods for WebGL2RenderingContext { + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1 + fn Canvas(&self) -> DomRoot<HTMLCanvasElement> { + self.base.Canvas() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 + fn Flush(&self) { + self.base.Flush() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 + fn Finish(&self) { + self.base.Finish() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1 + fn DrawingBufferWidth(&self) -> i32 { + self.base.DrawingBufferWidth() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1 + fn DrawingBufferHeight(&self) -> i32 { + self.base.DrawingBufferHeight() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 + fn GetBufferParameter(&self, _cx: JSContext, target: u32, parameter: u32) -> JSVal { + let buffer = + handle_potential_webgl_error!(self.base, self.bound_buffer(target), return NullValue()); + self.base.get_buffer_param(buffer, parameter) + } + + #[allow(unsafe_code)] + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn GetParameter(&self, cx: JSContext, parameter: u32) -> JSVal { + match parameter { + constants::VERSION => unsafe { + rooted!(in(*cx) let mut rval = UndefinedValue()); + "WebGL 2.0".to_jsval(*cx, rval.handle_mut()); + return rval.get(); + }, + constants::SHADING_LANGUAGE_VERSION => unsafe { + rooted!(in(*cx) let mut rval = UndefinedValue()); + "WebGL GLSL ES 3.00".to_jsval(*cx, rval.handle_mut()); + return rval.get(); + }, + constants::MAX_CLIENT_WAIT_TIMEOUT_WEBGL => { + return DoubleValue( + self.base.limits().max_client_wait_timeout_webgl.as_nanos() as f64 + ); + }, + constants::MAX_SERVER_WAIT_TIMEOUT => { + return DoubleValue(self.base.limits().max_server_wait_timeout.as_nanos() as f64); + }, + constants::SAMPLER_BINDING => unsafe { + let idx = (self.base.textures().active_unit_enum() - constants::TEXTURE0) as usize; + assert!(idx < self.samplers.len()); + let sampler = self.samplers[idx].get(); + return optional_root_object_to_js_or_null!(*cx, sampler); + }, + constants::COPY_READ_BUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!( + *cx, + &self.bound_copy_read_buffer.get() + ); + }, + constants::COPY_WRITE_BUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!( + *cx, + &self.bound_copy_write_buffer.get() + ); + }, + constants::PIXEL_PACK_BUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!( + *cx, + &self.bound_pixel_pack_buffer.get() + ); + }, + constants::PIXEL_UNPACK_BUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!( + *cx, + &self.bound_pixel_unpack_buffer.get() + ); + }, + constants::TRANSFORM_FEEDBACK_BUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!( + *cx, + &self.bound_transform_feedback_buffer.get() + ); + }, + constants::UNIFORM_BUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!(*cx, &self.bound_uniform_buffer.get()); + }, + constants::TRANSFORM_FEEDBACK_BINDING => unsafe { + return optional_root_object_to_js_or_null!( + *cx, + self.current_transform_feedback.get() + ); + }, + constants::ELEMENT_ARRAY_BUFFER_BINDING => unsafe { + let buffer = self.current_vao().element_array_buffer().get(); + return optional_root_object_to_js_or_null!(*cx, buffer); + }, + constants::VERTEX_ARRAY_BINDING => unsafe { + let vao = self.current_vao(); + let vao = vao.id().map(|_| &*vao); + return optional_root_object_to_js_or_null!(*cx, vao); + }, + // NOTE: DRAW_FRAMEBUFFER_BINDING is the same as FRAMEBUFFER_BINDING, handled on the WebGL1 side + constants::READ_FRAMEBUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!( + *cx, + &self.base.get_read_framebuffer_slot().get() + ); + }, + constants::READ_BUFFER => { + let buffer = match self.base.get_read_framebuffer_slot().get() { + Some(fb) => fb.read_buffer(), + None => self.default_fb_readbuffer.get(), + }; + return UInt32Value(buffer); + }, + constants::DRAW_BUFFER0..=constants::DRAW_BUFFER15 => { + let buffer = match self.base.get_read_framebuffer_slot().get() { + Some(fb) => { + let idx = parameter - constants::DRAW_BUFFER0; + fb.draw_buffer_i(idx as usize) + }, + None if parameter == constants::DRAW_BUFFER0 => { + self.default_fb_readbuffer.get() + }, + None => constants::NONE, + }; + return UInt32Value(buffer); + }, + constants::MAX_TEXTURE_LOD_BIAS => { + return DoubleValue(self.base.limits().max_texture_lod_bias as f64) + }, + constants::MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS => { + return DoubleValue( + self.base.limits().max_combined_fragment_uniform_components as f64, + ) + }, + constants::MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS => { + return DoubleValue( + self.base.limits().max_combined_vertex_uniform_components as f64, + ) + }, + constants::MAX_ELEMENT_INDEX => { + return DoubleValue(self.base.limits().max_element_index as f64) + }, + constants::MAX_UNIFORM_BLOCK_SIZE => { + return DoubleValue(self.base.limits().max_uniform_block_size as f64) + }, + constants::MIN_PROGRAM_TEXEL_OFFSET => { + return Int32Value(self.base.limits().min_program_texel_offset) + }, + _ => {}, + } + + let limit = match parameter { + constants::MAX_3D_TEXTURE_SIZE => Some(self.base.limits().max_3d_texture_size), + constants::MAX_ARRAY_TEXTURE_LAYERS => { + Some(self.base.limits().max_array_texture_layers) + }, + constants::MAX_COLOR_ATTACHMENTS => Some(self.base.limits().max_color_attachments), + constants::MAX_COMBINED_UNIFORM_BLOCKS => { + Some(self.base.limits().max_combined_uniform_blocks) + }, + constants::MAX_DRAW_BUFFERS => Some(self.base.limits().max_draw_buffers), + constants::MAX_ELEMENTS_INDICES => Some(self.base.limits().max_elements_indices), + constants::MAX_ELEMENTS_VERTICES => Some(self.base.limits().max_elements_vertices), + constants::MAX_FRAGMENT_INPUT_COMPONENTS => { + Some(self.base.limits().max_fragment_input_components) + }, + constants::MAX_FRAGMENT_UNIFORM_BLOCKS => { + Some(self.base.limits().max_fragment_uniform_blocks) + }, + constants::MAX_FRAGMENT_UNIFORM_COMPONENTS => { + Some(self.base.limits().max_fragment_uniform_components) + }, + constants::MAX_PROGRAM_TEXEL_OFFSET => { + Some(self.base.limits().max_program_texel_offset) + }, + constants::MAX_SAMPLES => Some(self.base.limits().max_samples), + constants::MAX_UNIFORM_BUFFER_BINDINGS => { + Some(self.base.limits().max_uniform_buffer_bindings) + }, + constants::MAX_VARYING_COMPONENTS => Some(self.base.limits().max_varying_components), + constants::MAX_VERTEX_OUTPUT_COMPONENTS => { + Some(self.base.limits().max_vertex_output_components) + }, + constants::MAX_VERTEX_UNIFORM_BLOCKS => { + Some(self.base.limits().max_vertex_uniform_blocks) + }, + constants::MAX_VERTEX_UNIFORM_COMPONENTS => { + Some(self.base.limits().max_vertex_uniform_components) + }, + constants::UNIFORM_BUFFER_OFFSET_ALIGNMENT => { + Some(self.base.limits().uniform_buffer_offset_alignment) + }, + _ => None, + }; + if let Some(limit) = limit { + return UInt32Value(limit); + } + + self.base.GetParameter(cx, parameter) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn GetTexParameter(&self, cx: JSContext, target: u32, pname: u32) -> JSVal { + self.base.GetTexParameter(cx, target, pname) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn GetError(&self) -> u32 { + self.base.GetError() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.2 + fn GetContextAttributes(&self) -> Option<WebGLContextAttributes> { + self.base.GetContextAttributes() + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.13 + fn IsContextLost(&self) -> bool { + self.base.IsContextLost() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14 + fn GetSupportedExtensions(&self) -> Option<Vec<DOMString>> { + self.base.GetSupportedExtensions() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14 + fn GetExtension(&self, cx: JSContext, name: DOMString) -> Option<NonNull<JSObject>> { + self.base.GetExtension(cx, name) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4 + fn GetFramebufferAttachmentParameter( + &self, + cx: JSContext, + target: u32, + attachment: u32, + pname: u32, + ) -> JSVal { + let fb_slot = match target { + constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => { + self.base.get_draw_framebuffer_slot() + }, + constants::READ_FRAMEBUFFER => &self.base.get_read_framebuffer_slot(), + _ => { + self.base.webgl_error(InvalidEnum); + return NullValue(); + }, + }; + + if let Some(fb) = fb_slot.get() { + // A selected framebuffer is bound to the target + handle_potential_webgl_error!(self.base, fb.validate_transparent(), return NullValue()); + handle_potential_webgl_error!( + self.base, + self.get_specific_fb_attachment_param(cx, &fb, target, attachment, pname), + return NullValue() + ) + } else { + // The default framebuffer is bound to the target + handle_potential_webgl_error!( + self.base, + self.get_default_fb_attachment_param(attachment, pname), + return NullValue() + ) + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 + fn GetRenderbufferParameter(&self, cx: JSContext, target: u32, pname: u32) -> JSVal { + self.base.GetRenderbufferParameter(cx, target, pname) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn ActiveTexture(&self, texture: u32) { + self.base.ActiveTexture(texture) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn BlendColor(&self, r: f32, g: f32, b: f32, a: f32) { + self.base.BlendColor(r, g, b, a) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn BlendEquation(&self, mode: u32) { + self.base.BlendEquation(mode) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn BlendEquationSeparate(&self, mode_rgb: u32, mode_alpha: u32) { + self.base.BlendEquationSeparate(mode_rgb, mode_alpha) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn BlendFunc(&self, src_factor: u32, dest_factor: u32) { + self.base.BlendFunc(src_factor, dest_factor) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn BlendFuncSeparate(&self, src_rgb: u32, dest_rgb: u32, src_alpha: u32, dest_alpha: u32) { + self.base + .BlendFuncSeparate(src_rgb, dest_rgb, src_alpha, dest_alpha) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn AttachShader(&self, program: &WebGLProgram, shader: &WebGLShader) { + self.base.AttachShader(program, shader) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn DetachShader(&self, program: &WebGLProgram, shader: &WebGLShader) { + self.base.DetachShader(program, shader) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn BindAttribLocation(&self, program: &WebGLProgram, index: u32, name: DOMString) { + self.base.BindAttribLocation(program, index, name) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.2 + fn BindBuffer(&self, target: u32, buffer: Option<&WebGLBuffer>) { + let current_vao; + let slot = match target { + constants::COPY_READ_BUFFER => &self.bound_copy_read_buffer, + constants::COPY_WRITE_BUFFER => &self.bound_copy_write_buffer, + constants::PIXEL_PACK_BUFFER => &self.bound_pixel_pack_buffer, + constants::PIXEL_UNPACK_BUFFER => &self.bound_pixel_unpack_buffer, + constants::TRANSFORM_FEEDBACK_BUFFER => &self.bound_transform_feedback_buffer, + constants::UNIFORM_BUFFER => &self.bound_uniform_buffer, + constants::ELEMENT_ARRAY_BUFFER => { + current_vao = self.current_vao(); + current_vao.element_array_buffer() + }, + _ => return self.base.BindBuffer(target, buffer), + }; + self.base.bind_buffer_maybe(&slot, target, buffer); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn BindFramebuffer(&self, target: u32, framebuffer: Option<&WebGLFramebuffer>) { + handle_potential_webgl_error!( + self.base, + self.base.validate_new_framebuffer_binding(framebuffer), + return + ); + + let (bind_read, bind_draw) = match target { + constants::FRAMEBUFFER => (true, true), + constants::READ_FRAMEBUFFER => (true, false), + constants::DRAW_FRAMEBUFFER => (false, true), + _ => return self.base.webgl_error(InvalidEnum), + }; + if bind_read { + self.base.bind_framebuffer_to( + target, + framebuffer, + &self.base.get_read_framebuffer_slot(), + ); + } + if bind_draw { + self.base.bind_framebuffer_to( + target, + framebuffer, + &self.base.get_draw_framebuffer_slot(), + ); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 + fn BindRenderbuffer(&self, target: u32, renderbuffer: Option<&WebGLRenderbuffer>) { + self.base.BindRenderbuffer(target, renderbuffer) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn BindTexture(&self, target: u32, texture: Option<&WebGLTexture>) { + self.base.BindTexture(target, texture) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn GenerateMipmap(&self, target: u32) { + self.base.GenerateMipmap(target) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 + fn BufferData_(&self, target: u32, data: Option<ArrayBufferViewOrArrayBuffer>, usage: u32) { + let usage = handle_potential_webgl_error!(self.base, self.buffer_usage(usage), return); + let bound_buffer = + handle_potential_webgl_error!(self.base, self.bound_buffer(target), return); + self.base.buffer_data(target, data, usage, bound_buffer) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 + fn BufferData(&self, target: u32, size: i64, usage: u32) { + let usage = handle_potential_webgl_error!(self.base, self.buffer_usage(usage), return); + let bound_buffer = + handle_potential_webgl_error!(self.base, self.bound_buffer(target), return); + self.base.buffer_data_(target, size, usage, bound_buffer) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.3 + #[allow(unsafe_code)] + fn BufferData__( + &self, + target: u32, + data: CustomAutoRooterGuard<ArrayBufferView>, + usage: u32, + elem_offset: u32, + length: u32, + ) { + let usage = handle_potential_webgl_error!(self.base, self.buffer_usage(usage), return); + let bound_buffer = + handle_potential_webgl_error!(self.base, self.bound_buffer(target), return); + let bound_buffer = + handle_potential_webgl_error!(self.base, bound_buffer.ok_or(InvalidOperation), return); + + let elem_size = typedarray_elem_size(data.get_array_type()); + let elem_count = data.len() / elem_size; + let elem_offset = elem_offset as usize; + let byte_offset = elem_offset * elem_size; + + if byte_offset > data.len() { + return self.base.webgl_error(InvalidValue); + } + + let copy_count = if length == 0 { + elem_count - elem_offset + } else { + length as usize + }; + if copy_count == 0 { + return; + } + let copy_bytes = copy_count * elem_size; + + if byte_offset + copy_bytes > data.len() { + return self.base.webgl_error(InvalidValue); + } + + let data_end = byte_offset + copy_bytes; + let data: &[u8] = unsafe { &data.as_slice()[byte_offset..data_end] }; + handle_potential_webgl_error!(self.base, bound_buffer.buffer_data(target, &data, usage)); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 + fn BufferSubData(&self, target: u32, offset: i64, data: ArrayBufferViewOrArrayBuffer) { + let bound_buffer = + handle_potential_webgl_error!(self.base, self.bound_buffer(target), return); + self.base + .buffer_sub_data(target, offset, data, bound_buffer) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.3 + #[allow(unsafe_code)] + fn BufferSubData_( + &self, + target: u32, + dst_byte_offset: i64, + src_data: CustomAutoRooterGuard<ArrayBufferView>, + src_elem_offset: u32, + length: u32, + ) { + let bound_buffer = + handle_potential_webgl_error!(self.base, self.bound_buffer(target), return); + let bound_buffer = + handle_potential_webgl_error!(self.base, bound_buffer.ok_or(InvalidOperation), return); + + let src_elem_size = typedarray_elem_size(src_data.get_array_type()); + let src_elem_count = src_data.len() / src_elem_size; + let src_elem_offset = src_elem_offset as usize; + let src_byte_offset = src_elem_offset * src_elem_size; + + if dst_byte_offset < 0 || src_byte_offset > src_data.len() { + return self.base.webgl_error(InvalidValue); + } + + let copy_count = if length == 0 { + src_elem_count - src_elem_offset + } else { + length as usize + }; + if copy_count == 0 { + return; + } + let copy_bytes = copy_count * src_elem_size; + + let dst_byte_offset = dst_byte_offset as usize; + if dst_byte_offset + copy_bytes > bound_buffer.capacity() || + src_byte_offset + copy_bytes > src_data.len() + { + return self.base.webgl_error(InvalidValue); + } + + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.base.send_command(WebGLCommand::BufferSubData( + target, + dst_byte_offset as isize, + receiver, + )); + let src_end = src_byte_offset + copy_bytes; + let data: &[u8] = unsafe { &src_data.as_slice()[src_byte_offset..src_end] }; + sender.send(data).unwrap(); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.3 + fn CopyBufferSubData( + &self, + read_target: u32, + write_target: u32, + read_offset: i64, + write_offset: i64, + size: i64, + ) { + if read_offset < 0 || write_offset < 0 || size < 0 { + return self.base.webgl_error(InvalidValue); + } + + let read_buffer = + handle_potential_webgl_error!(self.base, self.bound_buffer(read_target), return); + let read_buffer = + handle_potential_webgl_error!(self.base, read_buffer.ok_or(InvalidOperation), return); + + let write_buffer = + handle_potential_webgl_error!(self.base, self.bound_buffer(write_target), return); + let write_buffer = + handle_potential_webgl_error!(self.base, write_buffer.ok_or(InvalidOperation), return); + + let read_until = read_offset + size; + let write_until = write_offset + size; + if read_until as usize > read_buffer.capacity() || + write_until as usize > write_buffer.capacity() + { + return self.base.webgl_error(InvalidValue); + } + + if read_target == write_target { + let is_separate = read_until <= write_offset || write_until <= read_offset; + if !is_separate { + return self.base.webgl_error(InvalidValue); + } + } + let src_is_elemarray = read_buffer + .target() + .map_or(false, |t| t == constants::ELEMENT_ARRAY_BUFFER); + let dst_is_elemarray = write_buffer + .target() + .map_or(false, |t| t == constants::ELEMENT_ARRAY_BUFFER); + if src_is_elemarray != dst_is_elemarray { + return self.base.webgl_error(InvalidOperation); + } + + self.base.send_command(WebGLCommand::CopyBufferSubData( + read_target, + write_target, + read_offset, + write_offset, + size, + )); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.3 + #[allow(unsafe_code)] + fn GetBufferSubData( + &self, + target: u32, + src_byte_offset: i64, + mut dst_buffer: CustomAutoRooterGuard<ArrayBufferView>, + dst_elem_offset: u32, + length: u32, + ) { + let bound_buffer = + handle_potential_webgl_error!(self.base, self.bound_buffer(target), return); + let bound_buffer = + handle_potential_webgl_error!(self.base, bound_buffer.ok_or(InvalidOperation), return); + + let dst_elem_size = typedarray_elem_size(dst_buffer.get_array_type()); + let dst_elem_count = dst_buffer.len() / dst_elem_size; + let dst_elem_offset = dst_elem_offset as usize; + let dst_byte_offset = dst_elem_offset * dst_elem_size; + + if src_byte_offset < 0 || dst_byte_offset > dst_buffer.len() { + return self.base.webgl_error(InvalidValue); + } + + let copy_count = if length == 0 { + dst_elem_count - dst_elem_offset + } else { + length as usize + }; + if copy_count == 0 { + return; + } + let copy_bytes = copy_count * dst_elem_size; + + // TODO(mmatyas): Transform Feedback + + let src_byte_offset = src_byte_offset as usize; + if src_byte_offset + copy_bytes > bound_buffer.capacity() || + dst_byte_offset + copy_bytes > dst_buffer.len() + { + return self.base.webgl_error(InvalidValue); + } + + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.base.send_command(WebGLCommand::GetBufferSubData( + target, + src_byte_offset, + copy_bytes, + sender, + )); + let data = receiver.recv().unwrap(); + let dst_end = dst_byte_offset + copy_bytes; + unsafe { + dst_buffer.as_mut_slice()[dst_byte_offset..dst_end].copy_from_slice(&data); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6 + #[allow(unsafe_code)] + fn CompressedTexImage2D( + &self, + target: u32, + level: i32, + internal_format: u32, + width: i32, + height: i32, + border: i32, + pixels: CustomAutoRooterGuard<ArrayBufferView>, + src_offset: u32, + src_length_override: u32, + ) { + let mut data = unsafe { pixels.as_slice() }; + let start = src_offset as usize; + let end = (src_offset + src_length_override) as usize; + if start > data.len() || end > data.len() { + self.base.webgl_error(InvalidValue); + return; + } + if src_length_override != 0 { + data = &data[start..end]; + } + self.base.compressed_tex_image_2d( + target, + level, + internal_format, + width, + height, + border, + data, + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + #[allow(unsafe_code)] + fn CompressedTexSubImage2D( + &self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + width: i32, + height: i32, + format: u32, + pixels: CustomAutoRooterGuard<ArrayBufferView>, + src_offset: u32, + src_length_override: u32, + ) { + let mut data = unsafe { pixels.as_slice() }; + let start = src_offset as usize; + let end = (src_offset + src_length_override) as usize; + if start > data.len() || end > data.len() { + self.base.webgl_error(InvalidValue); + return; + } + if src_length_override != 0 { + data = &data[start..end]; + } + self.base.compressed_tex_sub_image_2d( + target, level, xoffset, yoffset, width, height, format, data, + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn CopyTexImage2D( + &self, + target: u32, + level: i32, + internal_format: u32, + x: i32, + y: i32, + width: i32, + height: i32, + border: i32, + ) { + self.base + .CopyTexImage2D(target, level, internal_format, x, y, width, height, border) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn CopyTexSubImage2D( + &self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + x: i32, + y: i32, + width: i32, + height: i32, + ) { + self.base + .CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 + fn Clear(&self, mask: u32) { + self.base.Clear(mask) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn ClearColor(&self, red: f32, green: f32, blue: f32, alpha: f32) { + self.base.ClearColor(red, green, blue, alpha) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn ClearDepth(&self, depth: f32) { + self.base.ClearDepth(depth) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn ClearStencil(&self, stencil: i32) { + self.base.ClearStencil(stencil) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn ColorMask(&self, r: bool, g: bool, b: bool, a: bool) { + self.base.ColorMask(r, g, b, a) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn CullFace(&self, mode: u32) { + self.base.CullFace(mode) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn FrontFace(&self, mode: u32) { + self.base.FrontFace(mode) + } + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn DepthFunc(&self, func: u32) { + self.base.DepthFunc(func) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn DepthMask(&self, flag: bool) { + self.base.DepthMask(flag) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn DepthRange(&self, near: f32, far: f32) { + self.base.DepthRange(near, far) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn Enable(&self, cap: u32) { + match cap { + constants::RASTERIZER_DISCARD => { + self.enable_rasterizer_discard.set(true); + self.base.send_command(WebGLCommand::Enable(cap)); + }, + _ => self.base.Enable(cap), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn Disable(&self, cap: u32) { + match cap { + constants::RASTERIZER_DISCARD => { + self.enable_rasterizer_discard.set(false); + self.base.send_command(WebGLCommand::Disable(cap)); + }, + _ => self.base.Disable(cap), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn CompileShader(&self, shader: &WebGLShader) { + self.base.CompileShader(shader) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 + fn CreateBuffer(&self) -> Option<DomRoot<WebGLBuffer>> { + self.base.CreateBuffer() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn CreateFramebuffer(&self) -> Option<DomRoot<WebGLFramebuffer>> { + self.base.CreateFramebuffer() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 + fn CreateRenderbuffer(&self) -> Option<DomRoot<WebGLRenderbuffer>> { + self.base.CreateRenderbuffer() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn CreateTexture(&self) -> Option<DomRoot<WebGLTexture>> { + self.base.CreateTexture() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn CreateProgram(&self) -> Option<DomRoot<WebGLProgram>> { + self.base.CreateProgram() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn CreateShader(&self, shader_type: u32) -> Option<DomRoot<WebGLShader>> { + self.base.CreateShader(shader_type) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.17 + fn CreateVertexArray(&self) -> Option<DomRoot<WebGLVertexArrayObject>> { + self.base.create_vertex_array_webgl2() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 + fn DeleteBuffer(&self, buffer: Option<&WebGLBuffer>) { + let buffer = match buffer { + Some(buffer) => buffer, + None => return, + }; + handle_potential_webgl_error!(self.base, self.base.validate_ownership(buffer), return); + if buffer.is_marked_for_deletion() { + return; + } + self.current_vao().unbind_buffer(buffer); + self.unbind_from(&self.base.array_buffer_slot(), &buffer); + self.unbind_from(&self.bound_copy_read_buffer, &buffer); + self.unbind_from(&self.bound_copy_write_buffer, &buffer); + self.unbind_from(&self.bound_pixel_pack_buffer, &buffer); + self.unbind_from(&self.bound_pixel_unpack_buffer, &buffer); + self.unbind_from(&self.bound_transform_feedback_buffer, &buffer); + self.unbind_from(&self.bound_uniform_buffer, &buffer); + + for binding in self.indexed_uniform_buffer_bindings.iter() { + self.unbind_from(&binding.buffer, &buffer); + } + for binding in self.indexed_transform_feedback_buffer_bindings.iter() { + self.unbind_from(&binding.buffer, &buffer); + } + + buffer.mark_for_deletion(Operation::Infallible); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn DeleteFramebuffer(&self, framebuffer: Option<&WebGLFramebuffer>) { + self.base.DeleteFramebuffer(framebuffer) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 + fn DeleteRenderbuffer(&self, renderbuffer: Option<&WebGLRenderbuffer>) { + self.base.DeleteRenderbuffer(renderbuffer) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn DeleteTexture(&self, texture: Option<&WebGLTexture>) { + self.base.DeleteTexture(texture) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn DeleteProgram(&self, program: Option<&WebGLProgram>) { + self.base.DeleteProgram(program) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn DeleteShader(&self, shader: Option<&WebGLShader>) { + self.base.DeleteShader(shader) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.17 + fn DeleteVertexArray(&self, vertex_array: Option<&WebGLVertexArrayObject>) { + self.base.delete_vertex_array_webgl2(vertex_array); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 + fn DrawArrays(&self, mode: u32, first: i32, count: i32) { + self.validate_uniform_block_for_draw(); + self.validate_vertex_attribs_for_draw(); + self.base.DrawArrays(mode, first, count) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 + fn DrawElements(&self, mode: u32, count: i32, type_: u32, offset: i64) { + self.validate_uniform_block_for_draw(); + self.validate_vertex_attribs_for_draw(); + self.base.DrawElements(mode, count, type_, offset) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn EnableVertexAttribArray(&self, attrib_id: u32) { + self.base.EnableVertexAttribArray(attrib_id) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn DisableVertexAttribArray(&self, attrib_id: u32) { + self.base.DisableVertexAttribArray(attrib_id) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn GetActiveUniform( + &self, + program: &WebGLProgram, + index: u32, + ) -> Option<DomRoot<WebGLActiveInfo>> { + self.base.GetActiveUniform(program, index) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn GetActiveAttrib( + &self, + program: &WebGLProgram, + index: u32, + ) -> Option<DomRoot<WebGLActiveInfo>> { + self.base.GetActiveAttrib(program, index) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn GetAttribLocation(&self, program: &WebGLProgram, name: DOMString) -> i32 { + self.base.GetAttribLocation(program, name) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.7 + fn GetFragDataLocation(&self, program: &WebGLProgram, name: DOMString) -> i32 { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(program), return -1); + handle_potential_webgl_error!(self.base, program.get_frag_data_location(name), -1) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn GetProgramInfoLog(&self, program: &WebGLProgram) -> Option<DOMString> { + self.base.GetProgramInfoLog(program) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn GetProgramParameter(&self, cx: JSContext, program: &WebGLProgram, param_id: u32) -> JSVal { + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(program), + return NullValue() + ); + if program.is_deleted() { + self.base.webgl_error(InvalidOperation); + return NullValue(); + } + match param_id { + constants::TRANSFORM_FEEDBACK_VARYINGS => { + Int32Value(program.transform_feedback_varyings_length()) + }, + constants::TRANSFORM_FEEDBACK_BUFFER_MODE => { + Int32Value(program.transform_feedback_buffer_mode()) + }, + _ => self.base.GetProgramParameter(cx, program, param_id), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn GetShaderInfoLog(&self, shader: &WebGLShader) -> Option<DOMString> { + self.base.GetShaderInfoLog(shader) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn GetShaderParameter(&self, cx: JSContext, shader: &WebGLShader, param_id: u32) -> JSVal { + self.base.GetShaderParameter(cx, shader, param_id) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn GetShaderPrecisionFormat( + &self, + shader_type: u32, + precision_type: u32, + ) -> Option<DomRoot<WebGLShaderPrecisionFormat>> { + self.base + .GetShaderPrecisionFormat(shader_type, precision_type) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.2 + #[allow(unsafe_code)] + fn GetIndexedParameter(&self, cx: JSContext, target: u32, index: u32) -> JSVal { + let bindings = match target { + constants::TRANSFORM_FEEDBACK_BUFFER_BINDING | + constants::TRANSFORM_FEEDBACK_BUFFER_SIZE | + constants::TRANSFORM_FEEDBACK_BUFFER_START => { + &self.indexed_transform_feedback_buffer_bindings + }, + constants::UNIFORM_BUFFER_BINDING | + constants::UNIFORM_BUFFER_SIZE | + constants::UNIFORM_BUFFER_START => &self.indexed_uniform_buffer_bindings, + _ => { + self.base.webgl_error(InvalidEnum); + return NullValue(); + }, + }; + + let binding = match bindings.get(index as usize) { + Some(binding) => binding, + None => { + self.base.webgl_error(InvalidValue); + return NullValue(); + }, + }; + + match target { + constants::TRANSFORM_FEEDBACK_BUFFER_BINDING | constants::UNIFORM_BUFFER_BINDING => unsafe { + optional_root_object_to_js_or_null!(*cx, binding.buffer.get()) + }, + constants::TRANSFORM_FEEDBACK_BUFFER_START | constants::UNIFORM_BUFFER_START => { + Int32Value(binding.start.get() as _) + }, + constants::TRANSFORM_FEEDBACK_BUFFER_SIZE | constants::UNIFORM_BUFFER_SIZE => { + Int32Value(binding.size.get() as _) + }, + _ => unreachable!(), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn GetUniformLocation( + &self, + program: &WebGLProgram, + name: DOMString, + ) -> Option<DomRoot<WebGLUniformLocation>> { + self.base.GetUniformLocation(program, name) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn GetVertexAttrib(&self, cx: JSContext, index: u32, pname: u32) -> JSVal { + self.base.GetVertexAttrib(cx, index, pname) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn GetVertexAttribOffset(&self, index: u32, pname: u32) -> i64 { + self.base.GetVertexAttribOffset(index, pname) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn Hint(&self, target: u32, mode: u32) { + self.base.Hint(target, mode) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 + fn IsBuffer(&self, buffer: Option<&WebGLBuffer>) -> bool { + self.base.IsBuffer(buffer) + } + + // TODO: We could write this without IPC, recording the calls to `enable` and `disable`. + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.2 + fn IsEnabled(&self, cap: u32) -> bool { + match cap { + constants::RASTERIZER_DISCARD => self.enable_rasterizer_discard.get(), + _ => self.base.IsEnabled(cap), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn IsFramebuffer(&self, frame_buffer: Option<&WebGLFramebuffer>) -> bool { + self.base.IsFramebuffer(frame_buffer) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn IsProgram(&self, program: Option<&WebGLProgram>) -> bool { + self.base.IsProgram(program) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 + fn IsRenderbuffer(&self, render_buffer: Option<&WebGLRenderbuffer>) -> bool { + self.base.IsRenderbuffer(render_buffer) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn IsShader(&self, shader: Option<&WebGLShader>) -> bool { + self.base.IsShader(shader) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn IsTexture(&self, texture: Option<&WebGLTexture>) -> bool { + self.base.IsTexture(texture) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.17 + fn IsVertexArray(&self, vertex_array: Option<&WebGLVertexArrayObject>) -> bool { + self.base.is_vertex_array_webgl2(vertex_array) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn LineWidth(&self, width: f32) { + self.base.LineWidth(width) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.2 + fn PixelStorei(&self, param_name: u32, param_value: i32) { + if param_value < 0 { + return self.base.webgl_error(InvalidValue); + } + + match param_name { + constants::PACK_ROW_LENGTH => self.texture_pack_row_length.set(param_value as _), + constants::PACK_SKIP_PIXELS => self.texture_pack_skip_pixels.set(param_value as _), + constants::PACK_SKIP_ROWS => self.texture_pack_skip_rows.set(param_value as _), + _ => self.base.PixelStorei(param_name, param_value), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn PolygonOffset(&self, factor: f32, units: f32) { + self.base.PolygonOffset(factor, units) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12 + fn ReadPixels( + &self, + x: i32, + y: i32, + width: i32, + height: i32, + format: u32, + pixel_type: u32, + mut pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>, + ) { + let pixels = + handle_potential_webgl_error!(self.base, pixels.as_mut().ok_or(InvalidValue), return); + + self.read_pixels_into(x, y, width, height, format, pixel_type, pixels, 0) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.10 + fn ReadPixels_( + &self, + x: i32, + y: i32, + width: i32, + height: i32, + format: u32, + pixel_type: u32, + dst_byte_offset: i64, + ) { + handle_potential_webgl_error!(self.base, self.base.validate_framebuffer(), return); + + let dst = match self.bound_pixel_pack_buffer.get() { + Some(buffer) => buffer, + None => return self.base.webgl_error(InvalidOperation), + }; + + if dst_byte_offset < 0 { + return self.base.webgl_error(InvalidValue); + } + let dst_byte_offset = dst_byte_offset as usize; + if dst_byte_offset > dst.capacity() { + return self.base.webgl_error(InvalidOperation); + } + + let ReadPixelsAllowedFormats { + array_types: _, + channels: bytes_per_pixel, + } = match self.calc_read_pixel_formats(pixel_type, format) { + Ok(result) => result, + Err(error) => return self.base.webgl_error(error), + }; + if format != constants::RGBA || pixel_type != constants::UNSIGNED_BYTE { + return self.base.webgl_error(InvalidOperation); + } + + let ReadPixelsSizes { + row_stride: _, + skipped_bytes, + size, + } = match self.calc_read_pixel_sizes(width, height, bytes_per_pixel) { + Ok(result) => result, + Err(error) => return self.base.webgl_error(error), + }; + let dst_end = dst_byte_offset + skipped_bytes + size; + if dst.capacity() < dst_end { + return self.base.webgl_error(InvalidOperation); + } + + { + let (fb_width, fb_height) = handle_potential_webgl_error!( + self.base, + self.base + .get_current_framebuffer_size() + .ok_or(InvalidOperation), + return + ); + let src_origin = Point2D::new(x, y); + let src_size = Size2D::new(width as u32, height as u32); + let fb_size = Size2D::new(fb_width as u32, fb_height as u32); + if pixels::clip(src_origin, src_size.to_u64(), fb_size.to_u64()).is_none() { + return; + } + } + let src_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height)); + + self.base.send_command(WebGLCommand::PixelStorei( + constants::PACK_ALIGNMENT, + self.base.get_texture_packing_alignment() as _, + )); + self.base.send_command(WebGLCommand::PixelStorei( + constants::PACK_ROW_LENGTH, + self.texture_pack_row_length.get() as _, + )); + self.base.send_command(WebGLCommand::PixelStorei( + constants::PACK_SKIP_ROWS, + self.texture_pack_skip_rows.get() as _, + )); + self.base.send_command(WebGLCommand::PixelStorei( + constants::PACK_SKIP_PIXELS, + self.texture_pack_skip_pixels.get() as _, + )); + self.base.send_command(WebGLCommand::ReadPixelsPP( + src_rect, + format, + pixel_type, + dst_byte_offset, + )); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.10 + #[allow(unsafe_code)] + fn ReadPixels__( + &self, + x: i32, + y: i32, + width: i32, + height: i32, + format: u32, + pixel_type: u32, + mut dst: CustomAutoRooterGuard<ArrayBufferView>, + dst_elem_offset: u32, + ) { + self.read_pixels_into( + x, + y, + width, + height, + format, + pixel_type, + &mut dst, + dst_elem_offset, + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn SampleCoverage(&self, value: f32, invert: bool) { + self.base.SampleCoverage(value, invert) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4 + fn Scissor(&self, x: i32, y: i32, width: i32, height: i32) { + self.base.Scissor(x, y, width, height) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn StencilFunc(&self, func: u32, ref_: i32, mask: u32) { + self.base.StencilFunc(func, ref_, mask) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn StencilFuncSeparate(&self, face: u32, func: u32, ref_: i32, mask: u32) { + self.base.StencilFuncSeparate(face, func, ref_, mask) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn StencilMask(&self, mask: u32) { + self.base.StencilMask(mask) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn StencilMaskSeparate(&self, face: u32, mask: u32) { + self.base.StencilMaskSeparate(face, mask) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn StencilOp(&self, fail: u32, zfail: u32, zpass: u32) { + self.base.StencilOp(fail, zfail, zpass) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 + fn StencilOpSeparate(&self, face: u32, fail: u32, zfail: u32, zpass: u32) { + self.base.StencilOpSeparate(face, fail, zfail, zpass) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn LinkProgram(&self, program: &WebGLProgram) { + self.base.LinkProgram(program) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn ShaderSource(&self, shader: &WebGLShader, source: DOMString) { + self.base.ShaderSource(shader, source) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn GetShaderSource(&self, shader: &WebGLShader) -> Option<DOMString> { + self.base.GetShaderSource(shader) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform1f(&self, location: Option<&WebGLUniformLocation>, val: f32) { + self.base.Uniform1f(location, val) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform1i(&self, location: Option<&WebGLUniformLocation>, val: i32) { + self.base.Uniform1i(location, val) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform1iv( + &self, + location: Option<&WebGLUniformLocation>, + v: Int32ArrayOrLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.uniform1iv(location, v, src_offset, src_length) + } + + // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn Uniform1ui(&self, location: Option<&WebGLUniformLocation>, val: u32) { + self.base.with_location(location, |location| { + match location.type_() { + constants::BOOL | constants::UNSIGNED_INT => (), + _ => return Err(InvalidOperation), + } + self.base + .send_command(WebGLCommand::Uniform1ui(location.id(), val)); + Ok(()) + }); + } + + // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn Uniform1uiv( + &self, + location: Option<&WebGLUniformLocation>, + val: Uint32ArrayOrUnsignedLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::BOOL | + constants::UNSIGNED_INT | + constants::SAMPLER_2D | + constants::SAMPLER_CUBE => {}, + _ => return Err(InvalidOperation), + } + + let val = self.uniform_vec_section_uint(val, src_offset, src_length, 1, location)?; + + match location.type_() { + constants::SAMPLER_2D | constants::SAMPLER_CUBE => { + for &v in val + .iter() + .take(cmp::min(location.size().unwrap_or(1) as usize, val.len())) + { + if v >= self.base.limits().max_combined_texture_image_units { + return Err(InvalidValue); + } + } + }, + _ => {}, + } + self.base + .send_command(WebGLCommand::Uniform1uiv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform1fv( + &self, + location: Option<&WebGLUniformLocation>, + v: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.uniform1fv(location, v, src_offset, src_length); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform2f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32) { + self.base.Uniform2f(location, x, y) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform2fv( + &self, + location: Option<&WebGLUniformLocation>, + v: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.uniform2fv(location, v, src_offset, src_length); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform2i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32) { + self.base.Uniform2i(location, x, y) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform2iv( + &self, + location: Option<&WebGLUniformLocation>, + v: Int32ArrayOrLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.uniform2iv(location, v, src_offset, src_length) + } + + // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn Uniform2ui(&self, location: Option<&WebGLUniformLocation>, x: u32, y: u32) { + self.base.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC2 | constants::UNSIGNED_INT_VEC2 => {}, + _ => return Err(InvalidOperation), + } + self.base + .send_command(WebGLCommand::Uniform2ui(location.id(), x, y)); + Ok(()) + }); + } + + // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn Uniform2uiv( + &self, + location: Option<&WebGLUniformLocation>, + val: Uint32ArrayOrUnsignedLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC2 | constants::UNSIGNED_INT_VEC2 => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_uint(val, src_offset, src_length, 2, location)?; + self.base + .send_command(WebGLCommand::Uniform2uiv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform3f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32, z: f32) { + self.base.Uniform3f(location, x, y, z) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform3fv( + &self, + location: Option<&WebGLUniformLocation>, + v: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.uniform3fv(location, v, src_offset, src_length); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform3i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32, z: i32) { + self.base.Uniform3i(location, x, y, z) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform3iv( + &self, + location: Option<&WebGLUniformLocation>, + v: Int32ArrayOrLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.uniform3iv(location, v, src_offset, src_length) + } + + // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn Uniform3ui(&self, location: Option<&WebGLUniformLocation>, x: u32, y: u32, z: u32) { + self.base.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC3 | constants::UNSIGNED_INT_VEC3 => {}, + _ => return Err(InvalidOperation), + } + self.base + .send_command(WebGLCommand::Uniform3ui(location.id(), x, y, z)); + Ok(()) + }); + } + + // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn Uniform3uiv( + &self, + location: Option<&WebGLUniformLocation>, + val: Uint32ArrayOrUnsignedLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC3 | constants::UNSIGNED_INT_VEC3 => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_uint(val, src_offset, src_length, 3, location)?; + self.base + .send_command(WebGLCommand::Uniform3uiv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform4i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32, z: i32, w: i32) { + self.base.Uniform4i(location, x, y, z, w) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform4iv( + &self, + location: Option<&WebGLUniformLocation>, + v: Int32ArrayOrLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.uniform4iv(location, v, src_offset, src_length) + } + + // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn Uniform4ui(&self, location: Option<&WebGLUniformLocation>, x: u32, y: u32, z: u32, w: u32) { + self.base.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC4 | constants::UNSIGNED_INT_VEC4 => {}, + _ => return Err(InvalidOperation), + } + self.base + .send_command(WebGLCommand::Uniform4ui(location.id(), x, y, z, w)); + Ok(()) + }); + } + + // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn Uniform4uiv( + &self, + location: Option<&WebGLUniformLocation>, + val: Uint32ArrayOrUnsignedLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC4 | constants::UNSIGNED_INT_VEC4 => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_uint(val, src_offset, src_length, 4, location)?; + self.base + .send_command(WebGLCommand::Uniform4uiv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform4f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32, z: f32, w: f32) { + self.base.Uniform4f(location, x, y, z, w) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn Uniform4fv( + &self, + location: Option<&WebGLUniformLocation>, + v: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.uniform4fv(location, v, src_offset, src_length); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn UniformMatrix2fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + v: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base + .uniform_matrix_2fv(location, transpose, v, src_offset, src_length) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn UniformMatrix3fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + v: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base + .uniform_matrix_3fv(location, transpose, v, src_offset, src_length) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn UniformMatrix4fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + v: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base + .uniform_matrix_4fv(location, transpose, v, src_offset, src_length) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn UniformMatrix3x2fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::FLOAT_MAT3x2 => {}, + _ => return Err(InvalidOperation), + } + let val = self.base.uniform_matrix_section( + val, + src_offset, + src_length, + transpose, + 3 * 2, + location, + )?; + self.base + .send_command(WebGLCommand::UniformMatrix3x2fv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn UniformMatrix4x2fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::FLOAT_MAT4x2 => {}, + _ => return Err(InvalidOperation), + } + let val = self.base.uniform_matrix_section( + val, + src_offset, + src_length, + transpose, + 4 * 2, + location, + )?; + self.base + .send_command(WebGLCommand::UniformMatrix4x2fv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn UniformMatrix2x3fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::FLOAT_MAT2x3 => {}, + _ => return Err(InvalidOperation), + } + let val = self.base.uniform_matrix_section( + val, + src_offset, + src_length, + transpose, + 2 * 3, + location, + )?; + self.base + .send_command(WebGLCommand::UniformMatrix2x3fv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn UniformMatrix4x3fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::FLOAT_MAT4x3 => {}, + _ => return Err(InvalidOperation), + } + let val = self.base.uniform_matrix_section( + val, + src_offset, + src_length, + transpose, + 4 * 3, + location, + )?; + self.base + .send_command(WebGLCommand::UniformMatrix4x3fv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn UniformMatrix2x4fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::FLOAT_MAT2x4 => {}, + _ => return Err(InvalidOperation), + } + let val = self.base.uniform_matrix_section( + val, + src_offset, + src_length, + transpose, + 2 * 4, + location, + )?; + self.base + .send_command(WebGLCommand::UniformMatrix2x4fv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn UniformMatrix3x4fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.base.with_location(location, |location| { + match location.type_() { + constants::FLOAT_MAT3x4 => {}, + _ => return Err(InvalidOperation), + } + let val = self.base.uniform_matrix_section( + val, + src_offset, + src_length, + transpose, + 3 * 4, + location, + )?; + self.base + .send_command(WebGLCommand::UniformMatrix3x4fv(location.id(), val)); + Ok(()) + }); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + #[allow(unsafe_code)] + fn GetUniform( + &self, + cx: JSContext, + program: &WebGLProgram, + location: &WebGLUniformLocation, + ) -> JSVal { + handle_potential_webgl_error!( + self.base, + self.base.uniform_check_program(program, location), + return NullValue() + ); + + let triple = (&*self.base, program.id(), location.id()); + + match location.type_() { + constants::UNSIGNED_INT => { + UInt32Value(uniform_get(triple, WebGLCommand::GetUniformUint)) + }, + constants::UNSIGNED_INT_VEC2 => unsafe { + uniform_typed::<Uint32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformUint2)) + }, + constants::UNSIGNED_INT_VEC3 => unsafe { + uniform_typed::<Uint32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformUint3)) + }, + constants::UNSIGNED_INT_VEC4 => unsafe { + uniform_typed::<Uint32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformUint4)) + }, + constants::FLOAT_MAT2x3 => unsafe { + uniform_typed::<Float32>( + *cx, + &uniform_get(triple, WebGLCommand::GetUniformFloat2x3), + ) + }, + constants::FLOAT_MAT2x4 => unsafe { + uniform_typed::<Float32>( + *cx, + &uniform_get(triple, WebGLCommand::GetUniformFloat2x4), + ) + }, + constants::FLOAT_MAT3x2 => unsafe { + uniform_typed::<Float32>( + *cx, + &uniform_get(triple, WebGLCommand::GetUniformFloat3x2), + ) + }, + constants::FLOAT_MAT3x4 => unsafe { + uniform_typed::<Float32>( + *cx, + &uniform_get(triple, WebGLCommand::GetUniformFloat3x4), + ) + }, + constants::FLOAT_MAT4x2 => unsafe { + uniform_typed::<Float32>( + *cx, + &uniform_get(triple, WebGLCommand::GetUniformFloat4x2), + ) + }, + constants::FLOAT_MAT4x3 => unsafe { + uniform_typed::<Float32>( + *cx, + &uniform_get(triple, WebGLCommand::GetUniformFloat4x3), + ) + }, + constants::SAMPLER_3D | constants::SAMPLER_2D_ARRAY => { + Int32Value(uniform_get(triple, WebGLCommand::GetUniformInt)) + }, + _ => self.base.GetUniform(cx, program, location), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn UseProgram(&self, program: Option<&WebGLProgram>) { + self.base.UseProgram(program) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn ValidateProgram(&self, program: &WebGLProgram) { + self.base.ValidateProgram(program) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn VertexAttrib1f(&self, indx: u32, x: f32) { + self.base.VertexAttrib1f(indx, x) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn VertexAttrib1fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) { + self.base.VertexAttrib1fv(indx, v) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn VertexAttrib2f(&self, indx: u32, x: f32, y: f32) { + self.base.VertexAttrib2f(indx, x, y) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn VertexAttrib2fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) { + self.base.VertexAttrib2fv(indx, v) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn VertexAttrib3f(&self, indx: u32, x: f32, y: f32, z: f32) { + self.base.VertexAttrib3f(indx, x, y, z) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn VertexAttrib3fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) { + self.base.VertexAttrib3fv(indx, v) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn VertexAttrib4f(&self, indx: u32, x: f32, y: f32, z: f32, w: f32) { + self.base.VertexAttrib4f(indx, x, y, z, w) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn VertexAttrib4fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) { + self.base.VertexAttrib4fv(indx, v) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn VertexAttribI4i(&self, index: u32, x: i32, y: i32, z: i32, w: i32) { + self.vertex_attrib_i(index, x, y, z, w) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn VertexAttribI4iv(&self, index: u32, v: Int32ArrayOrLongSequence) { + let values = match v { + Int32ArrayOrLongSequence::Int32Array(v) => v.to_vec(), + Int32ArrayOrLongSequence::LongSequence(v) => v, + }; + if values.len() < 4 { + return self.base.webgl_error(InvalidValue); + } + self.vertex_attrib_i(index, values[0], values[1], values[2], values[3]); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn VertexAttribI4ui(&self, index: u32, x: u32, y: u32, z: u32, w: u32) { + self.vertex_attrib_u(index, x, y, z, w) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn VertexAttribI4uiv(&self, index: u32, v: Uint32ArrayOrUnsignedLongSequence) { + let values = match v { + Uint32ArrayOrUnsignedLongSequence::Uint32Array(v) => v.to_vec(), + Uint32ArrayOrUnsignedLongSequence::UnsignedLongSequence(v) => v, + }; + if values.len() < 4 { + return self.base.webgl_error(InvalidValue); + } + self.vertex_attrib_u(index, values[0], values[1], values[2], values[3]); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn VertexAttribPointer( + &self, + attrib_id: u32, + size: i32, + data_type: u32, + normalized: bool, + stride: i32, + offset: i64, + ) { + self.base + .VertexAttribPointer(attrib_id, size, data_type, normalized, stride, offset) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8 + fn VertexAttribIPointer(&self, index: u32, size: i32, type_: u32, stride: i32, offset: i64) { + match type_ { + constants::BYTE | + constants::UNSIGNED_BYTE | + constants::SHORT | + constants::UNSIGNED_SHORT | + constants::INT | + constants::UNSIGNED_INT => {}, + _ => return self.base.webgl_error(InvalidEnum), + }; + self.base + .VertexAttribPointer(index, size, type_, false, stride, offset) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4 + fn Viewport(&self, x: i32, y: i32, width: i32, height: i32) { + self.base.Viewport(x, y, width, height) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexImage2D( + &self, + target: u32, + level: i32, + internal_format: i32, + width: i32, + height: i32, + border: i32, + format: u32, + data_type: u32, + pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>, + ) -> Fallible<()> { + self.base.TexImage2D( + target, + level, + internal_format, + width, + height, + border, + format, + data_type, + pixels, + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexImage2D_( + &self, + target: u32, + level: i32, + internal_format: i32, + format: u32, + data_type: u32, + source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement, + ) -> ErrorResult { + self.base + .TexImage2D_(target, level, internal_format, format, data_type, source) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6 + fn TexImage2D__( + &self, + target: u32, + level: i32, + internalformat: i32, + width: i32, + height: i32, + border: i32, + format: u32, + type_: u32, + pbo_offset: i64, + ) -> Fallible<()> { + let pixel_unpack_buffer = match self.bound_pixel_unpack_buffer.get() { + Some(pixel_unpack_buffer) => pixel_unpack_buffer, + None => return Ok(self.base.webgl_error(InvalidOperation)), + }; + + if let Some(tf_buffer) = self.bound_transform_feedback_buffer.get() { + if pixel_unpack_buffer == tf_buffer { + return Ok(self.base.webgl_error(InvalidOperation)); + } + } + + if pbo_offset < 0 || pbo_offset as usize > pixel_unpack_buffer.capacity() { + return Ok(self.base.webgl_error(InvalidValue)); + } + + let unpacking_alignment = self.base.texture_unpacking_alignment(); + + let validator = TexImage2DValidator::new( + &self.base, + target, + level, + internalformat as u32, + width, + height, + border, + format, + type_, + ); + + let TexImage2DValidatorResult { + texture, + target, + width, + height, + level, + border, + internal_format, + format, + data_type, + } = match validator.validate() { + Ok(result) => result, + Err(_) => return Ok(()), + }; + + self.base.tex_image_2d( + &texture, + target, + data_type, + internal_format, + format, + level, + border, + unpacking_alignment, + Size2D::new(width, height), + TexSource::BufferOffset(pbo_offset), + ); + + Ok(()) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6 + fn TexImage2D___( + &self, + target: u32, + level: i32, + internalformat: i32, + width: i32, + height: i32, + border: i32, + format: u32, + type_: u32, + source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement, + ) -> Fallible<()> { + if self.bound_pixel_unpack_buffer.get().is_some() { + return Ok(self.base.webgl_error(InvalidOperation)); + } + + let validator = TexImage2DValidator::new( + &self.base, + target, + level, + internalformat as u32, + width, + height, + border, + format, + type_, + ); + + let TexImage2DValidatorResult { + texture, + target, + width: _, + height: _, + level, + border, + internal_format, + format, + data_type, + } = match validator.validate() { + Ok(result) => result, + Err(_) => return Ok(()), + }; + + let unpacking_alignment = self.base.texture_unpacking_alignment(); + + let pixels = match self.base.get_image_pixels(source)? { + Some(pixels) => pixels, + None => return Ok(()), + }; + + self.base.tex_image_2d( + &texture, + target, + data_type, + internal_format, + format, + level, + border, + unpacking_alignment, + pixels.size(), + TexSource::Pixels(pixels), + ); + + Ok(()) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6 + #[allow(unsafe_code)] + fn TexImage2D____( + &self, + target: u32, + level: i32, + internalformat: i32, + width: i32, + height: i32, + border: i32, + format: u32, + type_: u32, + src_data: CustomAutoRooterGuard<ArrayBufferView>, + src_offset: u32, + ) -> Fallible<()> { + if self.bound_pixel_unpack_buffer.get().is_some() { + return Ok(self.base.webgl_error(InvalidOperation)); + } + + if type_ == constants::FLOAT_32_UNSIGNED_INT_24_8_REV { + return Ok(self.base.webgl_error(InvalidOperation)); + } + + let validator = TexImage2DValidator::new( + &self.base, + target, + level, + internalformat as u32, + width, + height, + border, + format, + type_, + ); + + let TexImage2DValidatorResult { + texture, + target, + width, + height, + level, + border, + internal_format, + format, + data_type, + } = match validator.validate() { + Ok(result) => result, + Err(_) => return Ok(()), + }; + + let unpacking_alignment = self.base.texture_unpacking_alignment(); + + let src_elem_size = typedarray_elem_size(src_data.get_array_type()); + let src_byte_offset = src_offset as usize * src_elem_size; + + if src_data.len() <= src_byte_offset { + return Ok(self.base.webgl_error(InvalidOperation)); + } + + let buff = IpcSharedMemory::from_bytes(unsafe { &src_data.as_slice()[src_byte_offset..] }); + + let expected_byte_length = match { + self.base.validate_tex_image_2d_data( + width, + height, + format, + data_type, + unpacking_alignment, + Some(&*src_data), + ) + } { + Ok(byte_length) => byte_length, + Err(()) => return Ok(()), + }; + + if expected_byte_length as usize > buff.len() { + return Ok(self.base.webgl_error(InvalidOperation)); + } + + let size = Size2D::new(width, height); + + self.base.tex_image_2d( + &texture, + target, + data_type, + internal_format, + format, + level, + border, + unpacking_alignment, + size, + TexSource::Pixels(TexPixels::from_array(buff, size)), + ); + + Ok(()) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexSubImage2D( + &self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + width: i32, + height: i32, + format: u32, + data_type: u32, + pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>, + ) -> Fallible<()> { + self.base.TexSubImage2D( + target, level, xoffset, yoffset, width, height, format, data_type, pixels, + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexSubImage2D_( + &self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + format: u32, + data_type: u32, + source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement, + ) -> ErrorResult { + self.base + .TexSubImage2D_(target, level, xoffset, yoffset, format, data_type, source) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexParameterf(&self, target: u32, name: u32, value: f32) { + self.base.TexParameterf(target, name, value) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn TexParameteri(&self, target: u32, name: u32, value: i32) { + self.base.TexParameteri(target, name, value) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn CheckFramebufferStatus(&self, target: u32) -> u32 { + let fb_slot = match target { + constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => { + self.base.get_draw_framebuffer_slot() + }, + constants::READ_FRAMEBUFFER => &self.base.get_read_framebuffer_slot(), + _ => { + self.base.webgl_error(InvalidEnum); + return 0; + }, + }; + match fb_slot.get() { + Some(fb) => fb.check_status(), + None => constants::FRAMEBUFFER_COMPLETE, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 + fn RenderbufferStorage(&self, target: u32, internal_format: u32, width: i32, height: i32) { + self.base + .RenderbufferStorage(target, internal_format, width, height) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn FramebufferRenderbuffer( + &self, + target: u32, + attachment: u32, + renderbuffertarget: u32, + rb: Option<&WebGLRenderbuffer>, + ) { + if let Some(rb) = rb { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(rb), return); + } + + let fb_slot = match target { + constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => { + self.base.get_draw_framebuffer_slot() + }, + constants::READ_FRAMEBUFFER => &self.base.get_read_framebuffer_slot(), + _ => return self.base.webgl_error(InvalidEnum), + }; + + if renderbuffertarget != constants::RENDERBUFFER { + return self.base.webgl_error(InvalidEnum); + } + + match fb_slot.get() { + Some(fb) => match attachment { + constants::DEPTH_STENCIL_ATTACHMENT => { + handle_potential_webgl_error!( + self.base, + fb.renderbuffer(constants::DEPTH_ATTACHMENT, rb) + ); + handle_potential_webgl_error!( + self.base, + fb.renderbuffer(constants::STENCIL_ATTACHMENT, rb) + ); + }, + _ => handle_potential_webgl_error!(self.base, fb.renderbuffer(attachment, rb)), + }, + None => self.base.webgl_error(InvalidOperation), + }; + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn FramebufferTexture2D( + &self, + target: u32, + attachment: u32, + textarget: u32, + texture: Option<&WebGLTexture>, + level: i32, + ) { + if let Some(texture) = texture { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(texture), return); + } + + let fb_slot = match target { + constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => { + self.base.get_draw_framebuffer_slot() + }, + constants::READ_FRAMEBUFFER => self.base.get_read_framebuffer_slot(), + _ => return self.base.webgl_error(InvalidEnum), + }; + match fb_slot.get() { + Some(fb) => handle_potential_webgl_error!( + self.base, + fb.texture2d(attachment, textarget, texture, level) + ), + None => self.base.webgl_error(InvalidOperation), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn GetAttachedShaders(&self, program: &WebGLProgram) -> Option<Vec<DomRoot<WebGLShader>>> { + self.base.GetAttachedShaders(program) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9 + fn DrawArraysInstanced(&self, mode: u32, first: i32, count: i32, primcount: i32) { + self.validate_uniform_block_for_draw(); + self.validate_vertex_attribs_for_draw(); + handle_potential_webgl_error!( + self.base, + self.base + .draw_arrays_instanced(mode, first, count, primcount) + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9 + fn DrawElementsInstanced( + &self, + mode: u32, + count: i32, + type_: u32, + offset: i64, + primcount: i32, + ) { + self.validate_uniform_block_for_draw(); + self.validate_vertex_attribs_for_draw(); + handle_potential_webgl_error!( + self.base, + self.base + .draw_elements_instanced(mode, count, type_, offset, primcount) + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9 + fn DrawRangeElements( + &self, + mode: u32, + start: u32, + end: u32, + count: i32, + type_: u32, + offset: i64, + ) { + if end < start { + self.base.webgl_error(InvalidValue); + return; + } + self.validate_uniform_block_for_draw(); + self.validate_vertex_attribs_for_draw(); + handle_potential_webgl_error!( + self.base, + self.base + .draw_elements_instanced(mode, count, type_, offset, 1) + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9 + fn VertexAttribDivisor(&self, index: u32, divisor: u32) { + self.base.vertex_attrib_divisor(index, divisor); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12 + fn CreateQuery(&self) -> Option<DomRoot<WebGLQuery>> { + Some(WebGLQuery::new(&self.base)) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12 + #[cfg_attr(rustfmt, rustfmt_skip)] + fn DeleteQuery(&self, query: Option<&WebGLQuery>) { + if let Some(query) = query { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(query), return); + + if let Some(query_target) = query.target() { + let slot = match query_target { + constants::ANY_SAMPLES_PASSED | + constants::ANY_SAMPLES_PASSED_CONSERVATIVE => { + &self.occlusion_query + }, + constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => { + &self.primitives_query + }, + _ => unreachable!(), + }; + if let Some(stored_query) = slot.get() { + if stored_query.target() == query.target() { + slot.set(None); + } + } + } + + query.delete(Operation::Infallible); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12 + fn IsQuery(&self, query: Option<&WebGLQuery>) -> bool { + match query { + Some(query) => self.base.validate_ownership(query).is_ok() && query.is_valid(), + None => false, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13 + fn CreateSampler(&self) -> Option<DomRoot<WebGLSampler>> { + Some(WebGLSampler::new(&self.base)) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13 + fn DeleteSampler(&self, sampler: Option<&WebGLSampler>) { + if let Some(sampler) = sampler { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(sampler), return); + for slot in self.samplers.iter() { + if slot.get().map_or(false, |s| sampler == &*s) { + slot.set(None); + } + } + sampler.delete(Operation::Infallible); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13 + fn IsSampler(&self, sampler: Option<&WebGLSampler>) -> bool { + match sampler { + Some(sampler) => self.base.validate_ownership(sampler).is_ok() && sampler.is_valid(), + None => false, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12 + #[cfg_attr(rustfmt, rustfmt_skip)] + fn BeginQuery(&self, target: u32, query: &WebGLQuery) { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(query), return); + + let active_query = match target { + constants::ANY_SAMPLES_PASSED | + constants::ANY_SAMPLES_PASSED_CONSERVATIVE => { + &self.occlusion_query + }, + constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => { + &self.primitives_query + }, + _ => { + self.base.webgl_error(InvalidEnum); + return; + }, + }; + if active_query.get().is_some() { + self.base.webgl_error(InvalidOperation); + return; + } + let result = query.begin(&self.base, target); + match result { + Ok(_) => active_query.set(Some(query)), + Err(error) => self.base.webgl_error(error), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12 + #[cfg_attr(rustfmt, rustfmt_skip)] + fn EndQuery(&self, target: u32) { + let active_query = match target { + constants::ANY_SAMPLES_PASSED | + constants::ANY_SAMPLES_PASSED_CONSERVATIVE => { + self.occlusion_query.take() + }, + constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => { + self.primitives_query.take() + }, + _ => { + self.base.webgl_error(InvalidEnum); + return; + }, + }; + match active_query { + None => self.base.webgl_error(InvalidOperation), + Some(query) => { + let result = query.end(&self.base, target); + if let Err(error) = result { + self.base.webgl_error(error); + } + }, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12 + #[cfg_attr(rustfmt, rustfmt_skip)] + fn GetQuery(&self, target: u32, pname: u32) -> Option<DomRoot<WebGLQuery>> { + if pname != constants::CURRENT_QUERY { + self.base.webgl_error(InvalidEnum); + return None; + } + let active_query = match target { + constants::ANY_SAMPLES_PASSED | + constants::ANY_SAMPLES_PASSED_CONSERVATIVE => { + self.occlusion_query.get() + }, + constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => { + self.primitives_query.get() + }, + _ => { + self.base.webgl_error(InvalidEnum); + None + }, + }; + if let Some(query) = active_query.as_ref() { + if query.target() != Some(target) { + return None; + } + } + active_query + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12 + #[cfg_attr(rustfmt, rustfmt_skip)] + fn GetQueryParameter(&self, _cx: JSContext, query: &WebGLQuery, pname: u32) -> JSVal { + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(query), + return NullValue() + ); + match query.get_parameter(&self.base, pname) { + Ok(value) => match pname { + constants::QUERY_RESULT => UInt32Value(value), + constants::QUERY_RESULT_AVAILABLE => BooleanValue(value != 0), + _ => unreachable!(), + }, + Err(error) => { + self.base.webgl_error(error); + NullValue() + }, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14 + fn FenceSync(&self, condition: u32, flags: u32) -> Option<DomRoot<WebGLSync>> { + if flags != 0 { + self.base.webgl_error(InvalidValue); + return None; + } + if condition != constants::SYNC_GPU_COMMANDS_COMPLETE { + self.base.webgl_error(InvalidEnum); + return None; + } + + Some(WebGLSync::new(&self.base)) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14 + fn IsSync(&self, sync: Option<&WebGLSync>) -> bool { + match sync { + Some(sync) => { + if !sync.is_valid() { + return false; + } + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(sync), + return false + ); + let (sender, receiver) = webgl_channel().unwrap(); + self.base + .send_command(WebGLCommand::IsSync(sync.id(), sender)); + receiver.recv().unwrap() + }, + None => false, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14 + fn ClientWaitSync(&self, sync: &WebGLSync, flags: u32, timeout: u64) -> u32 { + if !sync.is_valid() { + self.base.webgl_error(InvalidOperation); + return constants::WAIT_FAILED; + } + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(sync), + return constants::WAIT_FAILED + ); + if flags != 0 && flags != constants::SYNC_FLUSH_COMMANDS_BIT { + self.base.webgl_error(InvalidValue); + return constants::WAIT_FAILED; + } + if timeout > self.base.limits().max_client_wait_timeout_webgl.as_nanos() as u64 { + self.base.webgl_error(InvalidOperation); + return constants::WAIT_FAILED; + } + + match sync.client_wait_sync(&self.base, flags, timeout) { + Some(status) => status, + None => constants::WAIT_FAILED, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14 + fn WaitSync(&self, sync: &WebGLSync, flags: u32, timeout: i64) { + if !sync.is_valid() { + self.base.webgl_error(InvalidOperation); + return; + } + handle_potential_webgl_error!(self.base, self.base.validate_ownership(sync), return); + if flags != 0 { + self.base.webgl_error(InvalidValue); + return; + } + if timeout != constants::TIMEOUT_IGNORED { + self.base.webgl_error(InvalidValue); + return; + } + + self.base + .send_command(WebGLCommand::WaitSync(sync.id(), flags, timeout)); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14 + fn GetSyncParameter(&self, _cx: JSContext, sync: &WebGLSync, pname: u32) -> JSVal { + if !sync.is_valid() { + self.base.webgl_error(InvalidOperation); + return NullValue(); + } + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(sync), + return NullValue() + ); + match pname { + constants::OBJECT_TYPE | constants::SYNC_CONDITION | constants::SYNC_FLAGS => { + let (sender, receiver) = webgl_channel().unwrap(); + self.base + .send_command(WebGLCommand::GetSyncParameter(sync.id(), pname, sender)); + UInt32Value(receiver.recv().unwrap()) + }, + constants::SYNC_STATUS => match sync.get_sync_status(pname, &self.base) { + Some(status) => UInt32Value(status), + None => UInt32Value(constants::UNSIGNALED), + }, + _ => { + self.base.webgl_error(InvalidEnum); + NullValue() + }, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14 + fn DeleteSync(&self, sync: Option<&WebGLSync>) { + if let Some(sync) = sync { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(sync), return); + sync.delete(Operation::Infallible); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13 + fn BindSampler(&self, unit: u32, sampler: Option<&WebGLSampler>) { + if let Some(sampler) = sampler { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(sampler), return); + + if unit as usize >= self.samplers.len() { + self.base.webgl_error(InvalidValue); + return; + } + + let result = sampler.bind(&self.base, unit); + match result { + Ok(_) => self.samplers[unit as usize].set(Some(sampler)), + Err(error) => self.base.webgl_error(error), + } + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.17 + fn BindVertexArray(&self, array: Option<&WebGLVertexArrayObject>) { + self.base.bind_vertex_array_webgl2(array); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13 + fn SamplerParameteri(&self, sampler: &WebGLSampler, pname: u32, param: i32) { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(sampler), return); + let param = WebGLSamplerValue::GLenum(param as u32); + let result = sampler.set_parameter(&self.base, pname, param); + if let Err(error) = result { + self.base.webgl_error(error); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13 + fn SamplerParameterf(&self, sampler: &WebGLSampler, pname: u32, param: f32) { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(sampler), return); + let param = WebGLSamplerValue::Float(param); + let result = sampler.set_parameter(&self.base, pname, param); + if let Err(error) = result { + self.base.webgl_error(error); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13 + fn GetSamplerParameter(&self, _cx: JSContext, sampler: &WebGLSampler, pname: u32) -> JSVal { + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(sampler), + return NullValue() + ); + match sampler.get_parameter(&self.base, pname) { + Ok(value) => match value { + WebGLSamplerValue::GLenum(value) => UInt32Value(value), + WebGLSamplerValue::Float(value) => DoubleValue(value as f64), + }, + Err(error) => { + self.base.webgl_error(error); + NullValue() + }, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + fn CreateTransformFeedback(&self) -> Option<DomRoot<WebGLTransformFeedback>> { + Some(WebGLTransformFeedback::new(&self.base)) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + fn DeleteTransformFeedback(&self, tf: Option<&WebGLTransformFeedback>) { + if let Some(tf) = tf { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(tf), return); + if tf.is_active() { + self.base.webgl_error(InvalidOperation); + return; + } + tf.delete(Operation::Infallible); + self.current_transform_feedback.set(None); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + fn IsTransformFeedback(&self, tf: Option<&WebGLTransformFeedback>) -> bool { + match tf { + Some(tf) => { + if !tf.is_valid() { + return false; + } + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(tf), + return false + ); + let (sender, receiver) = webgl_channel().unwrap(); + self.base + .send_command(WebGLCommand::IsTransformFeedback(tf.id(), sender)); + receiver.recv().unwrap() + }, + None => false, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + fn BindTransformFeedback(&self, target: u32, tf: Option<&WebGLTransformFeedback>) { + if target != constants::TRANSFORM_FEEDBACK { + self.base.webgl_error(InvalidEnum); + return; + } + match tf { + Some(transform_feedback) => { + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(transform_feedback), + return + ); + if !transform_feedback.is_valid() { + self.base.webgl_error(InvalidOperation); + return; + } + if let Some(current_tf) = self.current_transform_feedback.get() { + if current_tf.is_active() && !current_tf.is_paused() { + self.base.webgl_error(InvalidOperation); + return; + } + } + transform_feedback.bind(&self.base, target); + self.current_transform_feedback + .set(Some(transform_feedback)); + }, + None => self + .base + .send_command(WebGLCommand::BindTransformFeedback(target, 0)), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + #[allow(non_snake_case)] + fn BeginTransformFeedback(&self, primitiveMode: u32) { + match primitiveMode { + constants::POINTS | constants::LINES | constants::TRIANGLES => {}, + _ => { + self.base.webgl_error(InvalidEnum); + return; + }, + }; + let current_tf = match self.current_transform_feedback.get() { + Some(current_tf) => current_tf, + None => { + self.base.webgl_error(InvalidOperation); + return; + }, + }; + if current_tf.is_active() { + self.base.webgl_error(InvalidOperation); + return; + }; + let program = match self.base.current_program() { + Some(program) => program, + None => { + self.base.webgl_error(InvalidOperation); + return; + }, + }; + if !program.is_linked() || program.transform_feedback_varyings_length() == 0 { + self.base.webgl_error(InvalidOperation); + return; + }; + current_tf.begin(&self.base, primitiveMode); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + fn EndTransformFeedback(&self) { + if let Some(current_tf) = self.current_transform_feedback.get() { + if !current_tf.is_active() { + self.base.webgl_error(InvalidOperation); + return; + } + current_tf.end(&self.base); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + fn ResumeTransformFeedback(&self) { + if let Some(current_tf) = self.current_transform_feedback.get() { + if !current_tf.is_active() || !current_tf.is_paused() { + self.base.webgl_error(InvalidOperation); + return; + } + current_tf.resume(&self.base); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + fn PauseTransformFeedback(&self) { + if let Some(current_tf) = self.current_transform_feedback.get() { + if !current_tf.is_active() || current_tf.is_paused() { + self.base.webgl_error(InvalidOperation); + return; + } + current_tf.pause(&self.base); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + #[allow(non_snake_case)] + fn TransformFeedbackVaryings( + &self, + program: &WebGLProgram, + varyings: Vec<DOMString>, + bufferMode: u32, + ) { + handle_potential_webgl_error!(self.base, program.validate(), return); + let strs = varyings + .iter() + .map(|name| String::from(name.to_owned())) + .collect::<Vec<String>>(); + match bufferMode { + constants::INTERLEAVED_ATTRIBS => { + self.base + .send_command(WebGLCommand::TransformFeedbackVaryings( + program.id(), + strs, + bufferMode, + )); + }, + constants::SEPARATE_ATTRIBS => { + let max_tf_sp_att = + self.base.limits().max_transform_feedback_separate_attribs as usize; + if strs.len() >= max_tf_sp_att { + self.base.webgl_error(InvalidValue); + return; + } + self.base + .send_command(WebGLCommand::TransformFeedbackVaryings( + program.id(), + strs, + bufferMode, + )); + }, + _ => self.base.webgl_error(InvalidEnum), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 + fn GetTransformFeedbackVarying( + &self, + program: &WebGLProgram, + index: u32, + ) -> Option<DomRoot<WebGLActiveInfo>> { + handle_potential_webgl_error!(self.base, program.validate(), return None); + if index >= program.transform_feedback_varyings_length() as u32 { + self.base.webgl_error(InvalidValue); + return None; + } + + let (sender, receiver) = webgl_channel().unwrap(); + self.base + .send_command(WebGLCommand::GetTransformFeedbackVarying( + program.id(), + index, + sender, + )); + let (size, ty, name) = receiver.recv().unwrap(); + Some(WebGLActiveInfo::new( + self.base.global().as_window(), + size, + ty, + DOMString::from(name), + )) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16 + fn BindBufferBase(&self, target: u32, index: u32, buffer: Option<&WebGLBuffer>) { + let (generic_slot, indexed_bindings) = match target { + constants::TRANSFORM_FEEDBACK_BUFFER => ( + &self.bound_transform_feedback_buffer, + &self.indexed_transform_feedback_buffer_bindings, + ), + constants::UNIFORM_BUFFER => ( + &self.bound_uniform_buffer, + &self.indexed_uniform_buffer_bindings, + ), + _ => return self.base.webgl_error(InvalidEnum), + }; + let indexed_binding = match indexed_bindings.get(index as usize) { + Some(slot) => slot, + None => return self.base.webgl_error(InvalidValue), + }; + + if let Some(buffer) = buffer { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(buffer), return); + + if buffer.is_marked_for_deletion() { + return self.base.webgl_error(InvalidOperation); + } + handle_potential_webgl_error!(self.base, buffer.set_target_maybe(target), return); + + // for both the generic and the indexed bindings + buffer.increment_attached_counter(); + buffer.increment_attached_counter(); + } + + self.base.send_command(WebGLCommand::BindBufferBase( + target, + index, + buffer.map(|b| b.id()), + )); + + for slot in &[&generic_slot, &indexed_binding.buffer] { + if let Some(old) = slot.get() { + old.decrement_attached_counter(Operation::Infallible); + } + slot.set(buffer); + } + indexed_binding.start.set(0); + indexed_binding.size.set(0); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16 + fn BindBufferRange( + &self, + target: u32, + index: u32, + buffer: Option<&WebGLBuffer>, + offset: i64, + size: i64, + ) { + let (generic_slot, indexed_bindings) = match target { + constants::TRANSFORM_FEEDBACK_BUFFER => ( + &self.bound_transform_feedback_buffer, + &self.indexed_transform_feedback_buffer_bindings, + ), + constants::UNIFORM_BUFFER => ( + &self.bound_uniform_buffer, + &self.indexed_uniform_buffer_bindings, + ), + _ => return self.base.webgl_error(InvalidEnum), + }; + let indexed_binding = match indexed_bindings.get(index as usize) { + Some(slot) => slot, + None => return self.base.webgl_error(InvalidValue), + }; + + if offset < 0 || size < 0 { + return self.base.webgl_error(InvalidValue); + } + if buffer.is_some() && size == 0 { + return self.base.webgl_error(InvalidValue); + } + + match target { + constants::TRANSFORM_FEEDBACK_BUFFER => { + if size % 4 != 0 && offset % 4 != 0 { + return self.base.webgl_error(InvalidValue); + } + }, + constants::UNIFORM_BUFFER => { + let offset_alignment = self.base.limits().uniform_buffer_offset_alignment; + if offset % offset_alignment as i64 != 0 { + return self.base.webgl_error(InvalidValue); + } + }, + _ => unreachable!(), + } + + if let Some(buffer) = buffer { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(buffer), return); + + if buffer.is_marked_for_deletion() { + return self.base.webgl_error(InvalidOperation); + } + handle_potential_webgl_error!(self.base, buffer.set_target_maybe(target), return); + + // for both the generic and the indexed bindings + buffer.increment_attached_counter(); + buffer.increment_attached_counter(); + } + + self.base.send_command(WebGLCommand::BindBufferRange( + target, + index, + buffer.map(|b| b.id()), + offset, + size, + )); + + for slot in &[&generic_slot, &indexed_binding.buffer] { + if let Some(old) = slot.get() { + old.decrement_attached_counter(Operation::Infallible); + } + slot.set(buffer); + } + indexed_binding.start.set(offset); + indexed_binding.size.set(size); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16 + fn GetUniformIndices(&self, program: &WebGLProgram, names: Vec<DOMString>) -> Option<Vec<u32>> { + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(program), + return None + ); + let indices = handle_potential_webgl_error!( + self.base, + program.get_uniform_indices(names), + return None + ); + Some(indices) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16 + #[allow(unsafe_code)] + fn GetActiveUniforms( + &self, + cx: JSContext, + program: &WebGLProgram, + indices: Vec<u32>, + pname: u32, + ) -> JSVal { + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(program), + return NullValue() + ); + let values = handle_potential_webgl_error!( + self.base, + program.get_active_uniforms(indices, pname), + return NullValue() + ); + + rooted!(in(*cx) let mut rval = UndefinedValue()); + match pname { + constants::UNIFORM_SIZE | + constants::UNIFORM_TYPE | + constants::UNIFORM_BLOCK_INDEX | + constants::UNIFORM_OFFSET | + constants::UNIFORM_ARRAY_STRIDE | + constants::UNIFORM_MATRIX_STRIDE => unsafe { + values.to_jsval(*cx, rval.handle_mut()); + }, + constants::UNIFORM_IS_ROW_MAJOR => unsafe { + let values = values.iter().map(|&v| v != 0).collect::<Vec<_>>(); + values.to_jsval(*cx, rval.handle_mut()); + }, + _ => unreachable!(), + } + rval.get() + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16 + fn GetUniformBlockIndex(&self, program: &WebGLProgram, block_name: DOMString) -> u32 { + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(program), + return constants::INVALID_INDEX + ); + let index = handle_potential_webgl_error!( + self.base, + program.get_uniform_block_index(block_name), + return constants::INVALID_INDEX + ); + index + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16 + #[allow(unsafe_code)] + fn GetActiveUniformBlockParameter( + &self, + cx: JSContext, + program: &WebGLProgram, + block_index: u32, + pname: u32, + ) -> JSVal { + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(program), + return NullValue() + ); + let values = handle_potential_webgl_error!( + self.base, + program.get_active_uniform_block_parameter(block_index, pname), + return NullValue() + ); + match pname { + constants::UNIFORM_BLOCK_BINDING | + constants::UNIFORM_BLOCK_DATA_SIZE | + constants::UNIFORM_BLOCK_ACTIVE_UNIFORMS => { + assert!(values.len() == 1); + UInt32Value(values[0] as u32) + }, + constants::UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES => unsafe { + let values = values.iter().map(|&v| v as u32).collect::<Vec<_>>(); + rooted!(in(*cx) let mut result = ptr::null_mut::<JSObject>()); + let _ = Uint32Array::create(*cx, CreateWith::Slice(&values), result.handle_mut()) + .unwrap(); + ObjectValue(result.get()) + }, + constants::UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER | + constants::UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER => { + assert!(values.len() == 1); + BooleanValue(values[0] != 0) + }, + _ => unreachable!(), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16 + fn GetActiveUniformBlockName( + &self, + program: &WebGLProgram, + block_index: u32, + ) -> Option<DOMString> { + handle_potential_webgl_error!( + self.base, + self.base.validate_ownership(program), + return None + ); + let name = handle_potential_webgl_error!( + self.base, + program.get_active_uniform_block_name(block_index), + return None + ); + Some(DOMString::from(name)) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16 + fn UniformBlockBinding(&self, program: &WebGLProgram, block_index: u32, block_binding: u32) { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(program), return); + + if block_binding >= self.base.limits().max_uniform_buffer_bindings { + return self.base.webgl_error(InvalidValue); + } + + handle_potential_webgl_error!( + self.base, + program.bind_uniform_block(block_index, block_binding), + return + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11 + fn ClearBufferfv( + &self, + buffer: u32, + draw_buffer: i32, + values: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + ) { + let array = match values { + Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => v.to_vec(), + Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, + }; + self.clear_buffer::<f32>( + buffer, + draw_buffer, + &[constants::COLOR, constants::DEPTH], + src_offset, + array, + WebGLCommand::ClearBufferfv, + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11 + fn ClearBufferiv( + &self, + buffer: u32, + draw_buffer: i32, + values: Int32ArrayOrLongSequence, + src_offset: u32, + ) { + let array = match values { + Int32ArrayOrLongSequence::Int32Array(v) => v.to_vec(), + Int32ArrayOrLongSequence::LongSequence(v) => v, + }; + self.clear_buffer::<i32>( + buffer, + draw_buffer, + &[constants::COLOR, constants::STENCIL], + src_offset, + array, + WebGLCommand::ClearBufferiv, + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11 + fn ClearBufferuiv( + &self, + buffer: u32, + draw_buffer: i32, + values: Uint32ArrayOrUnsignedLongSequence, + src_offset: u32, + ) { + let array = match values { + Uint32ArrayOrUnsignedLongSequence::Uint32Array(v) => v.to_vec(), + Uint32ArrayOrUnsignedLongSequence::UnsignedLongSequence(v) => v, + }; + self.clear_buffer::<u32>( + buffer, + draw_buffer, + &[constants::COLOR], + src_offset, + array, + WebGLCommand::ClearBufferuiv, + ) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11 + fn ClearBufferfi(&self, buffer: u32, draw_buffer: i32, depth: f32, stencil: i32) { + if buffer != constants::DEPTH_STENCIL { + return self.base.webgl_error(InvalidEnum); + } + + handle_potential_webgl_error!( + self.base, + self.clearbuffer_array_size(buffer, draw_buffer), + return + ); + + self.base.send_command(WebGLCommand::ClearBufferfi( + buffer, + draw_buffer, + depth, + stencil, + )); + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4 + fn InvalidateFramebuffer(&self, target: u32, attachments: Vec<u32>) { + if !self.valid_fb_attachment_values(target, &attachments) { + return; + } + + self.base + .send_command(WebGLCommand::InvalidateFramebuffer(target, attachments)) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4 + fn InvalidateSubFramebuffer( + &self, + target: u32, + attachments: Vec<u32>, + x: i32, + y: i32, + width: i32, + height: i32, + ) { + if !self.valid_fb_attachment_values(target, &attachments) { + return; + } + + if width < 0 || height < 0 { + return self.base.webgl_error(InvalidValue); + } + + self.base + .send_command(WebGLCommand::InvalidateSubFramebuffer( + target, + attachments, + x, + y, + width, + height, + )) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4 + fn FramebufferTextureLayer( + &self, + target: u32, + attachment: u32, + texture: Option<&WebGLTexture>, + level: i32, + layer: i32, + ) { + if let Some(tex) = texture { + handle_potential_webgl_error!(self.base, self.base.validate_ownership(tex), return); + } + + let fb_slot = match target { + constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => { + self.base.get_draw_framebuffer_slot() + }, + constants::READ_FRAMEBUFFER => self.base.get_read_framebuffer_slot(), + _ => return self.base.webgl_error(InvalidEnum), + }; + + match fb_slot.get() { + Some(fb) => handle_potential_webgl_error!( + self.base, + fb.texture_layer(attachment, texture, level, layer) + ), + None => self.base.webgl_error(InvalidOperation), + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.5 + #[allow(unsafe_code)] + fn GetInternalformatParameter( + &self, + cx: JSContext, + target: u32, + internal_format: u32, + pname: u32, + ) -> JSVal { + if target != constants::RENDERBUFFER { + self.base.webgl_error(InvalidEnum); + return NullValue(); + } + + match handle_potential_webgl_error!( + self.base, + InternalFormatParameter::from_u32(pname), + return NullValue() + ) { + InternalFormatParameter::IntVec(param) => unsafe { + let (sender, receiver) = webgl_channel().unwrap(); + self.base + .send_command(WebGLCommand::GetInternalFormatIntVec( + target, + internal_format, + param, + sender, + )); + + rooted!(in(*cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Int32Array::create( + *cx, + CreateWith::Slice(&receiver.recv().unwrap()), + rval.handle_mut(), + ) + .unwrap(); + ObjectValue(rval.get()) + }, + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.5 + fn RenderbufferStorageMultisample( + &self, + target: u32, + samples: i32, + internal_format: u32, + width: i32, + height: i32, + ) { + self.base + .renderbuffer_storage(target, samples, internal_format, width, height) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4 + fn ReadBuffer(&self, src: u32) { + match src { + constants::BACK | constants::NONE => {}, + _ if self.base.valid_color_attachment_enum(src) => {}, + _ => return self.base.webgl_error(InvalidEnum), + } + + if let Some(fb) = self.base.get_read_framebuffer_slot().get() { + handle_potential_webgl_error!(self.base, fb.set_read_buffer(src), return) + } else { + match src { + constants::NONE | constants::BACK => {}, + _ => return self.base.webgl_error(InvalidOperation), + } + + self.default_fb_readbuffer.set(src); + self.base.send_command(WebGLCommand::ReadBuffer(src)); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11 + fn DrawBuffers(&self, buffers: Vec<u32>) { + if let Some(fb) = self.base.get_draw_framebuffer_slot().get() { + handle_potential_webgl_error!(self.base, fb.set_draw_buffers(buffers), return) + } else { + if buffers.len() != 1 { + return self.base.webgl_error(InvalidOperation); + } + + match buffers[0] { + constants::NONE | constants::BACK => {}, + _ => return self.base.webgl_error(InvalidOperation), + } + + self.default_fb_drawbuffer.set(buffers[0]); + self.base.send_command(WebGLCommand::DrawBuffers(buffers)); + } + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6 + fn TexStorage2D( + &self, + target: u32, + levels: i32, + internal_format: u32, + width: i32, + height: i32, + ) { + self.tex_storage(2, target, levels, internal_format, width, height, 1) + } + + /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6 + fn TexStorage3D( + &self, + target: u32, + levels: i32, + internal_format: u32, + width: i32, + height: i32, + depth: i32, + ) { + self.tex_storage(3, target, levels, internal_format, width, height, depth) + } +} + +impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, WebGL2RenderingContext> { + #[allow(unsafe_code)] + unsafe fn canvas_data_source(self) -> HTMLCanvasDataSource { + let this = &*self.unsafe_get(); + (*this.base.to_layout().unsafe_get()).layout_handle() + } +} diff --git a/components/script/dom/webgl_extensions/ext/angleinstancedarrays.rs b/components/script/dom/webgl_extensions/ext/angleinstancedarrays.rs new file mode 100644 index 00000000000..4d03f25781b --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/angleinstancedarrays.rs @@ -0,0 +1,92 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysConstants; +use crate::dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::WebGLVersion; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct ANGLEInstancedArrays { + reflector_: Reflector, + ctx: Dom<WebGLRenderingContext>, +} + +impl ANGLEInstancedArrays { + fn new_inherited(ctx: &WebGLRenderingContext) -> Self { + Self { + reflector_: Reflector::new(), + ctx: Dom::from_ref(ctx), + } + } +} + +impl WebGLExtension for ANGLEInstancedArrays { + type Extension = Self; + + fn new(ctx: &WebGLRenderingContext) -> DomRoot<Self> { + reflect_dom_object( + Box::new(ANGLEInstancedArrays::new_inherited(ctx)), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_any_gl_extension(&[ + "GL_ANGLE_instanced_arrays", + "GL_ARB_instanced_arrays", + "GL_EXT_instanced_arrays", + "GL_NV_instanced_arrays", + ]) + } + + fn enable(ext: &WebGLExtensions) { + ext.enable_get_vertex_attrib_name( + ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE, + ); + } + + fn name() -> &'static str { + "ANGLE_instanced_arrays" + } +} + +impl ANGLEInstancedArraysMethods for ANGLEInstancedArrays { + // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ + fn DrawArraysInstancedANGLE(&self, mode: u32, first: i32, count: i32, primcount: i32) { + handle_potential_webgl_error!( + self.ctx, + self.ctx + .draw_arrays_instanced(mode, first, count, primcount) + ) + } + + // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ + fn DrawElementsInstancedANGLE( + &self, + mode: u32, + count: i32, + type_: u32, + offset: i64, + primcount: i32, + ) { + handle_potential_webgl_error!( + self.ctx, + self.ctx + .draw_elements_instanced(mode, count, type_, offset, primcount) + ) + } + + fn VertexAttribDivisorANGLE(&self, index: u32, divisor: u32) { + self.ctx.vertex_attrib_divisor(index, divisor); + } +} diff --git a/components/script/dom/webgl_extensions/ext/extblendminmax.rs b/components/script/dom/webgl_extensions/ext/extblendminmax.rs new file mode 100644 index 00000000000..122aac6c861 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/extblendminmax.rs @@ -0,0 +1,47 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::WebGLVersion; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct EXTBlendMinmax { + reflector_: Reflector, +} + +impl EXTBlendMinmax { + fn new_inherited() -> Self { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for EXTBlendMinmax { + type Extension = Self; + + fn new(ctx: &WebGLRenderingContext) -> DomRoot<Self> { + reflect_dom_object(Box::new(Self::new_inherited()), &*ctx.global()) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_gl_extension("GL_EXT_blend_minmax") + } + + fn enable(ext: &WebGLExtensions) { + ext.enable_blend_minmax(); + } + + fn name() -> &'static str { + "EXT_blend_minmax" + } +} diff --git a/components/script/dom/webgl_extensions/ext/extcolorbufferhalffloat.rs b/components/script/dom/webgl_extensions/ext/extcolorbufferhalffloat.rs new file mode 100644 index 00000000000..bb4a630d537 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/extcolorbufferhalffloat.rs @@ -0,0 +1,48 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webgl_extensions::ext::oestexturehalffloat::OESTextureHalfFloat; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::WebGLVersion; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct EXTColorBufferHalfFloat { + reflector_: Reflector, +} + +impl EXTColorBufferHalfFloat { + fn new_inherited() -> EXTColorBufferHalfFloat { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for EXTColorBufferHalfFloat { + type Extension = EXTColorBufferHalfFloat; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<EXTColorBufferHalfFloat> { + reflect_dom_object( + Box::new(EXTColorBufferHalfFloat::new_inherited()), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + OESTextureHalfFloat::is_supported(ext) + } + + fn enable(_ext: &WebGLExtensions) {} + + fn name() -> &'static str { + "EXT_color_buffer_half_float" + } +} diff --git a/components/script/dom/webgl_extensions/ext/extfragdepth.rs b/components/script/dom/webgl_extensions/ext/extfragdepth.rs new file mode 100644 index 00000000000..182b30bf754 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/extfragdepth.rs @@ -0,0 +1,62 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::{WebGLSLVersion, WebGLVersion}; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct EXTFragDepth { + reflector_: Reflector, +} + +impl EXTFragDepth { + fn new_inherited() -> EXTFragDepth { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for EXTFragDepth { + type Extension = Self; + + fn new(ctx: &WebGLRenderingContext) -> DomRoot<Self> { + reflect_dom_object(Box::new(Self::new_inherited()), &*ctx.global()) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + let min_glsl_version = if ext.is_gles() { + WebGLSLVersion { major: 3, minor: 0 } + } else { + WebGLSLVersion { + major: 1, + minor: 10, + } + }; + match ( + ext.is_gles(), + ext.is_min_glsl_version_satisfied(min_glsl_version), + ) { + // ANGLE's shader translator can't translate ESSL1 exts to ESSL3. (bug + // 1524804) + (true, true) => false, + (true, false) => ext.supports_gl_extension("GL_EXT_frag_depth"), + (false, is_min_glsl_version_satisfied) => is_min_glsl_version_satisfied, + } + } + + fn enable(_ext: &WebGLExtensions) {} + + fn name() -> &'static str { + "EXT_frag_depth" + } +} diff --git a/components/script/dom/webgl_extensions/ext/extshadertexturelod.rs b/components/script/dom/webgl_extensions/ext/extshadertexturelod.rs new file mode 100644 index 00000000000..ea68b518dac --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/extshadertexturelod.rs @@ -0,0 +1,46 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::WebGLVersion; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct EXTShaderTextureLod { + reflector_: Reflector, +} + +impl EXTShaderTextureLod { + fn new_inherited() -> Self { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for EXTShaderTextureLod { + type Extension = Self; + + fn new(ctx: &WebGLRenderingContext) -> DomRoot<Self> { + reflect_dom_object(Box::new(Self::new_inherited()), &*ctx.global()) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + // This extension is always available on desktop GL. + !ext.is_gles() || ext.supports_gl_extension("GL_EXT_shader_texture_lod") + } + + fn enable(_ext: &WebGLExtensions) {} + + fn name() -> &'static str { + "EXT_shader_texture_lod" + } +} diff --git a/components/script/dom/webgl_extensions/ext/exttexturefilteranisotropic.rs b/components/script/dom/webgl_extensions/ext/exttexturefilteranisotropic.rs new file mode 100644 index 00000000000..89a4d3ed6a4 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/exttexturefilteranisotropic.rs @@ -0,0 +1,53 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::codegen::Bindings::EXTTextureFilterAnisotropicBinding::EXTTextureFilterAnisotropicConstants; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::WebGLVersion; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct EXTTextureFilterAnisotropic { + reflector_: Reflector, +} + +impl EXTTextureFilterAnisotropic { + fn new_inherited() -> EXTTextureFilterAnisotropic { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for EXTTextureFilterAnisotropic { + type Extension = EXTTextureFilterAnisotropic; + + fn new(ctx: &WebGLRenderingContext) -> DomRoot<Self> { + reflect_dom_object(Box::new(Self::new_inherited()), &*ctx.global()) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_gl_extension("GL_EXT_texture_filter_anisotropic") + } + + fn enable(ext: &WebGLExtensions) { + ext.enable_get_tex_parameter_name( + EXTTextureFilterAnisotropicConstants::TEXTURE_MAX_ANISOTROPY_EXT, + ); + ext.enable_get_parameter_name( + EXTTextureFilterAnisotropicConstants::MAX_TEXTURE_MAX_ANISOTROPY_EXT, + ); + } + + fn name() -> &'static str { + "EXT_texture_filter_anisotropic" + } +} diff --git a/components/script/dom/webgl_extensions/ext/mod.rs b/components/script/dom/webgl_extensions/ext/mod.rs new file mode 100644 index 00000000000..229d09d56f5 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/mod.rs @@ -0,0 +1,23 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; + +pub mod angleinstancedarrays; +pub mod extblendminmax; +pub mod extcolorbufferhalffloat; +pub mod extfragdepth; +pub mod extshadertexturelod; +pub mod exttexturefilteranisotropic; +pub mod oeselementindexuint; +pub mod oesstandardderivatives; +pub mod oestexturefloat; +pub mod oestexturefloatlinear; +pub mod oestexturehalffloat; +pub mod oestexturehalffloatlinear; +pub mod oesvertexarrayobject; +pub mod webglcolorbufferfloat; +pub mod webglcompressedtextureetc1; +pub mod webglcompressedtextures3tc; diff --git a/components/script/dom/webgl_extensions/ext/oeselementindexuint.rs b/components/script/dom/webgl_extensions/ext/oeselementindexuint.rs new file mode 100644 index 00000000000..096c22d3dc5 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/oeselementindexuint.rs @@ -0,0 +1,51 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::WebGLVersion; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct OESElementIndexUint { + reflector_: Reflector, +} + +impl OESElementIndexUint { + fn new_inherited() -> Self { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for OESElementIndexUint { + type Extension = Self; + + fn new(ctx: &WebGLRenderingContext) -> DomRoot<Self> { + reflect_dom_object( + Box::new(OESElementIndexUint::new_inherited()), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + // This extension is always available in desktop OpenGL. + !ext.is_gles() || ext.supports_gl_extension("GL_OES_element_index_uint") + } + + fn enable(ext: &WebGLExtensions) { + ext.enable_element_index_uint(); + } + + fn name() -> &'static str { + "OES_element_index_uint" + } +} diff --git a/components/script/dom/webgl_extensions/ext/oesstandardderivatives.rs b/components/script/dom/webgl_extensions/ext/oesstandardderivatives.rs new file mode 100644 index 00000000000..dfafa7051a5 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/oesstandardderivatives.rs @@ -0,0 +1,56 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::codegen::Bindings::OESStandardDerivativesBinding::OESStandardDerivativesConstants; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::WebGLVersion; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct OESStandardDerivatives { + reflector_: Reflector, +} + +impl OESStandardDerivatives { + fn new_inherited() -> OESStandardDerivatives { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for OESStandardDerivatives { + type Extension = OESStandardDerivatives; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<OESStandardDerivatives> { + reflect_dom_object( + Box::new(OESStandardDerivatives::new_inherited()), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + // The standard derivatives are always available in desktop OpenGL. + !ext.is_gles() || ext.supports_any_gl_extension(&["GL_OES_standard_derivatives"]) + } + + fn enable(ext: &WebGLExtensions) { + ext.enable_hint_target( + OESStandardDerivativesConstants::FRAGMENT_SHADER_DERIVATIVE_HINT_OES, + ); + ext.enable_get_parameter_name( + OESStandardDerivativesConstants::FRAGMENT_SHADER_DERIVATIVE_HINT_OES, + ); + } + + fn name() -> &'static str { + "OES_standard_derivatives" + } +} diff --git a/components/script/dom/webgl_extensions/ext/oestexturefloat.rs b/components/script/dom/webgl_extensions/ext/oestexturefloat.rs new file mode 100644 index 00000000000..e286095b7c7 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/oestexturefloat.rs @@ -0,0 +1,63 @@ +/* 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 super::{constants as webgl, WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::{TexFormat, WebGLVersion}; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct OESTextureFloat { + reflector_: Reflector, +} + +impl OESTextureFloat { + fn new_inherited() -> OESTextureFloat { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for OESTextureFloat { + type Extension = OESTextureFloat; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<OESTextureFloat> { + reflect_dom_object(Box::new(OESTextureFloat::new_inherited()), &*ctx.global()) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_any_gl_extension(&[ + "GL_OES_texture_float", + "GL_ARB_texture_float", + "GL_EXT_color_buffer_float", + ]) + } + + fn enable(ext: &WebGLExtensions) { + ext.enable_tex_type(webgl::FLOAT); + ext.add_effective_tex_internal_format(TexFormat::RGBA, webgl::FLOAT, TexFormat::RGBA32f); + ext.add_effective_tex_internal_format(TexFormat::RGB, webgl::FLOAT, TexFormat::RGB32f); + ext.add_effective_tex_internal_format( + TexFormat::Luminance, + webgl::FLOAT, + TexFormat::Luminance32f, + ); + ext.add_effective_tex_internal_format(TexFormat::Alpha, webgl::FLOAT, TexFormat::Alpha32f); + ext.add_effective_tex_internal_format( + TexFormat::LuminanceAlpha, + webgl::FLOAT, + TexFormat::LuminanceAlpha32f, + ); + } + + fn name() -> &'static str { + "OES_texture_float" + } +} diff --git a/components/script/dom/webgl_extensions/ext/oestexturefloatlinear.rs b/components/script/dom/webgl_extensions/ext/oestexturefloatlinear.rs new file mode 100644 index 00000000000..9e5c021f0ef --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/oestexturefloatlinear.rs @@ -0,0 +1,48 @@ +/* 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 super::{constants as webgl, WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct OESTextureFloatLinear { + reflector_: Reflector, +} + +impl OESTextureFloatLinear { + fn new_inherited() -> OESTextureFloatLinear { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for OESTextureFloatLinear { + type Extension = OESTextureFloatLinear; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<OESTextureFloatLinear> { + reflect_dom_object( + Box::new(OESTextureFloatLinear::new_inherited()), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::All + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_any_gl_extension(&["GL_OES_texture_float_linear", "GL_ARB_texture_float"]) + } + + fn enable(ext: &WebGLExtensions) { + ext.enable_filterable_tex_type(webgl::FLOAT); + } + + fn name() -> &'static str { + "OES_texture_float_linear" + } +} diff --git a/components/script/dom/webgl_extensions/ext/oestexturehalffloat.rs b/components/script/dom/webgl_extensions/ext/oestexturehalffloat.rs new file mode 100644 index 00000000000..4f2ef41c651 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/oestexturehalffloat.rs @@ -0,0 +1,65 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::codegen::Bindings::OESTextureHalfFloatBinding::OESTextureHalfFloatConstants; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::{TexFormat, WebGLVersion}; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct OESTextureHalfFloat { + reflector_: Reflector, +} + +impl OESTextureHalfFloat { + fn new_inherited() -> OESTextureHalfFloat { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for OESTextureHalfFloat { + type Extension = OESTextureHalfFloat; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<OESTextureHalfFloat> { + reflect_dom_object( + Box::new(OESTextureHalfFloat::new_inherited()), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_any_gl_extension(&[ + "GL_OES_texture_half_float", + "GL_ARB_half_float_pixel", + "GL_NV_half_float", + "GL_EXT_color_buffer_half_float", + ]) + } + + fn enable(ext: &WebGLExtensions) { + let hf = OESTextureHalfFloatConstants::HALF_FLOAT_OES; + ext.enable_tex_type(hf); + ext.add_effective_tex_internal_format(TexFormat::RGBA, hf, TexFormat::RGBA16f); + ext.add_effective_tex_internal_format(TexFormat::RGB, hf, TexFormat::RGB16f); + ext.add_effective_tex_internal_format(TexFormat::Luminance, hf, TexFormat::Luminance16f); + ext.add_effective_tex_internal_format(TexFormat::Alpha, hf, TexFormat::Alpha16f); + ext.add_effective_tex_internal_format( + TexFormat::LuminanceAlpha, + hf, + TexFormat::LuminanceAlpha16f, + ); + } + + fn name() -> &'static str { + "OES_texture_half_float" + } +} diff --git a/components/script/dom/webgl_extensions/ext/oestexturehalffloatlinear.rs b/components/script/dom/webgl_extensions/ext/oestexturehalffloatlinear.rs new file mode 100644 index 00000000000..a1e5f3bf15d --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/oestexturehalffloatlinear.rs @@ -0,0 +1,53 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::codegen::Bindings::OESTextureHalfFloatBinding::OESTextureHalfFloatConstants; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct OESTextureHalfFloatLinear { + reflector_: Reflector, +} + +impl OESTextureHalfFloatLinear { + fn new_inherited() -> OESTextureHalfFloatLinear { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for OESTextureHalfFloatLinear { + type Extension = OESTextureHalfFloatLinear; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<OESTextureHalfFloatLinear> { + reflect_dom_object( + Box::new(OESTextureHalfFloatLinear::new_inherited()), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::All + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_any_gl_extension(&[ + "GL_OES_texture_float_linear", + "GL_ARB_half_float_pixel", + "GL_NV_half_float", + ]) + } + + fn enable(ext: &WebGLExtensions) { + ext.enable_filterable_tex_type(OESTextureHalfFloatConstants::HALF_FLOAT_OES); + } + + fn name() -> &'static str { + "OES_texture_half_float_linear" + } +} diff --git a/components/script/dom/webgl_extensions/ext/oesvertexarrayobject.rs b/components/script/dom/webgl_extensions/ext/oesvertexarrayobject.rs new file mode 100644 index 00000000000..57a68142609 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/oesvertexarrayobject.rs @@ -0,0 +1,80 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::codegen::Bindings::OESVertexArrayObjectBinding::OESVertexArrayObjectConstants; +use crate::dom::bindings::codegen::Bindings::OESVertexArrayObjectBinding::OESVertexArrayObjectMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::dom::webglvertexarrayobjectoes::WebGLVertexArrayObjectOES; +use canvas_traits::webgl::WebGLVersion; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct OESVertexArrayObject { + reflector_: Reflector, + ctx: Dom<WebGLRenderingContext>, +} + +impl OESVertexArrayObject { + fn new_inherited(ctx: &WebGLRenderingContext) -> OESVertexArrayObject { + Self { + reflector_: Reflector::new(), + ctx: Dom::from_ref(ctx), + } + } +} + +impl OESVertexArrayObjectMethods for OESVertexArrayObject { + // https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/ + fn CreateVertexArrayOES(&self) -> Option<DomRoot<WebGLVertexArrayObjectOES>> { + self.ctx.create_vertex_array() + } + + // https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/ + fn DeleteVertexArrayOES(&self, vao: Option<&WebGLVertexArrayObjectOES>) { + self.ctx.delete_vertex_array(vao); + } + + // https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/ + fn IsVertexArrayOES(&self, vao: Option<&WebGLVertexArrayObjectOES>) -> bool { + self.ctx.is_vertex_array(vao) + } + + // https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/ + fn BindVertexArrayOES(&self, vao: Option<&WebGLVertexArrayObjectOES>) { + self.ctx.bind_vertex_array(vao); + } +} + +impl WebGLExtension for OESVertexArrayObject { + type Extension = OESVertexArrayObject; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<OESVertexArrayObject> { + reflect_dom_object( + Box::new(OESVertexArrayObject::new_inherited(ctx)), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_any_gl_extension(&[ + "GL_OES_vertex_array_object", + "GL_ARB_vertex_array_object", + "GL_APPLE_vertex_array_object", + ]) + } + + fn enable(ext: &WebGLExtensions) { + ext.enable_get_parameter_name(OESVertexArrayObjectConstants::VERTEX_ARRAY_BINDING_OES); + } + + fn name() -> &'static str { + "OES_vertex_array_object" + } +} diff --git a/components/script/dom/webgl_extensions/ext/webglcolorbufferfloat.rs b/components/script/dom/webgl_extensions/ext/webglcolorbufferfloat.rs new file mode 100644 index 00000000000..c0d8dc13776 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/webglcolorbufferfloat.rs @@ -0,0 +1,48 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webgl_extensions::ext::oestexturefloat::OESTextureFloat; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::WebGLVersion; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct WEBGLColorBufferFloat { + reflector_: Reflector, +} + +impl WEBGLColorBufferFloat { + fn new_inherited() -> WEBGLColorBufferFloat { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for WEBGLColorBufferFloat { + type Extension = WEBGLColorBufferFloat; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<WEBGLColorBufferFloat> { + reflect_dom_object( + Box::new(WEBGLColorBufferFloat::new_inherited()), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + OESTextureFloat::is_supported(ext) + } + + fn enable(_ext: &WebGLExtensions) {} + + fn name() -> &'static str { + "WEBGL_color_buffer_float" + } +} diff --git a/components/script/dom/webgl_extensions/ext/webglcompressedtextureetc1.rs b/components/script/dom/webgl_extensions/ext/webglcompressedtextureetc1.rs new file mode 100644 index 00000000000..749c38abd82 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/webglcompressedtextureetc1.rs @@ -0,0 +1,56 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::dom::webgltexture::{TexCompression, TexCompressionValidation}; +use canvas_traits::webgl::{TexFormat, WebGLVersion}; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct WEBGLCompressedTextureETC1 { + reflector_: Reflector, +} + +impl WEBGLCompressedTextureETC1 { + fn new_inherited() -> WEBGLCompressedTextureETC1 { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for WEBGLCompressedTextureETC1 { + type Extension = WEBGLCompressedTextureETC1; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<WEBGLCompressedTextureETC1> { + reflect_dom_object( + Box::new(WEBGLCompressedTextureETC1::new_inherited()), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_gl_extension("GL_OES_compressed_ETC1_RGB8_texture") + } + + fn enable(ext: &WebGLExtensions) { + ext.add_tex_compression_formats(&[TexCompression { + format: TexFormat::CompressedRgbEtc1, + bytes_per_block: 8, + block_width: 4, + block_height: 4, + validation: TexCompressionValidation::None, + }]); + } + + fn name() -> &'static str { + "WEBGL_compressed_texture_etc1" + } +} diff --git a/components/script/dom/webgl_extensions/ext/webglcompressedtextures3tc.rs b/components/script/dom/webgl_extensions/ext/webglcompressedtextures3tc.rs new file mode 100644 index 00000000000..362dbe67117 --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/webglcompressedtextures3tc.rs @@ -0,0 +1,84 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::dom::webgltexture::{TexCompression, TexCompressionValidation}; +use canvas_traits::webgl::{TexFormat, WebGLVersion}; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct WEBGLCompressedTextureS3TC { + reflector_: Reflector, +} + +impl WEBGLCompressedTextureS3TC { + fn new_inherited() -> WEBGLCompressedTextureS3TC { + Self { + reflector_: Reflector::new(), + } + } +} + +impl WebGLExtension for WEBGLCompressedTextureS3TC { + type Extension = WEBGLCompressedTextureS3TC; + fn new(ctx: &WebGLRenderingContext) -> DomRoot<WEBGLCompressedTextureS3TC> { + reflect_dom_object( + Box::new(WEBGLCompressedTextureS3TC::new_inherited()), + &*ctx.global(), + ) + } + + fn spec() -> WebGLExtensionSpec { + WebGLExtensionSpec::Specific(WebGLVersion::WebGL1) + } + + fn is_supported(ext: &WebGLExtensions) -> bool { + ext.supports_gl_extension("GL_EXT_texture_compression_s3tc") || + ext.supports_all_gl_extension(&[ + "GL_EXT_texture_compression_dxt1", + "GL_ANGLE_texture_compression_dxt3", + "GL_ANGLE_texture_compression_dxt5", + ]) + } + + fn enable(ext: &WebGLExtensions) { + ext.add_tex_compression_formats(&[ + TexCompression { + format: TexFormat::CompressedRgbS3tcDxt1, + bytes_per_block: 8, + block_width: 4, + block_height: 4, + validation: TexCompressionValidation::S3TC, + }, + TexCompression { + format: TexFormat::CompressedRgbaS3tcDxt1, + bytes_per_block: 8, + block_width: 4, + block_height: 4, + validation: TexCompressionValidation::S3TC, + }, + TexCompression { + format: TexFormat::CompressedRgbaS3tcDxt3, + bytes_per_block: 16, + block_width: 4, + block_height: 4, + validation: TexCompressionValidation::S3TC, + }, + TexCompression { + format: TexFormat::CompressedRgbaS3tcDxt5, + bytes_per_block: 16, + block_width: 4, + block_height: 4, + validation: TexCompressionValidation::S3TC, + }, + ]); + } + + fn name() -> &'static str { + "WEBGL_compressed_texture_s3tc" + } +} diff --git a/components/script/dom/webgl_extensions/extension.rs b/components/script/dom/webgl_extensions/extension.rs new file mode 100644 index 00000000000..3854acee122 --- /dev/null +++ b/components/script/dom/webgl_extensions/extension.rs @@ -0,0 +1,40 @@ +/* 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 super::WebGLExtensions; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use canvas_traits::webgl::WebGLVersion; + +/// Trait implemented by WebGL extensions. +pub trait WebGLExtension: Sized +where + Self::Extension: DomObject + JSTraceable, +{ + type Extension; + + /// Creates the DOM object of the WebGL extension. + fn new(ctx: &WebGLRenderingContext) -> DomRoot<Self::Extension>; + + /// Returns which WebGL spec is this extension written against. + fn spec() -> WebGLExtensionSpec; + + /// Checks if the extension is supported. + fn is_supported(ext: &WebGLExtensions) -> bool; + + /// Enable the extension. + fn enable(ext: &WebGLExtensions); + + /// Name of the WebGL Extension. + fn name() -> &'static str; +} + +pub enum WebGLExtensionSpec { + /// Extensions written against both WebGL and WebGL2 specs. + All, + /// Extensions writen against a specific WebGL version spec. + Specific(WebGLVersion), +} diff --git a/components/script/dom/webgl_extensions/extensions.rs b/components/script/dom/webgl_extensions/extensions.rs new file mode 100644 index 00000000000..97e5900960f --- /dev/null +++ b/components/script/dom/webgl_extensions/extensions.rs @@ -0,0 +1,470 @@ +/* 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 super::wrapper::{TypedWebGLExtensionWrapper, WebGLExtensionWrapper}; +use super::{ext, WebGLExtension, WebGLExtensionSpec}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysConstants; +use crate::dom::bindings::codegen::Bindings::EXTTextureFilterAnisotropicBinding::EXTTextureFilterAnisotropicConstants; +use crate::dom::bindings::codegen::Bindings::OESStandardDerivativesBinding::OESStandardDerivativesConstants; +use crate::dom::bindings::codegen::Bindings::OESTextureHalfFloatBinding::OESTextureHalfFloatConstants; +use crate::dom::bindings::codegen::Bindings::OESVertexArrayObjectBinding::OESVertexArrayObjectConstants; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::extcolorbufferhalffloat::EXTColorBufferHalfFloat; +use crate::dom::oestexturefloat::OESTextureFloat; +use crate::dom::oestexturehalffloat::OESTextureHalfFloat; +use crate::dom::webglcolorbufferfloat::WEBGLColorBufferFloat; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::dom::webgltexture::TexCompression; +use canvas_traits::webgl::{GlType, TexFormat, WebGLSLVersion, WebGLVersion}; +use fnv::{FnvHashMap, FnvHashSet}; +use js::jsapi::JSObject; +use malloc_size_of::MallocSizeOf; +use sparkle::gl::{self, GLenum}; +use std::collections::HashMap; +use std::iter::FromIterator; +use std::ptr::NonNull; + +// Data types that are implemented for texImage2D and texSubImage2D in a WebGL 1.0 context +// but must trigger a InvalidValue error until the related WebGL Extensions are enabled. +// Example: https://www.khronos.org/registry/webgl/extensions/OES_texture_float/ +const DEFAULT_DISABLED_TEX_TYPES_WEBGL1: [GLenum; 2] = [ + constants::FLOAT, + OESTextureHalfFloatConstants::HALF_FLOAT_OES, +]; + +// Data types that are implemented for textures in WebGLRenderingContext +// but not allowed to use with linear filtering until the related WebGL Extensions are enabled. +// Example: https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/ +const DEFAULT_NOT_FILTERABLE_TEX_TYPES: [GLenum; 2] = [ + constants::FLOAT, + OESTextureHalfFloatConstants::HALF_FLOAT_OES, +]; + +// Param names that are implemented for glGetParameter in a WebGL 1.0 context +// but must trigger a InvalidEnum error until the related WebGL Extensions are enabled. +// Example: https://www.khronos.org/registry/webgl/extensions/OES_standard_derivatives/ +const DEFAULT_DISABLED_GET_PARAMETER_NAMES_WEBGL1: [GLenum; 3] = [ + EXTTextureFilterAnisotropicConstants::MAX_TEXTURE_MAX_ANISOTROPY_EXT, + OESStandardDerivativesConstants::FRAGMENT_SHADER_DERIVATIVE_HINT_OES, + OESVertexArrayObjectConstants::VERTEX_ARRAY_BINDING_OES, +]; + +// Param names that are implemented for glGetParameter in a WebGL 2.0 context +// but must trigger a InvalidEnum error until the related WebGL Extensions are enabled. +// Example: https://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/ +const DEFAULT_DISABLED_GET_PARAMETER_NAMES_WEBGL2: [GLenum; 1] = + [EXTTextureFilterAnisotropicConstants::MAX_TEXTURE_MAX_ANISOTROPY_EXT]; + +// Param names that are implemented for glGetTexParameter in a WebGL 1.0 context +// but must trigger a InvalidEnum error until the related WebGL Extensions are enabled. +// Example: https://www.khronos.org/registry/webgl/extensions/OES_standard_derivatives/ +const DEFAULT_DISABLED_GET_TEX_PARAMETER_NAMES_WEBGL1: [GLenum; 1] = + [EXTTextureFilterAnisotropicConstants::TEXTURE_MAX_ANISOTROPY_EXT]; + +// Param names that are implemented for glGetTexParameter in a WebGL 2.0 context +// but must trigger a InvalidEnum error until the related WebGL Extensions are enabled. +// Example: https://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/ +const DEFAULT_DISABLED_GET_TEX_PARAMETER_NAMES_WEBGL2: [GLenum; 1] = + [EXTTextureFilterAnisotropicConstants::TEXTURE_MAX_ANISOTROPY_EXT]; + +// Param names that are implemented for glGetVertexAttrib in a WebGL 1.0 context +// but must trigger a InvalidEnum error until the related WebGL Extensions are enabled. +// Example: https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ +const DEFAULT_DISABLED_GET_VERTEX_ATTRIB_NAMES_WEBGL1: [GLenum; 1] = + [ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE]; + +/// WebGL features that are enabled/disabled by WebGL Extensions. +#[derive(JSTraceable, MallocSizeOf)] +struct WebGLExtensionFeatures { + gl_extensions: FnvHashSet<String>, + disabled_tex_types: FnvHashSet<GLenum>, + not_filterable_tex_types: FnvHashSet<GLenum>, + effective_tex_internal_formats: FnvHashMap<TexFormatType, TexFormat>, + /// WebGL Hint() targets enabled by extensions. + hint_targets: FnvHashSet<GLenum>, + /// WebGL GetParameter() names enabled by extensions. + disabled_get_parameter_names: FnvHashSet<GLenum>, + /// WebGL GetTexParameter() names enabled by extensions. + disabled_get_tex_parameter_names: FnvHashSet<GLenum>, + /// WebGL GetAttribVertex() names enabled by extensions. + disabled_get_vertex_attrib_names: FnvHashSet<GLenum>, + /// WebGL OES_element_index_uint extension. + element_index_uint_enabled: bool, + /// WebGL EXT_blend_minmax extension. + blend_minmax_enabled: bool, + /// WebGL supported texture compression formats enabled by extensions. + tex_compression_formats: FnvHashMap<GLenum, TexCompression>, +} + +impl WebGLExtensionFeatures { + fn new(webgl_version: WebGLVersion) -> Self { + let ( + disabled_tex_types, + disabled_get_parameter_names, + disabled_get_tex_parameter_names, + disabled_get_vertex_attrib_names, + not_filterable_tex_types, + element_index_uint_enabled, + blend_minmax_enabled, + ) = match webgl_version { + WebGLVersion::WebGL1 => ( + DEFAULT_DISABLED_TEX_TYPES_WEBGL1.iter().cloned().collect(), + DEFAULT_DISABLED_GET_PARAMETER_NAMES_WEBGL1 + .iter() + .cloned() + .collect(), + DEFAULT_DISABLED_GET_TEX_PARAMETER_NAMES_WEBGL1 + .iter() + .cloned() + .collect(), + DEFAULT_DISABLED_GET_VERTEX_ATTRIB_NAMES_WEBGL1 + .iter() + .cloned() + .collect(), + DEFAULT_NOT_FILTERABLE_TEX_TYPES.iter().cloned().collect(), + false, + false, + ), + WebGLVersion::WebGL2 => ( + Default::default(), + DEFAULT_DISABLED_GET_PARAMETER_NAMES_WEBGL2 + .iter() + .cloned() + .collect(), + DEFAULT_DISABLED_GET_TEX_PARAMETER_NAMES_WEBGL2 + .iter() + .cloned() + .collect(), + Default::default(), + Default::default(), + true, + true, + ), + }; + Self { + gl_extensions: Default::default(), + disabled_tex_types, + not_filterable_tex_types, + effective_tex_internal_formats: Default::default(), + hint_targets: Default::default(), + disabled_get_parameter_names, + disabled_get_tex_parameter_names, + disabled_get_vertex_attrib_names, + element_index_uint_enabled, + blend_minmax_enabled, + tex_compression_formats: Default::default(), + } + } +} + +/// Handles the list of implemented, supported and enabled WebGL extensions. +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +pub struct WebGLExtensions { + extensions: DomRefCell<HashMap<String, Box<dyn WebGLExtensionWrapper>>>, + features: DomRefCell<WebGLExtensionFeatures>, + webgl_version: WebGLVersion, + api_type: GlType, + glsl_version: WebGLSLVersion, +} + +impl WebGLExtensions { + pub fn new( + webgl_version: WebGLVersion, + api_type: GlType, + glsl_version: WebGLSLVersion, + ) -> WebGLExtensions { + Self { + extensions: DomRefCell::new(HashMap::new()), + features: DomRefCell::new(WebGLExtensionFeatures::new(webgl_version)), + webgl_version, + api_type, + glsl_version, + } + } + + pub fn init_once<F>(&self, cb: F) + where + F: FnOnce() -> String, + { + if self.extensions.borrow().len() == 0 { + let gl_str = cb(); + self.features.borrow_mut().gl_extensions = + FnvHashSet::from_iter(gl_str.split(&[',', ' '][..]).map(|s| s.into())); + self.register_all_extensions(); + } + } + + pub fn register<T: 'static + WebGLExtension + JSTraceable + MallocSizeOf>(&self) { + let name = T::name().to_uppercase(); + self.extensions + .borrow_mut() + .insert(name, Box::new(TypedWebGLExtensionWrapper::<T>::new())); + } + + pub fn get_supported_extensions(&self) -> Vec<&'static str> { + self.extensions + .borrow() + .iter() + .filter(|ref v| { + if let WebGLExtensionSpec::Specific(version) = v.1.spec() { + if self.webgl_version != version { + return false; + } + } + v.1.is_supported(&self) + }) + .map(|ref v| v.1.name()) + .collect() + } + + pub fn get_or_init_extension( + &self, + name: &str, + ctx: &WebGLRenderingContext, + ) -> Option<NonNull<JSObject>> { + let name = name.to_uppercase(); + self.extensions.borrow().get(&name).and_then(|extension| { + if extension.is_supported(self) { + Some(extension.instance_or_init(ctx, self)) + } else { + None + } + }) + } + + pub fn is_enabled<T>(&self) -> bool + where + T: 'static + WebGLExtension + JSTraceable + MallocSizeOf, + { + let name = T::name().to_uppercase(); + self.extensions + .borrow() + .get(&name) + .map_or(false, |ext| ext.is_enabled()) + } + + pub fn supports_gl_extension(&self, name: &str) -> bool { + self.features.borrow().gl_extensions.contains(name) + } + + pub fn supports_any_gl_extension(&self, names: &[&str]) -> bool { + let features = self.features.borrow(); + names + .iter() + .any(|name| features.gl_extensions.contains(*name)) + } + + pub fn supports_all_gl_extension(&self, names: &[&str]) -> bool { + let features = self.features.borrow(); + names + .iter() + .all(|name| features.gl_extensions.contains(*name)) + } + + pub fn enable_tex_type(&self, data_type: GLenum) { + self.features + .borrow_mut() + .disabled_tex_types + .remove(&data_type); + } + + pub fn is_tex_type_enabled(&self, data_type: GLenum) -> bool { + self.features + .borrow() + .disabled_tex_types + .get(&data_type) + .is_none() + } + + pub fn add_effective_tex_internal_format( + &self, + source_internal_format: TexFormat, + source_data_type: u32, + effective_internal_format: TexFormat, + ) { + let format = TexFormatType(source_internal_format, source_data_type); + self.features + .borrow_mut() + .effective_tex_internal_formats + .insert(format, effective_internal_format); + } + + pub fn get_effective_tex_internal_format( + &self, + source_internal_format: TexFormat, + source_data_type: u32, + ) -> TexFormat { + let format = TexFormatType(source_internal_format, source_data_type); + *(self + .features + .borrow() + .effective_tex_internal_formats + .get(&format) + .unwrap_or(&source_internal_format)) + } + + pub fn enable_filterable_tex_type(&self, text_data_type: GLenum) { + self.features + .borrow_mut() + .not_filterable_tex_types + .remove(&text_data_type); + } + + pub fn is_filterable(&self, text_data_type: u32) -> bool { + self.features + .borrow() + .not_filterable_tex_types + .get(&text_data_type) + .is_none() + } + + pub fn enable_hint_target(&self, name: GLenum) { + self.features.borrow_mut().hint_targets.insert(name); + } + + pub fn is_hint_target_enabled(&self, name: GLenum) -> bool { + self.features.borrow().hint_targets.contains(&name) + } + + pub fn enable_get_parameter_name(&self, name: GLenum) { + self.features + .borrow_mut() + .disabled_get_parameter_names + .remove(&name); + } + + pub fn is_get_parameter_name_enabled(&self, name: GLenum) -> bool { + !self + .features + .borrow() + .disabled_get_parameter_names + .contains(&name) + } + + pub fn enable_get_tex_parameter_name(&self, name: GLenum) { + self.features + .borrow_mut() + .disabled_get_tex_parameter_names + .remove(&name); + } + + pub fn is_get_tex_parameter_name_enabled(&self, name: GLenum) -> bool { + !self + .features + .borrow() + .disabled_get_tex_parameter_names + .contains(&name) + } + + pub fn enable_get_vertex_attrib_name(&self, name: GLenum) { + self.features + .borrow_mut() + .disabled_get_vertex_attrib_names + .remove(&name); + } + + pub fn is_get_vertex_attrib_name_enabled(&self, name: GLenum) -> bool { + !self + .features + .borrow() + .disabled_get_vertex_attrib_names + .contains(&name) + } + + pub fn add_tex_compression_formats(&self, formats: &[TexCompression]) { + let formats: FnvHashMap<GLenum, TexCompression> = formats + .iter() + .map(|&compression| (compression.format.as_gl_constant(), compression)) + .collect(); + + self.features + .borrow_mut() + .tex_compression_formats + .extend(formats.iter()); + } + + pub fn get_tex_compression_format(&self, format_id: GLenum) -> Option<TexCompression> { + self.features + .borrow() + .tex_compression_formats + .get(&format_id) + .cloned() + } + + pub fn get_tex_compression_ids(&self) -> Vec<GLenum> { + self.features + .borrow() + .tex_compression_formats + .keys() + .map(|&k| k) + .collect() + } + + fn register_all_extensions(&self) { + self.register::<ext::angleinstancedarrays::ANGLEInstancedArrays>(); + self.register::<ext::extblendminmax::EXTBlendMinmax>(); + self.register::<ext::extcolorbufferhalffloat::EXTColorBufferHalfFloat>(); + self.register::<ext::extfragdepth::EXTFragDepth>(); + self.register::<ext::extshadertexturelod::EXTShaderTextureLod>(); + self.register::<ext::exttexturefilteranisotropic::EXTTextureFilterAnisotropic>(); + self.register::<ext::oeselementindexuint::OESElementIndexUint>(); + self.register::<ext::oesstandardderivatives::OESStandardDerivatives>(); + self.register::<ext::oestexturefloat::OESTextureFloat>(); + self.register::<ext::oestexturefloatlinear::OESTextureFloatLinear>(); + self.register::<ext::oestexturehalffloat::OESTextureHalfFloat>(); + self.register::<ext::oestexturehalffloatlinear::OESTextureHalfFloatLinear>(); + self.register::<ext::oesvertexarrayobject::OESVertexArrayObject>(); + self.register::<ext::webglcolorbufferfloat::WEBGLColorBufferFloat>(); + self.register::<ext::webglcompressedtextureetc1::WEBGLCompressedTextureETC1>(); + self.register::<ext::webglcompressedtextures3tc::WEBGLCompressedTextureS3TC>(); + } + + pub fn enable_element_index_uint(&self) { + self.features.borrow_mut().element_index_uint_enabled = true; + } + + pub fn is_element_index_uint_enabled(&self) -> bool { + self.features.borrow().element_index_uint_enabled + } + + pub fn enable_blend_minmax(&self) { + self.features.borrow_mut().blend_minmax_enabled = true; + } + + pub fn is_blend_minmax_enabled(&self) -> bool { + self.features.borrow().blend_minmax_enabled + } + + pub fn is_float_buffer_renderable(&self) -> bool { + self.is_enabled::<WEBGLColorBufferFloat>() || self.is_enabled::<OESTextureFloat>() + } + + pub fn is_min_glsl_version_satisfied(&self, min_glsl_version: WebGLSLVersion) -> bool { + self.glsl_version >= min_glsl_version + } + + pub fn is_half_float_buffer_renderable(&self) -> bool { + self.is_enabled::<EXTColorBufferHalfFloat>() || self.is_enabled::<OESTextureHalfFloat>() + } + + pub fn effective_type(&self, type_: u32) -> u32 { + if type_ == OESTextureHalfFloatConstants::HALF_FLOAT_OES { + if !self.supports_gl_extension("GL_OES_texture_half_float") { + return gl::HALF_FLOAT; + } + } + type_ + } + + pub fn is_gles(&self) -> bool { + self.api_type == GlType::Gles + } +} + +// Helper structs +#[derive(Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)] +struct TexFormatType(TexFormat, u32); diff --git a/components/script/dom/webgl_extensions/mod.rs b/components/script/dom/webgl_extensions/mod.rs new file mode 100644 index 00000000000..aa6dfd1c752 --- /dev/null +++ b/components/script/dom/webgl_extensions/mod.rs @@ -0,0 +1,12 @@ +/* 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/. */ + +pub mod ext; +mod extension; +mod extensions; +mod wrapper; + +pub use self::extension::WebGLExtension; +pub use self::extension::WebGLExtensionSpec; +pub use self::extensions::WebGLExtensions; diff --git a/components/script/dom/webgl_extensions/wrapper.rs b/components/script/dom/webgl_extensions/wrapper.rs new file mode 100644 index 00000000000..bf8db31418c --- /dev/null +++ b/components/script/dom/webgl_extensions/wrapper.rs @@ -0,0 +1,91 @@ +/* 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 super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::MutNullableDom; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use js::jsapi::JSObject; +use malloc_size_of::MallocSizeOf; +use std::any::Any; +use std::ptr::NonNull; + +/// Trait used internally by WebGLExtensions to store and +/// handle the different WebGL extensions in a common list. +pub trait WebGLExtensionWrapper: JSTraceable + MallocSizeOf { + fn instance_or_init( + &self, + ctx: &WebGLRenderingContext, + ext: &WebGLExtensions, + ) -> NonNull<JSObject>; + fn spec(&self) -> WebGLExtensionSpec; + fn is_supported(&self, _: &WebGLExtensions) -> bool; + fn is_enabled(&self) -> bool; + fn enable(&self, ext: &WebGLExtensions); + fn name(&self) -> &'static str; + fn as_any(&self) -> &dyn Any; +} + +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +pub struct TypedWebGLExtensionWrapper<T: WebGLExtension> { + extension: MutNullableDom<T::Extension>, +} + +/// Typed WebGL Extension implementation. +/// Exposes the exact MutNullableDom<DOMObject> type defined by the extension. +impl<T: WebGLExtension> TypedWebGLExtensionWrapper<T> { + pub fn new() -> TypedWebGLExtensionWrapper<T> { + TypedWebGLExtensionWrapper { + extension: MutNullableDom::new(None), + } + } +} + +impl<T> WebGLExtensionWrapper for TypedWebGLExtensionWrapper<T> +where + T: WebGLExtension + JSTraceable + MallocSizeOf + 'static, +{ + #[allow(unsafe_code)] + fn instance_or_init( + &self, + ctx: &WebGLRenderingContext, + ext: &WebGLExtensions, + ) -> NonNull<JSObject> { + let mut enabled = true; + let extension = self.extension.or_init(|| { + enabled = false; + T::new(ctx) + }); + if !enabled { + self.enable(ext); + } + unsafe { NonNull::new_unchecked(extension.reflector().get_jsobject().get()) } + } + + fn spec(&self) -> WebGLExtensionSpec { + T::spec() + } + + fn is_supported(&self, ext: &WebGLExtensions) -> bool { + self.is_enabled() || T::is_supported(ext) + } + + fn is_enabled(&self) -> bool { + self.extension.get().is_some() + } + + fn enable(&self, ext: &WebGLExtensions) { + T::enable(ext); + } + + fn name(&self) -> &'static str { + T::name() + } + + fn as_any<'a>(&'a self) -> &'a dyn Any { + self + } +} diff --git a/components/script/dom/webgl_validations/mod.rs b/components/script/dom/webgl_validations/mod.rs index 2e070c6d6bc..c590b6029c4 100644 --- a/components/script/dom/webgl_validations/mod.rs +++ b/components/script/dom/webgl_validations/mod.rs @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ pub trait WebGLValidator { type ValidatedOutput; diff --git a/components/script/dom/webgl_validations/tex_image_2d.rs b/components/script/dom/webgl_validations/tex_image_2d.rs index f77d80c2715..e162081f637 100644 --- a/components/script/dom/webgl_validations/tex_image_2d.rs +++ b/components/script/dom/webgl_validations/tex_image_2d.rs @@ -1,14 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::js::Root; -use dom::webglrenderingcontext::WebGLRenderingContext; -use dom::webgltexture::WebGLTexture; -use std::{self, fmt}; +use super::types::TexImageTarget; use super::WebGLValidator; -use super::types::{TexImageTarget, TexDataType, TexFormat}; -use webrender_traits::WebGLError::*; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::dom::webgltexture::{ImageInfo, WebGLTexture}; +use crate::dom::webgltexture::{TexCompression, TexCompressionValidation}; +use canvas_traits::webgl::{TexDataType, TexFormat, WebGLError::*}; +use std::{self, cmp, fmt}; /// The errors that the texImage* family of functions can generate. #[derive(Debug)] @@ -23,6 +24,10 @@ pub enum TexImageValidationError { NegativeLevel, /// A level too high to be allowed by the implementation was passed. LevelTooHigh, + /// A level less than an allowed minimal value was passed. + LevelTooLow, + /// A depth less than an allowed minimal value was passed. + DepthTooLow, /// A negative width and height was passed. NegativeDimension, /// A bigger with and height were passed than what the implementation @@ -40,45 +45,39 @@ pub enum TexImageValidationError { InvalidBorder, /// Expected a power of two texture. NonPotTexture, + /// Unrecognized texture compression format. + InvalidCompressionFormat, + /// Invalid X/Y texture offset parameters. + InvalidOffsets, } -impl std::error::Error for TexImageValidationError { - fn description(&self) -> &str { - use self::TexImageValidationError::*; - match *self { - InvalidTextureTarget(_) - => "Invalid texture target", - TextureTargetNotBound(_) - => "Texture was not bound", - InvalidCubicTextureDimensions - => "Invalid dimensions were given for a cubic texture target", - NegativeLevel - => "A negative level was passed", - LevelTooHigh - => "Level too high", - NegativeDimension - => "Negative dimensions were passed", - TextureTooBig - => "Dimensions given are too big", - InvalidDataType - => "Invalid data type", - InvalidTextureFormat - => "Invalid texture format", - TextureFormatMismatch - => "Texture format mismatch", - InvalidTypeForFormat - => "Invalid type for the given format", - InvalidBorder - => "Invalid border", - NonPotTexture - => "Expected a power of two texture", - } - } -} +impl std::error::Error for TexImageValidationError {} impl fmt::Display for TexImageValidationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TexImageValidationError({})", std::error::Error::description(self)) + use self::TexImageValidationError::*; + let description = match *self { + InvalidTextureTarget(_) => "Invalid texture target", + TextureTargetNotBound(_) => "Texture was not bound", + InvalidCubicTextureDimensions => { + "Invalid dimensions were given for a cubic texture target" + }, + NegativeLevel => "A negative level was passed", + LevelTooHigh => "Level too high", + LevelTooLow => "Level too low", + DepthTooLow => "Depth too low", + NegativeDimension => "Negative dimensions were passed", + TextureTooBig => "Dimensions given are too big", + InvalidDataType => "Invalid data type", + InvalidTextureFormat => "Invalid texture format", + TextureFormatMismatch => "Texture format mismatch", + InvalidTypeForFormat => "Invalid type for the given format", + InvalidBorder => "Invalid border", + NonPotTexture => "Expected a power of two texture", + InvalidCompressionFormat => "Unrecognized texture compression format", + InvalidOffsets => "Invalid X/Y texture offset parameters", + }; + write!(f, "TexImageValidationError({})", description) } } @@ -97,7 +96,7 @@ pub struct CommonTexImage2DValidator<'a> { } pub struct CommonTexImage2DValidatorResult { - pub texture: Root<WebGLTexture>, + pub texture: DomRoot<WebGLTexture>, pub target: TexImageTarget, pub level: u32, pub internal_format: TexFormat, @@ -115,20 +114,23 @@ impl<'a> WebGLValidator for CommonTexImage2DValidator<'a> { // GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, // GL_TEXTURE_CUBE_MAP_POSITIVE_Z, or GL_TEXTURE_CUBE_MAP_NEGATIVE_Z. let target = match TexImageTarget::from_gl_constant(self.target) { - Some(target) => target, - None => { + Some(target) if target.dimensions() == 2 => target, + _ => { self.context.webgl_error(InvalidEnum); return Err(TexImageValidationError::InvalidTextureTarget(self.target)); - } + }, }; - let texture = self.context.bound_texture_for_target(&target); + let texture = self + .context + .textures() + .active_texture_for_image_target(target); let limits = self.context.limits(); let max_size = if target.is_cubic() { - limits.max_cube_map_tex_size + limits.max_cube_map_tex_size } else { - limits.max_tex_size + limits.max_tex_size }; // If an attempt is made to call this function with no WebGLTexture @@ -138,19 +140,31 @@ impl<'a> WebGLValidator for CommonTexImage2DValidator<'a> { None => { self.context.webgl_error(InvalidOperation); return Err(TexImageValidationError::TextureTargetNotBound(self.target)); - } + }, }; // GL_INVALID_ENUM is generated if internal_format is not an accepted // format. let internal_format = match TexFormat::from_gl_constant(self.internal_format) { - Some(format) => format, - None => { + Some(format) + if format.required_webgl_version() <= self.context.webgl_version() && + format.usable_as_internal() => + { + format + }, + _ => { self.context.webgl_error(InvalidEnum); return Err(TexImageValidationError::InvalidTextureFormat); - } + }, }; + // GL_INVALID_VALUE is generated if target is one of the six cube map 2D + // image targets and the width and height parameters are not equal. + if target.is_cubic() && self.width != self.height { + self.context.webgl_error(InvalidValue); + return Err(TexImageValidationError::InvalidCubicTextureDimensions); + } + // GL_INVALID_VALUE is generated if level is less than 0. if self.level < 0 { self.context.webgl_error(InvalidValue); @@ -210,11 +224,15 @@ impl<'a> WebGLValidator for CommonTexImage2DValidator<'a> { } impl<'a> CommonTexImage2DValidator<'a> { - pub fn new(context: &'a WebGLRenderingContext, - target: u32, level: i32, - internal_format: u32, - width: i32, height: i32, - border: i32) -> Self { + pub fn new( + context: &'a WebGLRenderingContext, + target: u32, + level: i32, + internal_format: u32, + width: i32, + height: i32, + border: i32, + ) -> Self { CommonTexImage2DValidator { context: context, target: target, @@ -222,7 +240,7 @@ impl<'a> CommonTexImage2DValidator<'a> { internal_format: internal_format, width: width, height: height, - border: border + border: border, } } } @@ -235,21 +253,27 @@ pub struct TexImage2DValidator<'a> { impl<'a> TexImage2DValidator<'a> { // TODO: Move data validation logic here. - pub fn new(context: &'a WebGLRenderingContext, - target: u32, - level: i32, - internal_format: u32, - width: i32, - height: i32, - border: i32, - format: u32, - data_type: u32) -> Self { + pub fn new( + context: &'a WebGLRenderingContext, + target: u32, + level: i32, + internal_format: u32, + width: i32, + height: i32, + border: i32, + format: u32, + data_type: u32, + ) -> Self { TexImage2DValidator { - common_validator: CommonTexImage2DValidator::new(context, target, - level, - internal_format, - width, height, - border), + common_validator: CommonTexImage2DValidator::new( + context, + target, + level, + internal_format, + width, + height, + border, + ), format: format, data_type: data_type, } @@ -263,14 +287,15 @@ pub struct TexImage2DValidatorResult { pub height: u32, pub level: u32, pub border: u32, - pub texture: Root<WebGLTexture>, + pub texture: DomRoot<WebGLTexture>, pub target: TexImageTarget, + pub internal_format: TexFormat, pub format: TexFormat, pub data_type: TexDataType, } /// TexImage2d validator as per -/// https://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml +/// <https://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml> impl<'a> WebGLValidator for TexImage2DValidator<'a> { type ValidatedOutput = TexImage2DValidatorResult; type Error = TexImageValidationError; @@ -285,41 +310,40 @@ impl<'a> WebGLValidator for TexImage2DValidator<'a> { width, height, border, - } = try!(self.common_validator.validate()); - - // GL_INVALID_VALUE is generated if target is one of the six cube map 2D - // image targets and the width and height parameters are not equal. - if target.is_cubic() && width != height { - context.webgl_error(InvalidValue); - return Err(TexImageValidationError::InvalidCubicTextureDimensions); - } + } = self.common_validator.validate()?; // GL_INVALID_ENUM is generated if format or data_type is not an // accepted value. let data_type = match TexDataType::from_gl_constant(self.data_type) { - Some(data_type) => data_type, - None => { + Some(data_type) if data_type.required_webgl_version() <= context.webgl_version() => { + data_type + }, + _ => { context.webgl_error(InvalidEnum); return Err(TexImageValidationError::InvalidDataType); }, }; let format = match TexFormat::from_gl_constant(self.format) { - Some(format) => format, - None => { + Some(format) if format.required_webgl_version() <= context.webgl_version() => format, + _ => { context.webgl_error(InvalidEnum); return Err(TexImageValidationError::InvalidTextureFormat); - } + }, }; // GL_INVALID_OPERATION is generated if format does not match // internal_format. - if format != internal_format { + if format != internal_format.to_unsized() { context.webgl_error(InvalidOperation); return Err(TexImageValidationError::TextureFormatMismatch); } - + // NOTE: In WebGL2 data type check should be done based on the internal + // format, but in some functions this validator is called with the + // regular unsized format as parameter (eg. TexSubImage2D). For now + // it's left here to avoid duplication. + // // GL_INVALID_OPERATION is generated if type is // GL_UNSIGNED_SHORT_4_4_4_4 or GL_UNSIGNED_SHORT_5_5_5_1 and format is // not GL_RGBA. @@ -327,11 +351,12 @@ impl<'a> WebGLValidator for TexImage2DValidator<'a> { // GL_INVALID_OPERATION is generated if type is GL_UNSIGNED_SHORT_5_6_5 // and format is not GL_RGB. match data_type { - TexDataType::UnsignedShort4444 | - TexDataType::UnsignedShort5551 if format != TexFormat::RGBA => { + TexDataType::UnsignedShort4444 | TexDataType::UnsignedShort5551 + if format != TexFormat::RGBA => + { context.webgl_error(InvalidOperation); return Err(TexImageValidationError::InvalidTypeForFormat); - }, + } TexDataType::UnsignedShort565 if format != TexFormat::RGB => { context.webgl_error(InvalidOperation); return Err(TexImageValidationError::InvalidTypeForFormat); @@ -346,8 +371,419 @@ impl<'a> WebGLValidator for TexImage2DValidator<'a> { border: border, texture: texture, target: target, + internal_format: internal_format, format: format, data_type: data_type, }) } } + +pub struct CommonCompressedTexImage2DValidator<'a> { + common_validator: CommonTexImage2DValidator<'a>, + data_len: usize, +} + +impl<'a> CommonCompressedTexImage2DValidator<'a> { + pub fn new( + context: &'a WebGLRenderingContext, + target: u32, + level: i32, + width: i32, + height: i32, + border: i32, + compression_format: u32, + data_len: usize, + ) -> Self { + CommonCompressedTexImage2DValidator { + common_validator: CommonTexImage2DValidator::new( + context, + target, + level, + compression_format, + width, + height, + border, + ), + data_len, + } + } +} + +pub struct CommonCompressedTexImage2DValidatorResult { + pub texture: DomRoot<WebGLTexture>, + pub target: TexImageTarget, + pub level: u32, + pub width: u32, + pub height: u32, + pub compression: TexCompression, +} + +fn valid_s3tc_dimension(level: u32, side_length: u32, block_size: u32) -> bool { + (side_length % block_size == 0) || (level > 0 && [0, 1, 2].contains(&side_length)) +} + +fn valid_compressed_data_len( + data_len: usize, + width: u32, + height: u32, + compression: &TexCompression, +) -> bool { + let block_width = compression.block_width as u32; + let block_height = compression.block_height as u32; + + let required_blocks_hor = (width + block_width - 1) / block_width; + let required_blocks_ver = (height + block_height - 1) / block_height; + let required_blocks = required_blocks_hor * required_blocks_ver; + + let required_bytes = required_blocks * compression.bytes_per_block as u32; + data_len == required_bytes as usize +} + +fn is_subimage_blockaligned( + xoffset: u32, + yoffset: u32, + width: u32, + height: u32, + compression: &TexCompression, + tex_info: &ImageInfo, +) -> bool { + let block_width = compression.block_width as u32; + let block_height = compression.block_height as u32; + + (xoffset % block_width == 0 && yoffset % block_height == 0) && + (width % block_width == 0 || xoffset + width == tex_info.width()) && + (height % block_height == 0 || yoffset + height == tex_info.height()) +} + +impl<'a> WebGLValidator for CommonCompressedTexImage2DValidator<'a> { + type Error = TexImageValidationError; + type ValidatedOutput = CommonCompressedTexImage2DValidatorResult; + + fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> { + let context = self.common_validator.context; + let CommonTexImage2DValidatorResult { + texture, + target, + level, + internal_format, + width, + height, + border: _, + } = self.common_validator.validate()?; + + // GL_INVALID_ENUM is generated if internalformat is not a supported + // format returned in GL_COMPRESSED_TEXTURE_FORMATS. + let compression = context + .extension_manager() + .get_tex_compression_format(internal_format.as_gl_constant()); + let compression = match compression { + Some(compression) => compression, + None => { + context.webgl_error(InvalidEnum); + return Err(TexImageValidationError::InvalidCompressionFormat); + }, + }; + + // GL_INVALID_VALUE is generated if imageSize is not consistent with the + // format, dimensions, and contents of the specified compressed image data. + if !valid_compressed_data_len(self.data_len, width, height, &compression) { + context.webgl_error(InvalidValue); + return Err(TexImageValidationError::TextureFormatMismatch); + } + + Ok(CommonCompressedTexImage2DValidatorResult { + texture, + target, + level, + width, + height, + compression, + }) + } +} + +pub struct CompressedTexImage2DValidator<'a> { + compression_validator: CommonCompressedTexImage2DValidator<'a>, +} + +impl<'a> CompressedTexImage2DValidator<'a> { + pub fn new( + context: &'a WebGLRenderingContext, + target: u32, + level: i32, + width: i32, + height: i32, + border: i32, + compression_format: u32, + data_len: usize, + ) -> Self { + CompressedTexImage2DValidator { + compression_validator: CommonCompressedTexImage2DValidator::new( + context, + target, + level, + width, + height, + border, + compression_format, + data_len, + ), + } + } +} + +impl<'a> WebGLValidator for CompressedTexImage2DValidator<'a> { + type Error = TexImageValidationError; + type ValidatedOutput = CommonCompressedTexImage2DValidatorResult; + + fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> { + let context = self.compression_validator.common_validator.context; + let CommonCompressedTexImage2DValidatorResult { + texture, + target, + level, + width, + height, + compression, + } = self.compression_validator.validate()?; + + // GL_INVALID_OPERATION is generated if parameter combinations are not + // supported by the specific compressed internal format as specified + // in the specific texture compression extension. + let compression_valid = match compression.validation { + TexCompressionValidation::S3TC => { + let valid_width = + valid_s3tc_dimension(level, width, compression.block_width as u32); + let valid_height = + valid_s3tc_dimension(level, height, compression.block_height as u32); + valid_width && valid_height + }, + TexCompressionValidation::None => true, + }; + if !compression_valid { + context.webgl_error(InvalidOperation); + return Err(TexImageValidationError::TextureFormatMismatch); + } + + Ok(CommonCompressedTexImage2DValidatorResult { + texture, + target, + level, + width, + height, + compression, + }) + } +} + +pub struct CompressedTexSubImage2DValidator<'a> { + compression_validator: CommonCompressedTexImage2DValidator<'a>, + xoffset: i32, + yoffset: i32, +} + +impl<'a> CompressedTexSubImage2DValidator<'a> { + pub fn new( + context: &'a WebGLRenderingContext, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + width: i32, + height: i32, + compression_format: u32, + data_len: usize, + ) -> Self { + CompressedTexSubImage2DValidator { + compression_validator: CommonCompressedTexImage2DValidator::new( + context, + target, + level, + width, + height, + 0, + compression_format, + data_len, + ), + xoffset, + yoffset, + } + } +} + +impl<'a> WebGLValidator for CompressedTexSubImage2DValidator<'a> { + type Error = TexImageValidationError; + type ValidatedOutput = CommonCompressedTexImage2DValidatorResult; + + fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> { + let context = self.compression_validator.common_validator.context; + let CommonCompressedTexImage2DValidatorResult { + texture, + target, + level, + width, + height, + compression, + } = self.compression_validator.validate()?; + + let tex_info = texture.image_info_for_target(&target, level).unwrap(); + + // GL_INVALID_VALUE is generated if: + // - xoffset or yoffset is less than 0 + // - x offset plus the width is greater than the texture width + // - y offset plus the height is greater than the texture height + if self.xoffset < 0 || + (self.xoffset as u32 + width) > tex_info.width() || + self.yoffset < 0 || + (self.yoffset as u32 + height) > tex_info.height() + { + context.webgl_error(InvalidValue); + return Err(TexImageValidationError::InvalidOffsets); + } + + // GL_INVALID_OPERATION is generated if format does not match + // internal_format. + if compression.format != tex_info.internal_format() { + context.webgl_error(InvalidOperation); + return Err(TexImageValidationError::TextureFormatMismatch); + } + + // GL_INVALID_OPERATION is generated if parameter combinations are not + // supported by the specific compressed internal format as specified + // in the specific texture compression extension. + let compression_valid = match compression.validation { + TexCompressionValidation::S3TC => is_subimage_blockaligned( + self.xoffset as u32, + self.yoffset as u32, + width, + height, + &compression, + &tex_info, + ), + TexCompressionValidation::None => true, + }; + if !compression_valid { + context.webgl_error(InvalidOperation); + return Err(TexImageValidationError::TextureFormatMismatch); + } + + Ok(CommonCompressedTexImage2DValidatorResult { + texture, + target, + level, + width, + height, + compression, + }) + } +} + +pub struct TexStorageValidator<'a> { + common_validator: CommonTexImage2DValidator<'a>, + dimensions: u8, + depth: i32, +} + +pub struct TexStorageValidatorResult { + pub texture: DomRoot<WebGLTexture>, + pub target: TexImageTarget, + pub levels: u32, + pub internal_format: TexFormat, + pub width: u32, + pub height: u32, + pub depth: u32, +} + +impl<'a> TexStorageValidator<'a> { + pub fn new( + context: &'a WebGLRenderingContext, + dimensions: u8, + target: u32, + levels: i32, + internal_format: u32, + width: i32, + height: i32, + depth: i32, + ) -> Self { + TexStorageValidator { + common_validator: CommonTexImage2DValidator::new( + context, + target, + levels, + internal_format, + width, + height, + 0, + ), + dimensions, + depth, + } + } +} + +impl<'a> WebGLValidator for TexStorageValidator<'a> { + type Error = TexImageValidationError; + type ValidatedOutput = TexStorageValidatorResult; + + fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> { + let context = self.common_validator.context; + let CommonTexImage2DValidatorResult { + texture, + target, + level, + internal_format, + width, + height, + border: _, + } = self.common_validator.validate()?; + + if self.depth < 1 { + context.webgl_error(InvalidValue); + return Err(TexImageValidationError::DepthTooLow); + } + if level < 1 { + context.webgl_error(InvalidValue); + return Err(TexImageValidationError::LevelTooLow); + } + + let dimensions_valid = match target { + TexImageTarget::Texture2D | TexImageTarget::CubeMap => self.dimensions == 2, + TexImageTarget::Texture3D | TexImageTarget::Texture2DArray => self.dimensions == 3, + _ => false, + }; + if !dimensions_valid { + context.webgl_error(InvalidEnum); + return Err(TexImageValidationError::InvalidTextureTarget( + target.as_gl_constant(), + )); + } + + if !internal_format.is_sized() { + context.webgl_error(InvalidEnum); + return Err(TexImageValidationError::InvalidTextureFormat); + } + + let max_level = log2(cmp::max(width, height) as u32) + 1; + if level > max_level { + context.webgl_error(InvalidOperation); + return Err(TexImageValidationError::LevelTooHigh); + } + + if texture.target().is_none() { + context.webgl_error(InvalidOperation); + return Err(TexImageValidationError::TextureTargetNotBound( + target.as_gl_constant(), + )); + } + + Ok(TexStorageValidatorResult { + texture, + target, + levels: level, + internal_format, + width, + height, + depth: self.depth as u32, + }) + } +} diff --git a/components/script/dom/webgl_validations/types.rs b/components/script/dom/webgl_validations/types.rs index f42eb548efd..e7d87202e3a 100644 --- a/components/script/dom/webgl_validations/types.rs +++ b/components/script/dom/webgl_validations/types.rs @@ -1,47 +1,25 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; - -/// This macro creates type-safe wrappers for WebGL types, associating variants -/// with gl constants. -macro_rules! type_safe_wrapper { - ($name: ident, $($variant:ident => $constant:ident, )+) => { - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, JSTraceable, HeapSizeOf)] - #[repr(u32)] - pub enum $name { - $( - $variant = constants::$constant, - )+ - } - - impl $name { - pub fn from_gl_constant(constant: u32) -> Option<Self> { - Some(match constant { - $(constants::$constant => $name::$variant, )+ - _ => return None, - }) - } - - #[inline] - pub fn as_gl_constant(&self) -> u32 { - *self as u32 - } - } + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use canvas_traits::gl_enums; + +gl_enums! { + pub enum TexImageTarget { + Texture2D = constants::TEXTURE_2D, + Texture2DArray = constants::TEXTURE_2D_ARRAY, + Texture3D = constants::TEXTURE_3D, + CubeMap = constants::TEXTURE_CUBE_MAP, + CubeMapPositiveX = constants::TEXTURE_CUBE_MAP_POSITIVE_X, + CubeMapNegativeX = constants::TEXTURE_CUBE_MAP_NEGATIVE_X, + CubeMapPositiveY = constants::TEXTURE_CUBE_MAP_POSITIVE_Y, + CubeMapNegativeY = constants::TEXTURE_CUBE_MAP_NEGATIVE_Y, + CubeMapPositiveZ = constants::TEXTURE_CUBE_MAP_POSITIVE_Z, + CubeMapNegativeZ = constants::TEXTURE_CUBE_MAP_NEGATIVE_Z, } } -type_safe_wrapper! { TexImageTarget, - Texture2D => TEXTURE_2D, - CubeMapPositiveX => TEXTURE_CUBE_MAP_POSITIVE_X, - CubeMapNegativeX => TEXTURE_CUBE_MAP_NEGATIVE_X, - CubeMapPositiveY => TEXTURE_CUBE_MAP_POSITIVE_Y, - CubeMapNegativeY => TEXTURE_CUBE_MAP_NEGATIVE_Y, - CubeMapPositiveZ => TEXTURE_CUBE_MAP_POSITIVE_Z, - CubeMapNegativeZ => TEXTURE_CUBE_MAP_NEGATIVE_Z, -} - impl TexImageTarget { pub fn is_cubic(&self) -> bool { match *self { @@ -49,61 +27,11 @@ impl TexImageTarget { _ => true, } } -} -type_safe_wrapper! { TexDataType, - UnsignedByte => UNSIGNED_BYTE, - UnsignedShort4444 => UNSIGNED_SHORT_4_4_4_4, - UnsignedShort5551 => UNSIGNED_SHORT_5_5_5_1, - UnsignedShort565 => UNSIGNED_SHORT_5_6_5, -} - -impl TexDataType { - /// Returns the size in bytes of each element of data. - pub fn element_size(&self) -> u32 { - use self::TexDataType::*; - match *self { - UnsignedByte => 1, - UnsignedShort4444 | - UnsignedShort5551 | - UnsignedShort565 => 2, - } - } - - /// Returns how many components a single element may hold. For example, a - /// UnsignedShort4444 holds four components, each with 4 bits of data. - pub fn components_per_element(&self) -> u32 { - use self::TexDataType::*; - match *self { - UnsignedByte => 1, - UnsignedShort565 => 3, - UnsignedShort5551 => 4, - UnsignedShort4444 => 4, - } - } -} - -type_safe_wrapper! { TexFormat, - DepthComponent => DEPTH_COMPONENT, - Alpha => ALPHA, - RGB => RGB, - RGBA => RGBA, - Luminance => LUMINANCE, - LuminanceAlpha => LUMINANCE_ALPHA, -} - -impl TexFormat { - /// Returns how many components does this format need. For example, RGBA - /// needs 4 components, while RGB requires 3. - pub fn components(&self) -> u32 { - use self::TexFormat::*; - match *self { - DepthComponent => 1, - Alpha => 1, - Luminance => 1, - LuminanceAlpha => 2, - RGB => 3, - RGBA => 4, + pub fn dimensions(self) -> u8 { + match self { + TexImageTarget::Texture3D | TexImageTarget::Texture2DArray => 3, + _ => 2, } } } diff --git a/components/script/dom/webglactiveinfo.rs b/components/script/dom/webglactiveinfo.rs index 359a772a268..93cc0274f35 100644 --- a/components/script/dom/webglactiveinfo.rs +++ b/components/script/dom/webglactiveinfo.rs @@ -1,14 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use dom::bindings::codegen::Bindings::WebGLActiveInfoBinding; -use dom::bindings::codegen::Bindings::WebGLActiveInfoBinding::WebGLActiveInfoMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::WebGLActiveInfoBinding::WebGLActiveInfoMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::window::Window; use dom_struct::dom_struct; #[dom_struct] @@ -30,8 +29,11 @@ impl WebGLActiveInfo { } } - pub fn new(window: &Window, size: i32, ty: u32, name: DOMString) -> Root<WebGLActiveInfo> { - reflect_dom_object(box WebGLActiveInfo::new_inherited(size, ty, name), window, WebGLActiveInfoBinding::Wrap) + pub fn new(window: &Window, size: i32, ty: u32, name: DOMString) -> DomRoot<WebGLActiveInfo> { + reflect_dom_object( + Box::new(WebGLActiveInfo::new_inherited(size, ty, name)), + window, + ) } } diff --git a/components/script/dom/webglbuffer.rs b/components/script/dom/webglbuffer.rs index 6678b3d0b92..f81270d8240 100644 --- a/components/script/dom/webglbuffer.rs +++ b/components/script/dom/webglbuffer.rs @@ -1,19 +1,25 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use canvas_traits::CanvasMsg; -use dom::bindings::codegen::Bindings::WebGLBufferBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::webglobject::WebGLObject; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use canvas_traits::webgl::webgl_channel; +use canvas_traits::webgl::{WebGLBufferId, WebGLCommand, WebGLError, WebGLResult}; use dom_struct::dom_struct; -use ipc_channel::ipc::IpcSender; +use ipc_channel::ipc; use std::cell::Cell; -use webrender_traits; -use webrender_traits::{WebGLBufferId, WebGLCommand, WebGLError, WebGLResult}; + +fn target_is_copy_buffer(target: u32) -> bool { + target == WebGL2RenderingContextConstants::COPY_READ_BUFFER || + target == WebGL2RenderingContextConstants::COPY_WRITE_BUFFER +} #[dom_struct] pub struct WebGLBuffer { @@ -22,75 +28,68 @@ pub struct WebGLBuffer { /// The target to which this buffer was bound the first time target: Cell<Option<u32>>, capacity: Cell<usize>, - is_deleted: Cell<bool>, - #[ignore_heap_size_of = "Defined in ipc-channel"] - renderer: IpcSender<CanvasMsg>, + marked_for_deletion: Cell<bool>, + attached_counter: Cell<u32>, + /// https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glGetBufferParameteriv.xml + usage: Cell<u32>, } impl WebGLBuffer { - fn new_inherited(renderer: IpcSender<CanvasMsg>, - id: WebGLBufferId) - -> WebGLBuffer { - WebGLBuffer { - webgl_object: WebGLObject::new_inherited(), - id: id, - target: Cell::new(None), - capacity: Cell::new(0), - is_deleted: Cell::new(false), - renderer: renderer, + fn new_inherited(context: &WebGLRenderingContext, id: WebGLBufferId) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), + id, + target: Default::default(), + capacity: Default::default(), + marked_for_deletion: Default::default(), + attached_counter: Default::default(), + usage: Cell::new(WebGLRenderingContextConstants::STATIC_DRAW), } } - pub fn maybe_new(window: &Window, renderer: IpcSender<CanvasMsg>) - -> Option<Root<WebGLBuffer>> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateBuffer(sender))).unwrap(); - - let result = receiver.recv().unwrap(); - result.map(|buffer_id| WebGLBuffer::new(window, renderer, buffer_id)) + pub fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::CreateBuffer(sender)); + receiver + .recv() + .unwrap() + .map(|id| WebGLBuffer::new(context, id)) } - pub fn new(window: &Window, - renderer: IpcSender<CanvasMsg>, - id: WebGLBufferId) - -> Root<WebGLBuffer> { - reflect_dom_object(box WebGLBuffer::new_inherited(renderer, id), - window, WebGLBufferBinding::Wrap) + pub fn new(context: &WebGLRenderingContext, id: WebGLBufferId) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLBuffer::new_inherited(context, id)), + &*context.global(), + ) } } - impl WebGLBuffer { pub fn id(&self) -> WebGLBufferId { self.id } - // NB: Only valid buffer targets come here - pub fn bind(&self, target: u32) -> WebGLResult<()> { - if let Some(previous_target) = self.target.get() { - if target != previous_target { - return Err(WebGLError::InvalidOperation); - } - } else { - self.target.set(Some(target)); - } - let msg = CanvasMsg::WebGL(WebGLCommand::BindBuffer(target, Some(self.id))); - self.renderer.send(msg).unwrap(); - - Ok(()) - } - pub fn buffer_data(&self, target: u32, data: &[u8], usage: u32) -> WebGLResult<()> { - if let Some(previous_target) = self.target.get() { - if target != previous_target { - return Err(WebGLError::InvalidOperation); - } + match usage { + WebGLRenderingContextConstants::STREAM_DRAW | + WebGLRenderingContextConstants::STATIC_DRAW | + WebGLRenderingContextConstants::DYNAMIC_DRAW | + WebGL2RenderingContextConstants::STATIC_READ | + WebGL2RenderingContextConstants::DYNAMIC_READ | + WebGL2RenderingContextConstants::STREAM_READ | + WebGL2RenderingContextConstants::STATIC_COPY | + WebGL2RenderingContextConstants::DYNAMIC_COPY | + WebGL2RenderingContextConstants::STREAM_COPY => (), + _ => return Err(WebGLError::InvalidEnum), } - self.capacity.set(data.len()); - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::BufferData(target, data.to_vec(), usage))) - .unwrap(); + self.capacity.set(data.len()); + self.usage.set(usage); + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::BufferData(target, receiver, usage)); + sender.send(data).unwrap(); Ok(()) } @@ -98,24 +97,91 @@ impl WebGLBuffer { self.capacity.get() } - pub fn delete(&self) { - if !self.is_deleted.get() { - self.is_deleted.set(true); - let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteBuffer(self.id))); + pub fn mark_for_deletion(&self, operation_fallibility: Operation) { + if self.marked_for_deletion.get() { + return; + } + self.marked_for_deletion.set(true); + if self.is_deleted() { + self.delete(operation_fallibility); + } + } + + fn delete(&self, operation_fallibility: Operation) { + assert!(self.is_deleted()); + let context = self.upcast::<WebGLObject>().context(); + let cmd = WebGLCommand::DeleteBuffer(self.id); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(cmd), + Operation::Infallible => context.send_command(cmd), } } + pub fn is_marked_for_deletion(&self) -> bool { + self.marked_for_deletion.get() + } + pub fn is_deleted(&self) -> bool { - self.is_deleted.get() + self.marked_for_deletion.get() && !self.is_attached() } pub fn target(&self) -> Option<u32> { self.target.get() } + + fn can_bind_to(&self, new_target: u32) -> bool { + if let Some(current_target) = self.target.get() { + if [current_target, new_target] + .contains(&WebGLRenderingContextConstants::ELEMENT_ARRAY_BUFFER) + { + return target_is_copy_buffer(new_target) || new_target == current_target; + } + } + true + } + + pub fn set_target_maybe(&self, target: u32) -> WebGLResult<()> { + if !self.can_bind_to(target) { + return Err(WebGLError::InvalidOperation); + } + if !target_is_copy_buffer(target) { + self.target.set(Some(target)); + } + Ok(()) + } + + pub fn is_attached(&self) -> bool { + self.attached_counter.get() != 0 + } + + pub fn increment_attached_counter(&self) { + self.attached_counter.set( + self.attached_counter + .get() + .checked_add(1) + .expect("refcount overflowed"), + ); + } + + pub fn decrement_attached_counter(&self, operation_fallibility: Operation) { + self.attached_counter.set( + self.attached_counter + .get() + .checked_sub(1) + .expect("refcount underflowed"), + ); + if self.is_deleted() { + self.delete(operation_fallibility); + } + } + + pub fn usage(&self) -> u32 { + self.usage.get() + } } impl Drop for WebGLBuffer { fn drop(&mut self) { - self.delete(); + self.mark_for_deletion(Operation::Fallible); } } diff --git a/components/script/dom/webglcontextevent.rs b/components/script/dom/webglcontextevent.rs index ce569ee8cbc..95e83cbf3a5 100644 --- a/components/script/dom/webglcontextevent.rs +++ b/components/script/dom/webglcontextevent.rs @@ -1,18 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::WebGLContextEventBinding; -use dom::bindings::codegen::Bindings::WebGLContextEventBinding::WebGLContextEventInit; -use dom::bindings::codegen::Bindings::WebGLContextEventBinding::WebGLContextEventMethods; -use dom::bindings::error::Fallible; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::WebGLContextEventBinding::WebGLContextEventInit; +use crate::dom::bindings::codegen::Bindings::WebGLContextEventBinding::WebGLContextEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; @@ -42,26 +41,17 @@ impl WebGLContextEvent { } } - pub fn new_uninitialized(window: &Window) -> Root<WebGLContextEvent> { - // according to https://www.khronos.org/registry/webgl/specs/1.0/#5.15 this is - // additional information or the empty string if no additional information is - // available. - let status_message = DOMString::new(); - reflect_dom_object( - box WebGLContextEvent::new_inherited(status_message), - window, - WebGLContextEventBinding::Wrap) - } - - pub fn new(window: &Window, - type_: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable, - status_message: DOMString) -> Root<WebGLContextEvent> { + pub fn new( + window: &Window, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + status_message: DOMString, + ) -> DomRoot<WebGLContextEvent> { let event = reflect_dom_object( - box WebGLContextEvent::new_inherited(status_message), - window, - WebGLContextEventBinding::Wrap); + Box::new(WebGLContextEvent::new_inherited(status_message)), + window, + ); { let parent = event.upcast::<Event>(); @@ -71,9 +61,12 @@ impl WebGLContextEvent { event } - pub fn Constructor(window: &Window, - type_: DOMString, - init: &WebGLContextEventInit) -> Fallible<Root<WebGLContextEvent>> { + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &WebGLContextEventInit, + ) -> Fallible<DomRoot<WebGLContextEvent>> { let status_message = match init.statusMessage.as_ref() { Some(message) => message.clone(), None => DOMString::new(), @@ -83,10 +76,12 @@ impl WebGLContextEvent { let cancelable = EventCancelable::from(init.parent.cancelable); - Ok(WebGLContextEvent::new(window, - Atom::from(type_), - bubbles, - cancelable, - status_message)) + Ok(WebGLContextEvent::new( + window, + Atom::from(type_), + bubbles, + cancelable, + status_message, + )) } } diff --git a/components/script/dom/webglframebuffer.rs b/components/script/dom/webglframebuffer.rs index 8d2c0f7b89c..8172b1b6c4e 100644 --- a/components/script/dom/webglframebuffer.rs +++ b/components/script/dom/webglframebuffer.rs @@ -1,114 +1,213 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use canvas_traits::CanvasMsg; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::WebGLFramebufferBinding; -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; -use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::reflect_dom_object; -use dom::webglobject::WebGLObject; -use dom::webglrenderbuffer::WebGLRenderbuffer; -use dom::webgltexture::WebGLTexture; -use dom::window::Window; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderbuffer::WebGLRenderbuffer; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use crate::dom::webgltexture::WebGLTexture; +use crate::dom::xrsession::XRSession; +use canvas_traits::webgl::WebGLFramebufferId; +use canvas_traits::webgl::{webgl_channel, WebGLError, WebGLResult, WebGLVersion}; +use canvas_traits::webgl::{WebGLCommand, WebGLFramebufferBindingRequest}; +use canvas_traits::webgl::{WebGLRenderbufferId, WebGLTextureId}; use dom_struct::dom_struct; -use ipc_channel::ipc::IpcSender; +use euclid::Size2D; use std::cell::Cell; -use webrender_traits; -use webrender_traits::{WebGLCommand, WebGLFramebufferBindingRequest, WebGLFramebufferId, WebGLResult, WebGLError}; +use webxr_api::Viewport; -#[must_root] -#[derive(JSTraceable, Clone, HeapSizeOf)] +pub enum CompleteForRendering { + Complete, + Incomplete, + MissingColorAttachment, +} + +fn log2(n: u32) -> u32 { + 31 - n.leading_zeros() +} + +#[unrooted_must_root_lint::must_root] +#[derive(Clone, JSTraceable, MallocSizeOf)] enum WebGLFramebufferAttachment { - Renderbuffer(JS<WebGLRenderbuffer>), - Texture { texture: JS<WebGLTexture>, level: i32 }, + Renderbuffer(Dom<WebGLRenderbuffer>), + Texture { + texture: Dom<WebGLTexture>, + level: i32, + }, +} + +impl WebGLFramebufferAttachment { + fn needs_initialization(&self) -> bool { + match *self { + WebGLFramebufferAttachment::Renderbuffer(ref r) => !r.is_initialized(), + WebGLFramebufferAttachment::Texture { .. } => false, + } + } + + fn mark_initialized(&self) { + match *self { + WebGLFramebufferAttachment::Renderbuffer(ref r) => r.mark_initialized(), + WebGLFramebufferAttachment::Texture { .. } => (), + } + } + + fn root(&self) -> WebGLFramebufferAttachmentRoot { + match *self { + WebGLFramebufferAttachment::Renderbuffer(ref rb) => { + WebGLFramebufferAttachmentRoot::Renderbuffer(DomRoot::from_ref(&rb)) + }, + WebGLFramebufferAttachment::Texture { ref texture, .. } => { + WebGLFramebufferAttachmentRoot::Texture(DomRoot::from_ref(&texture)) + }, + } + } + + fn detach(&self) { + match self { + WebGLFramebufferAttachment::Renderbuffer(rb) => rb.detach_from_framebuffer(), + WebGLFramebufferAttachment::Texture { ref texture, .. } => { + texture.detach_from_framebuffer() + }, + } + } +} + +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub enum WebGLFramebufferAttachmentRoot { + Renderbuffer(DomRoot<WebGLRenderbuffer>), + Texture(DomRoot<WebGLTexture>), } #[dom_struct] pub struct WebGLFramebuffer { webgl_object: WebGLObject, + webgl_version: WebGLVersion, id: WebGLFramebufferId, - /// target can only be gl::FRAMEBUFFER at the moment target: Cell<Option<u32>>, is_deleted: Cell<bool>, size: Cell<Option<(i32, i32)>>, status: Cell<u32>, - #[ignore_heap_size_of = "Defined in ipc-channel"] - renderer: IpcSender<CanvasMsg>, - // The attachment points for textures and renderbuffers on this // FBO. - color: DOMRefCell<Option<WebGLFramebufferAttachment>>, - depth: DOMRefCell<Option<WebGLFramebufferAttachment>>, - stencil: DOMRefCell<Option<WebGLFramebufferAttachment>>, - depthstencil: DOMRefCell<Option<WebGLFramebufferAttachment>>, + colors: Vec<DomRefCell<Option<WebGLFramebufferAttachment>>>, + depth: DomRefCell<Option<WebGLFramebufferAttachment>>, + stencil: DomRefCell<Option<WebGLFramebufferAttachment>>, + depthstencil: DomRefCell<Option<WebGLFramebufferAttachment>>, + color_read_buffer: DomRefCell<u32>, + color_draw_buffers: DomRefCell<Vec<u32>>, + is_initialized: Cell<bool>, + // Framebuffers for XR keep a reference to the XR session. + // https://github.com/immersive-web/webxr/issues/856 + xr_session: MutNullableDom<XRSession>, } impl WebGLFramebuffer { - fn new_inherited(renderer: IpcSender<CanvasMsg>, - id: WebGLFramebufferId) - -> WebGLFramebuffer { - WebGLFramebuffer { - webgl_object: WebGLObject::new_inherited(), + fn new_inherited(context: &WebGLRenderingContext, id: WebGLFramebufferId) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), + webgl_version: context.webgl_version(), id: id, target: Cell::new(None), is_deleted: Cell::new(false), - renderer: renderer, size: Cell::new(None), - status: Cell::new(constants::FRAMEBUFFER_UNSUPPORTED), - color: DOMRefCell::new(None), - depth: DOMRefCell::new(None), - stencil: DOMRefCell::new(None), - depthstencil: DOMRefCell::new(None), + status: Cell::new(constants::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT), + colors: vec![DomRefCell::new(None); context.limits().max_color_attachments as usize], + depth: DomRefCell::new(None), + stencil: DomRefCell::new(None), + depthstencil: DomRefCell::new(None), + color_read_buffer: DomRefCell::new(constants::COLOR_ATTACHMENT0), + color_draw_buffers: DomRefCell::new(vec![constants::COLOR_ATTACHMENT0]), + is_initialized: Cell::new(false), + xr_session: Default::default(), } } - pub fn maybe_new(window: &Window, renderer: IpcSender<CanvasMsg>) - -> Option<Root<WebGLFramebuffer>> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateFramebuffer(sender))).unwrap(); + pub fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::CreateFramebuffer(sender)); + let id = receiver.recv().unwrap()?; + let framebuffer = WebGLFramebuffer::new(context, id); + Some(framebuffer) + } - let result = receiver.recv().unwrap(); - result.map(|fb_id| WebGLFramebuffer::new(window, renderer, fb_id)) + // TODO: depth, stencil and alpha + // https://github.com/servo/servo/issues/24498 + pub fn maybe_new_webxr( + session: &XRSession, + context: &WebGLRenderingContext, + size: Size2D<i32, Viewport>, + ) -> Option<DomRoot<Self>> { + let framebuffer = Self::maybe_new(&*context)?; + framebuffer.size.set(Some((size.width, size.height))); + framebuffer.status.set(constants::FRAMEBUFFER_COMPLETE); + framebuffer.xr_session.set(Some(session)); + Some(framebuffer) } - pub fn new(window: &Window, - renderer: IpcSender<CanvasMsg>, - id: WebGLFramebufferId) - -> Root<WebGLFramebuffer> { - reflect_dom_object(box WebGLFramebuffer::new_inherited(renderer, id), - window, - WebGLFramebufferBinding::Wrap) + pub fn new(context: &WebGLRenderingContext, id: WebGLFramebufferId) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLFramebuffer::new_inherited(context, id)), + &*context.global(), + ) } } - impl WebGLFramebuffer { pub fn id(&self) -> WebGLFramebufferId { self.id } + fn is_in_xr_session(&self) -> bool { + self.xr_session.get().is_some() + } + + pub fn validate_transparent(&self) -> WebGLResult<()> { + if self.is_in_xr_session() { + Err(WebGLError::InvalidOperation) + } else { + Ok(()) + } + } + pub fn bind(&self, target: u32) { - // Update the framebuffer status on binding. It may have - // changed if its attachments were resized or deleted while - // we've been unbound. - self.update_status(); + if !self.is_in_xr_session() { + // Update the framebuffer status on binding. It may have + // changed if its attachments were resized or deleted while + // we've been unbound. + self.update_status(); + } self.target.set(Some(target)); - let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Explicit(self.id)); - self.renderer.send(CanvasMsg::WebGL(cmd)).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::BindFramebuffer( + target, + WebGLFramebufferBindingRequest::Explicit(self.id), + )); } - pub fn delete(&self) { + pub fn delete(&self, operation_fallibility: Operation) { if !self.is_deleted.get() { self.is_deleted.set(true); - let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteFramebuffer(self.id))); + let context = self.upcast::<WebGLObject>().context(); + let cmd = WebGLCommand::DeleteFramebuffer(self.id); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(cmd), + Operation::Infallible => context.send_command(cmd), + } } } pub fn is_deleted(&self) -> bool { + // TODO: if a framebuffer has an attachment which is invalid due to + // being outside a webxr rAF, should this make the framebuffer invalid? + // https://github.com/immersive-web/layers/issues/196 self.is_deleted.get() } @@ -116,253 +215,765 @@ impl WebGLFramebuffer { self.size.get() } - fn update_status(&self) { - let c = self.color.borrow(); + fn check_attachment_constraints<'a>( + &self, + attachment: &Option<WebGLFramebufferAttachment>, + mut constraints: impl Iterator<Item = &'a u32>, + fb_size: &mut Option<(i32, i32)>, + ) -> Result<(), u32> { + // Get the size of this attachment. + let (format, size) = match attachment { + Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => { + (Some(att_rb.internal_format()), att_rb.size()) + }, + Some(WebGLFramebufferAttachment::Texture { + texture: ref att_tex, + level, + }) => match att_tex.image_info_at_face(0, *level as u32) { + Some(info) => ( + Some(info.internal_format().as_gl_constant()), + Some((info.width() as i32, info.height() as i32)), + ), + None => return Err(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT), + }, + None => (None, None), + }; + + // Make sure that, if we've found any other attachment, + // that the size matches. + if size.is_some() { + if fb_size.is_some() && size != *fb_size { + return Err(constants::FRAMEBUFFER_INCOMPLETE_DIMENSIONS); + } else { + *fb_size = size; + } + } + + if let Some(format) = format { + if constraints.all(|c| *c != format) { + return Err(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT); + } + } + + Ok(()) + } + + pub fn update_status(&self) { let z = self.depth.borrow(); let s = self.stencil.borrow(); let zs = self.depthstencil.borrow(); - let has_c = c.is_some(); let has_z = z.is_some(); let has_s = s.is_some(); let has_zs = zs.is_some(); - let attachments = [&*c, &*z, &*s, &*zs]; - - // From the WebGL spec, 6.6 ("Framebuffer Object Attachments"): - // - // "In the WebGL API, it is an error to concurrently attach - // renderbuffers to the following combinations of - // attachment points: - // - // DEPTH_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT - // STENCIL_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT - // DEPTH_ATTACHMENT + STENCIL_ATTACHMENT - // - // If any of the constraints above are violated, then: - // - // checkFramebufferStatus must return FRAMEBUFFER_UNSUPPORTED." - if (has_zs && (has_z || has_s)) || - (has_z && has_s) { - self.status.set(constants::FRAMEBUFFER_UNSUPPORTED); - return; + + let is_supported = match self.webgl_version { + // From the WebGL 1.0 spec, 6.6 ("Framebuffer Object Attachments"): + // + // "In the WebGL API, it is an error to concurrently attach + // renderbuffers to the following combinations of + // attachment points: + // + // DEPTH_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT + // STENCIL_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT + // DEPTH_ATTACHMENT + STENCIL_ATTACHMENT + // + // If any of the constraints above are violated, then: + // + // checkFramebufferStatus must return FRAMEBUFFER_UNSUPPORTED." + WebGLVersion::WebGL1 => !(has_zs && (has_z || has_s)) && !(has_z && has_s), + + // In WebGL 2.0, DEPTH_STENCIL_ATTACHMENT is considered an alias for + // DEPTH_ATTACHMENT + STENCIL_ATTACHMENT, i.e., the same image is attached to both DEPTH_ATTACHMENT + // and STENCIL_ATTACHMENT, overwriting the original images attached to the two attachment points. + // If different images are bound to the depth and stencil attachment points, checkFramebufferStatus + // returns FRAMEBUFFER_UNSUPPORTED, and getFramebufferAttachmentParameter with attachment of + // DEPTH_STENCIL_ATTACHMENT generates an INVALID_OPERATION error. + // -- WebGL 2.0 spec, 4.1.5 Framebuffer Object Attachments + WebGLVersion::WebGL2 => { + use WebGLFramebufferAttachment::{Renderbuffer, Texture}; + match (&*z, &*s) { + (Some(Renderbuffer(a)), Some(Renderbuffer(b))) => a.id() == b.id(), + (Some(Texture { texture: a, .. }), Some(Texture { texture: b, .. })) => { + a.id() == b.id() + }, + _ => !has_z || !has_s, + } + }, + }; + if !is_supported { + return self.status.set(constants::FRAMEBUFFER_UNSUPPORTED); } let mut fb_size = None; - for attachment in &attachments { - // Get the size of this attachment. - let size = match **attachment { - Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => { - att_rb.size() - } - Some(WebGLFramebufferAttachment::Texture { texture: ref att_tex, level } ) => { - let info = att_tex.image_info_at_face(0, level as u32); - Some((info.width() as i32, info.height() as i32)) - } - None => None, - }; - // Make sure that, if we've found any other attachment, - // that the size matches. - if size.is_some() { - if fb_size.is_some() && size != fb_size { - self.status.set(constants::FRAMEBUFFER_INCOMPLETE_DIMENSIONS); - return; - } else { - fb_size = size; - } + let attachments = [&*z, &*s, &*zs]; + let webgl1_attachment_constraints = &[ + &[ + constants::DEPTH_COMPONENT16, + constants::DEPTH_COMPONENT24, + constants::DEPTH_COMPONENT32F, + constants::DEPTH24_STENCIL8, + constants::DEPTH32F_STENCIL8, + ][..], + &[ + constants::STENCIL_INDEX8, + constants::DEPTH24_STENCIL8, + constants::DEPTH32F_STENCIL8, + ][..], + &[constants::DEPTH_STENCIL][..], + ]; + let webgl2_attachment_constraints = &[ + &[constants::DEPTH_STENCIL][..], + &[constants::DEPTH_STENCIL][..], + &[][..], + ]; + let empty_attachment_constrains = &[&[][..], &[][..], &[][..]]; + let extra_attachment_constraints = match self.webgl_version { + WebGLVersion::WebGL1 => empty_attachment_constrains, + WebGLVersion::WebGL2 => webgl2_attachment_constraints, + }; + let attachment_constraints = webgl1_attachment_constraints + .iter() + .zip(extra_attachment_constraints.iter()) + .map(|(a, b)| a.iter().chain(b.iter())); + + for (attachment, constraints) in attachments.iter().zip(attachment_constraints) { + if let Err(errnum) = + self.check_attachment_constraints(attachment, constraints, &mut fb_size) + { + return self.status.set(errnum); } } + + let webgl1_color_constraints = &[ + constants::RGB, + constants::RGB565, + constants::RGB5_A1, + constants::RGBA, + constants::RGBA4, + ][..]; + let webgl2_color_constraints = &[ + constants::ALPHA, + constants::LUMINANCE, + constants::LUMINANCE_ALPHA, + constants::R11F_G11F_B10F, + constants::R16F, + constants::R16I, + constants::R16UI, + constants::R32F, + constants::R32I, + constants::R32UI, + constants::R8, + constants::R8_SNORM, + constants::R8I, + constants::R8UI, + constants::RG16F, + constants::RG16I, + constants::RG16UI, + constants::RG32F, + constants::RG32I, + constants::RG32UI, + constants::RG8, + constants::RG8_SNORM, + constants::RG8I, + constants::RG8UI, + constants::RGB10_A2, + constants::RGB10_A2UI, + constants::RGB16F, + constants::RGB16I, + constants::RGB16UI, + constants::RGB32F, + constants::RGB32I, + constants::RGB32UI, + constants::RGB8, + constants::RGB8_SNORM, + constants::RGB8I, + constants::RGB8UI, + constants::RGB9_E5, + constants::RGBA16F, + constants::RGBA16I, + constants::RGBA16UI, + constants::RGBA32F, + constants::RGBA32I, + constants::RGBA32UI, + constants::RGBA8, + constants::RGBA8_SNORM, + constants::RGBA8I, + constants::RGBA8UI, + constants::SRGB8, + constants::SRGB8_ALPHA8, + ][..]; + let empty_color_constrains = &[][..]; + let extra_color_constraints = match self.webgl_version { + WebGLVersion::WebGL1 => empty_color_constrains, + WebGLVersion::WebGL2 => webgl2_color_constraints, + }; + let color_constraints = webgl1_color_constraints + .iter() + .chain(extra_color_constraints.iter()); + + let has_c = self.colors.iter().any(|att| att.borrow().is_some()); + for attachment in self.colors.iter() { + let attachment = attachment.borrow(); + let constraints = color_constraints.clone(); + if let Err(errnum) = + self.check_attachment_constraints(&*attachment, constraints, &mut fb_size) + { + return self.status.set(errnum); + } + } + self.size.set(fb_size); if has_c || has_z || has_zs || has_s { - self.status.set(constants::FRAMEBUFFER_COMPLETE); + if self.size.get().map_or(false, |(w, h)| w != 0 && h != 0) { + self.status.set(constants::FRAMEBUFFER_COMPLETE); + } else { + self.status + .set(constants::FRAMEBUFFER_INCOMPLETE_ATTACHMENT); + } } else { - self.status.set(constants::FRAMEBUFFER_UNSUPPORTED); + self.status + .set(constants::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT); } } pub fn check_status(&self) -> u32 { - return self.status.get(); + // For opaque framebuffers, check to see if the XR session is currently processing an rAF + // https://immersive-web.github.io/webxr/#opaque-framebuffer + if let Some(xr_session) = self.xr_session.get() { + if xr_session.is_outside_raf() { + constants::FRAMEBUFFER_UNSUPPORTED + } else { + constants::FRAMEBUFFER_COMPLETE + } + } else { + self.status.get() + } + // TODO: if a framebuffer has an attachment which is invalid due to + // being outside a webxr rAF, should this make the framebuffer incomplete? + // https://github.com/immersive-web/layers/issues/196 + } + + pub fn check_status_for_rendering(&self) -> CompleteForRendering { + let result = self.check_status(); + if result != constants::FRAMEBUFFER_COMPLETE { + return CompleteForRendering::Incomplete; + } + + // XR framebuffers are complete inside an rAF + // https://github.com/immersive-web/webxr/issues/854 + if self.xr_session.get().is_some() { + return CompleteForRendering::Complete; + } + + if self.colors.iter().all(|att| att.borrow().is_none()) { + return CompleteForRendering::MissingColorAttachment; + } + + if !self.is_initialized.get() { + let attachments = [ + (&self.depth, constants::DEPTH_BUFFER_BIT), + (&self.stencil, constants::STENCIL_BUFFER_BIT), + ( + &self.depthstencil, + constants::DEPTH_BUFFER_BIT | constants::STENCIL_BUFFER_BIT, + ), + ]; + let mut clear_bits = 0; + for &(attachment, bits) in &attachments { + if let Some(ref att) = *attachment.borrow() { + if att.needs_initialization() { + att.mark_initialized(); + clear_bits |= bits; + } + } + } + for attachment in self.colors.iter() { + if let Some(ref att) = *attachment.borrow() { + if att.needs_initialization() { + att.mark_initialized(); + clear_bits |= constants::COLOR_BUFFER_BIT; + } + } + } + self.upcast::<WebGLObject>() + .context() + .initialize_framebuffer(clear_bits); + self.is_initialized.set(true); + } + + // TODO: if a framebuffer has an attachment which is invalid due to + // being outside a webxr rAF, should this make the framebuffer incomplete? + // https://github.com/immersive-web/layers/issues/196 + + CompleteForRendering::Complete } pub fn renderbuffer(&self, attachment: u32, rb: Option<&WebGLRenderbuffer>) -> WebGLResult<()> { - let binding = match attachment { - constants::COLOR_ATTACHMENT0 => &self.color, - constants::DEPTH_ATTACHMENT => &self.depth, - constants::STENCIL_ATTACHMENT => &self.stencil, - constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil, - _ => return Err(WebGLError::InvalidEnum), - }; + // Opaque framebuffers cannot have their attachments changed + // https://immersive-web.github.io/webxr/#opaque-framebuffer + self.validate_transparent()?; + + let binding = self + .attachment_binding(attachment) + .ok_or(WebGLError::InvalidEnum)?; let rb_id = match rb { Some(rb) => { - *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Renderbuffer(JS::from_ref(rb))); + if !rb.ever_bound() { + return Err(WebGLError::InvalidOperation); + } + *binding.borrow_mut() = + Some(WebGLFramebufferAttachment::Renderbuffer(Dom::from_ref(rb))); + rb.attach_to_framebuffer(self); Some(rb.id()) - } + }, - _ => { - *binding.borrow_mut() = None; - None - } + _ => None, }; - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::FramebufferRenderbuffer(constants::FRAMEBUFFER, - attachment, - constants::RENDERBUFFER, - rb_id))).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::FramebufferRenderbuffer( + self.target.get().unwrap(), + attachment, + constants::RENDERBUFFER, + rb_id, + )); + + if rb.is_none() { + self.detach_binding(binding, attachment)?; + } self.update_status(); + self.is_initialized.set(false); + Ok(()) + } + + fn detach_binding( + &self, + binding: &DomRefCell<Option<WebGLFramebufferAttachment>>, + attachment: u32, + ) -> WebGLResult<()> { + // Opaque framebuffers cannot have their attachments changed + // https://immersive-web.github.io/webxr/#opaque-framebuffer + self.validate_transparent()?; + + if let Some(att) = &*binding.borrow() { + att.detach(); + } + *binding.borrow_mut() = None; + if INTERESTING_ATTACHMENT_POINTS.contains(&attachment) { + self.reattach_depth_stencil()?; + } Ok(()) } - pub fn texture2d(&self, attachment: u32, textarget: u32, texture: Option<&WebGLTexture>, - level: i32) -> WebGLResult<()> { - let binding = match attachment { - constants::COLOR_ATTACHMENT0 => &self.color, - constants::DEPTH_ATTACHMENT => &self.depth, - constants::STENCIL_ATTACHMENT => &self.stencil, - constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil, - _ => return Err(WebGLError::InvalidEnum), + fn attachment_binding( + &self, + attachment: u32, + ) -> Option<&DomRefCell<Option<WebGLFramebufferAttachment>>> { + match attachment { + constants::COLOR_ATTACHMENT0..=constants::COLOR_ATTACHMENT15 => { + let idx = attachment - constants::COLOR_ATTACHMENT0; + self.colors.get(idx as usize) + }, + constants::DEPTH_ATTACHMENT => Some(&self.depth), + constants::STENCIL_ATTACHMENT => Some(&self.stencil), + constants::DEPTH_STENCIL_ATTACHMENT => Some(&self.depthstencil), + _ => None, + } + } + + fn reattach_depth_stencil(&self) -> WebGLResult<()> { + // Opaque framebuffers cannot have their attachments changed + // https://immersive-web.github.io/webxr/#opaque-framebuffer + self.validate_transparent()?; + + let reattach = |attachment: &WebGLFramebufferAttachment, attachment_point| { + let context = self.upcast::<WebGLObject>().context(); + match *attachment { + WebGLFramebufferAttachment::Renderbuffer(ref rb) => { + rb.attach_to_framebuffer(self); + context.send_command(WebGLCommand::FramebufferRenderbuffer( + self.target.get().unwrap(), + attachment_point, + constants::RENDERBUFFER, + Some(rb.id()), + )); + }, + WebGLFramebufferAttachment::Texture { ref texture, level } => { + texture.attach_to_framebuffer(self); + context.send_command(WebGLCommand::FramebufferTexture2D( + self.target.get().unwrap(), + attachment_point, + texture.target().expect("missing texture target"), + Some(texture.id()), + level, + )); + }, + } }; + // Since the DEPTH_STENCIL attachment causes both the DEPTH and STENCIL + // attachments to be overwritten, we need to ensure that we reattach + // the DEPTH and STENCIL attachments when any of those attachments + // is cleared. + if let Some(ref depth) = *self.depth.borrow() { + reattach(depth, constants::DEPTH_ATTACHMENT); + } + if let Some(ref stencil) = *self.stencil.borrow() { + reattach(stencil, constants::STENCIL_ATTACHMENT); + } + if let Some(ref depth_stencil) = *self.depthstencil.borrow() { + reattach(depth_stencil, constants::DEPTH_STENCIL_ATTACHMENT); + } + Ok(()) + } + + pub fn attachment(&self, attachment: u32) -> Option<WebGLFramebufferAttachmentRoot> { + let binding = self.attachment_binding(attachment)?; + binding + .borrow() + .as_ref() + .map(WebGLFramebufferAttachment::root) + } + + pub fn texture2d( + &self, + attachment: u32, + textarget: u32, + texture: Option<&WebGLTexture>, + level: i32, + ) -> WebGLResult<()> { + // Opaque framebuffers cannot have their attachments changed + // https://immersive-web.github.io/webxr/#opaque-framebuffer + self.validate_transparent()?; + if let Some(texture) = texture { + // "If texture is not zero, then texture must either + // name an existing texture object with an target of + // textarget, or texture must name an existing cube + // map texture and textarget must be one of: + // TEXTURE_CUBE_MAP_POSITIVE_X, + // TEXTURE_CUBE_MAP_POSITIVE_Y, + // TEXTURE_CUBE_MAP_POSITIVE_Z, + // TEXTURE_CUBE_MAP_NEGATIVE_X, + // TEXTURE_CUBE_MAP_NEGATIVE_Y, or + // TEXTURE_CUBE_MAP_NEGATIVE_Z. Otherwise, + // INVALID_OPERATION is generated." + let is_cube = match textarget { + constants::TEXTURE_2D => false, + + constants::TEXTURE_CUBE_MAP_POSITIVE_X => true, + constants::TEXTURE_CUBE_MAP_POSITIVE_Y => true, + constants::TEXTURE_CUBE_MAP_POSITIVE_Z => true, + constants::TEXTURE_CUBE_MAP_NEGATIVE_X => true, + constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => true, + constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => true, + + _ => return Err(WebGLError::InvalidEnum), + }; + + match texture.target() { + Some(constants::TEXTURE_CUBE_MAP) if is_cube => {}, + Some(_) if !is_cube => {}, + _ => return Err(WebGLError::InvalidOperation), + } + + let context = self.upcast::<WebGLObject>().context(); + let max_tex_size = if is_cube { + context.limits().max_cube_map_tex_size + } else { + context.limits().max_tex_size + }; + if level < 0 || level as u32 > log2(max_tex_size) { + return Err(WebGLError::InvalidValue); + } + } + self.texture2d_even_if_opaque(attachment, textarget, texture, level) + } + + pub fn texture2d_even_if_opaque( + &self, + attachment: u32, + textarget: u32, + texture: Option<&WebGLTexture>, + level: i32, + ) -> WebGLResult<()> { + let binding = self + .attachment_binding(attachment) + .ok_or(WebGLError::InvalidEnum)?; + let tex_id = match texture { // Note, from the GLES 2.0.25 spec, page 113: // "If texture is zero, then textarget and level are ignored." Some(texture) => { - // From the GLES 2.0.25 spec, page 113: - // - // "level specifies the mipmap level of the texture image - // to be attached to the framebuffer and must be - // 0. Otherwise, INVALID_VALUE is generated." - if level != 0 { - return Err(WebGLError::InvalidValue); - } + *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture { + texture: Dom::from_ref(texture), + level: level, + }); + texture.attach_to_framebuffer(self); - // "If texture is not zero, then texture must either - // name an existing texture object with an target of - // textarget, or texture must name an existing cube - // map texture and textarget must be one of: - // TEXTURE_CUBE_MAP_POSITIVE_X, - // TEXTURE_CUBE_MAP_POSITIVE_Y, - // TEXTURE_CUBE_MAP_POSITIVE_Z, - // TEXTURE_CUBE_MAP_NEGATIVE_X, - // TEXTURE_CUBE_MAP_NEGATIVE_Y, or - // TEXTURE_CUBE_MAP_NEGATIVE_Z. Otherwise, - // INVALID_OPERATION is generated." - let is_cube = match textarget { - constants::TEXTURE_2D => false, - - constants::TEXTURE_CUBE_MAP_POSITIVE_X => true, - constants::TEXTURE_CUBE_MAP_POSITIVE_Y => true, - constants::TEXTURE_CUBE_MAP_POSITIVE_Z => true, - constants::TEXTURE_CUBE_MAP_NEGATIVE_X => true, - constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => true, - constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => true, - - _ => return Err(WebGLError::InvalidEnum), - }; + Some(texture.id()) + }, + + _ => None, + }; + + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::FramebufferTexture2D( + self.target.get().unwrap(), + attachment, + textarget, + tex_id, + level, + )); + + if texture.is_none() { + self.detach_binding(binding, attachment)?; + } + + self.update_status(); + self.is_initialized.set(false); + Ok(()) + } + + pub fn texture_layer( + &self, + attachment: u32, + texture: Option<&WebGLTexture>, + level: i32, + layer: i32, + ) -> WebGLResult<()> { + let binding = self + .attachment_binding(attachment) + .ok_or(WebGLError::InvalidEnum)?; + + let context = self.upcast::<WebGLObject>().context(); - match texture.target() { - Some(constants::TEXTURE_CUBE_MAP) if is_cube => {} - Some(_) if !is_cube => {} + let tex_id = match texture { + Some(texture) => { + let (max_level, max_layer) = match texture.target() { + Some(constants::TEXTURE_3D) => ( + log2(context.limits().max_3d_texture_size), + context.limits().max_3d_texture_size - 1, + ), + Some(constants::TEXTURE_2D) => ( + log2(context.limits().max_tex_size), + context.limits().max_array_texture_layers - 1, + ), _ => return Err(WebGLError::InvalidOperation), + }; + + if level < 0 || level as u32 >= max_level { + return Err(WebGLError::InvalidValue); + } + if layer < 0 || layer as u32 >= max_layer { + return Err(WebGLError::InvalidValue); } *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture { - texture: JS::from_ref(texture), - level: level } - ); + texture: Dom::from_ref(texture), + level: level, + }); + texture.attach_to_framebuffer(self); Some(texture.id()) - } - - _ => { - *binding.borrow_mut() = None; - None - } + }, + _ => None, }; - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::FramebufferTexture2D(constants::FRAMEBUFFER, - attachment, - textarget, - tex_id, - level))).unwrap(); - - self.update_status(); + context.send_command(WebGLCommand::FramebufferTextureLayer( + self.target.get().unwrap(), + attachment, + tex_id, + level, + layer, + )); Ok(()) } fn with_matching_renderbuffers<F>(&self, rb: &WebGLRenderbuffer, mut closure: F) - where F: FnMut(&DOMRefCell<Option<WebGLFramebufferAttachment>>) + where + F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32), { - let attachments = [&self.color, - &self.depth, - &self.stencil, - &self.depthstencil]; - - for attachment in &attachments { - let matched = { - match *attachment.borrow() { - Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) - if rb.id() == att_rb.id() => true, - _ => false, - } - }; + let rb_id = rb.id(); + let attachments = [ + (&self.depth, constants::DEPTH_ATTACHMENT), + (&self.stencil, constants::STENCIL_ATTACHMENT), + (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT), + ]; + + fn has_matching_id( + attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>, + target: &WebGLRenderbufferId, + ) -> bool { + match *attachment.borrow() { + Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb)) => { + att_rb.id() == *target + }, + _ => false, + } + } + + for (attachment, name) in &attachments { + if has_matching_id(attachment, &rb_id) { + closure(attachment, *name); + } + } - if matched { - closure(attachment); + for (idx, attachment) in self.colors.iter().enumerate() { + if has_matching_id(attachment, &rb_id) { + let name = constants::COLOR_ATTACHMENT0 + idx as u32; + closure(attachment, name); } } } fn with_matching_textures<F>(&self, texture: &WebGLTexture, mut closure: F) - where F: FnMut(&DOMRefCell<Option<WebGLFramebufferAttachment>>) + where + F: FnMut(&DomRefCell<Option<WebGLFramebufferAttachment>>, u32), { - let attachments = [&self.color, - &self.depth, - &self.stencil, - &self.depthstencil]; - - for attachment in &attachments { - let matched = { - match *attachment.borrow() { - Some(WebGLFramebufferAttachment::Texture { texture: ref att_texture, .. }) - if texture.id() == att_texture.id() => true, - _ => false, - } - }; + let tex_id = texture.id(); + let attachments = [ + (&self.depth, constants::DEPTH_ATTACHMENT), + (&self.stencil, constants::STENCIL_ATTACHMENT), + (&self.depthstencil, constants::DEPTH_STENCIL_ATTACHMENT), + ]; + + fn has_matching_id( + attachment: &DomRefCell<Option<WebGLFramebufferAttachment>>, + target: &WebGLTextureId, + ) -> bool { + match *attachment.borrow() { + Some(WebGLFramebufferAttachment::Texture { + texture: ref att_texture, + .. + }) if att_texture.id() == *target => true, + _ => false, + } + } - if matched { - closure(attachment); + for (attachment, name) in &attachments { + if has_matching_id(attachment, &tex_id) { + closure(attachment, *name); + } + } + + for (idx, attachment) in self.colors.iter().enumerate() { + if has_matching_id(attachment, &tex_id) { + let name = constants::COLOR_ATTACHMENT0 + idx as u32; + closure(attachment, name); } } } - pub fn detach_renderbuffer(&self, rb: &WebGLRenderbuffer) { - self.with_matching_renderbuffers(rb, |att| { + pub fn detach_renderbuffer(&self, rb: &WebGLRenderbuffer) -> WebGLResult<()> { + // Opaque framebuffers cannot have their attachments changed + // https://immersive-web.github.io/webxr/#opaque-framebuffer + self.validate_transparent()?; + + let mut depth_or_stencil_updated = false; + self.with_matching_renderbuffers(rb, |att, name| { + depth_or_stencil_updated |= INTERESTING_ATTACHMENT_POINTS.contains(&name); + if let Some(att) = &*att.borrow() { + att.detach(); + } *att.borrow_mut() = None; self.update_status(); }); + + if depth_or_stencil_updated { + self.reattach_depth_stencil()?; + } + Ok(()) } - pub fn detach_texture(&self, texture: &WebGLTexture) { - self.with_matching_textures(texture, |att| { + pub fn detach_texture(&self, texture: &WebGLTexture) -> WebGLResult<()> { + // Opaque framebuffers cannot have their attachments changed + // https://immersive-web.github.io/webxr/#opaque-framebuffer + self.validate_transparent()?; + + let mut depth_or_stencil_updated = false; + self.with_matching_textures(texture, |att, name| { + depth_or_stencil_updated |= INTERESTING_ATTACHMENT_POINTS.contains(&name); + if let Some(att) = &*att.borrow() { + att.detach(); + } *att.borrow_mut() = None; self.update_status(); }); + + if depth_or_stencil_updated { + self.reattach_depth_stencil()?; + } + Ok(()) } pub fn invalidate_renderbuffer(&self, rb: &WebGLRenderbuffer) { - self.with_matching_renderbuffers(rb, |_att| { + self.with_matching_renderbuffers(rb, |_att, _| { + self.is_initialized.set(false); self.update_status(); }); } pub fn invalidate_texture(&self, texture: &WebGLTexture) { - self.with_matching_textures(texture, |_att| { + self.with_matching_textures(texture, |_att, _name| { self.update_status(); }); } + pub fn set_read_buffer(&self, buffer: u32) -> WebGLResult<()> { + let context = self.upcast::<WebGLObject>().context(); + + match buffer { + constants::NONE => {}, + _ if context.valid_color_attachment_enum(buffer) => {}, + _ => return Err(WebGLError::InvalidOperation), + }; + + *self.color_read_buffer.borrow_mut() = buffer; + context.send_command(WebGLCommand::ReadBuffer(buffer)); + Ok(()) + } + + pub fn set_draw_buffers(&self, buffers: Vec<u32>) -> WebGLResult<()> { + let context = self.upcast::<WebGLObject>().context(); + + if buffers.len() > context.limits().max_draw_buffers as usize { + return Err(WebGLError::InvalidValue); + } + + let enums_valid = buffers + .iter() + .all(|&val| val == constants::NONE || context.valid_color_attachment_enum(val)); + if !enums_valid { + return Err(WebGLError::InvalidEnum); + } + + let values_valid = buffers.iter().enumerate().all(|(i, &val)| { + val == constants::NONE || val == (constants::COLOR_ATTACHMENT0 + i as u32) + }); + if !values_valid { + return Err(WebGLError::InvalidOperation); + } + + *self.color_draw_buffers.borrow_mut() = buffers.clone(); + context.send_command(WebGLCommand::DrawBuffers(buffers)); + Ok(()) + } + + pub fn read_buffer(&self) -> u32 { + *self.color_read_buffer.borrow() + } + + pub fn draw_buffer_i(&self, index: usize) -> u32 { + let buffers = &*self.color_draw_buffers.borrow(); + *buffers.get(index).unwrap_or(&constants::NONE) + } + pub fn target(&self) -> Option<u32> { self.target.get() } @@ -370,6 +981,12 @@ impl WebGLFramebuffer { impl Drop for WebGLFramebuffer { fn drop(&mut self) { - self.delete(); + let _ = self.delete(Operation::Fallible); } } + +static INTERESTING_ATTACHMENT_POINTS: &[u32] = &[ + constants::DEPTH_ATTACHMENT, + constants::STENCIL_ATTACHMENT, + constants::DEPTH_STENCIL_ATTACHMENT, +]; diff --git a/components/script/dom/webglobject.rs b/components/script/dom/webglobject.rs index 123420b7cb0..999c7797b10 100644 --- a/components/script/dom/webglobject.rs +++ b/components/script/dom/webglobject.rs @@ -1,20 +1,28 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use dom::bindings::reflector::Reflector; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::Dom; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; use dom_struct::dom_struct; #[dom_struct] pub struct WebGLObject { reflector_: Reflector, + context: Dom<WebGLRenderingContext>, } impl WebGLObject { - pub fn new_inherited() -> WebGLObject { + pub fn new_inherited(context: &WebGLRenderingContext) -> WebGLObject { WebGLObject { reflector_: Reflector::new(), + context: Dom::from_ref(context), } } + + pub fn context(&self) -> &WebGLRenderingContext { + &self.context + } } diff --git a/components/script/dom/webglprogram.rs b/components/script/dom/webglprogram.rs index 3b2710a5d91..88cecf0296f 100644 --- a/components/script/dom/webglprogram.rs +++ b/components/script/dom/webglprogram.rs @@ -1,98 +1,137 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use canvas_traits::CanvasMsg; -use dom::bindings::codegen::Bindings::WebGLProgramBinding; -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::webglactiveinfo::WebGLActiveInfo; -use dom::webglobject::WebGLObject; -use dom::webglrenderingcontext::MAX_UNIFORM_AND_ATTRIBUTE_LEN; -use dom::webglshader::WebGLShader; -use dom::window::Window; +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::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::webglactiveinfo::WebGLActiveInfo; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use crate::dom::webglshader::WebGLShader; +use crate::dom::webgluniformlocation::WebGLUniformLocation; +use canvas_traits::webgl::{webgl_channel, WebGLProgramId, WebGLResult}; +use canvas_traits::webgl::{ + ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, WebGLCommand, WebGLError, +}; use dom_struct::dom_struct; -use ipc_channel::ipc::IpcSender; +use fnv::FnvHashSet; use std::cell::Cell; -use webrender_traits; -use webrender_traits::{WebGLCommand, WebGLError, WebGLParameter}; -use webrender_traits::{WebGLProgramId, WebGLResult}; #[dom_struct] pub struct WebGLProgram { webgl_object: WebGLObject, id: WebGLProgramId, - is_deleted: Cell<bool>, + is_in_use: Cell<bool>, + marked_for_deletion: Cell<bool>, link_called: Cell<bool>, linked: Cell<bool>, - fragment_shader: MutNullableJS<WebGLShader>, - vertex_shader: MutNullableJS<WebGLShader>, - #[ignore_heap_size_of = "Defined in ipc-channel"] - renderer: IpcSender<CanvasMsg>, + link_generation: Cell<u64>, + fragment_shader: MutNullableDom<WebGLShader>, + vertex_shader: MutNullableDom<WebGLShader>, + active_attribs: DomRefCell<Box<[ActiveAttribInfo]>>, + active_uniforms: DomRefCell<Box<[ActiveUniformInfo]>>, + active_uniform_blocks: DomRefCell<Box<[ActiveUniformBlockInfo]>>, + transform_feedback_varyings_length: Cell<i32>, + transform_feedback_mode: Cell<i32>, } impl WebGLProgram { - fn new_inherited(renderer: IpcSender<CanvasMsg>, - id: WebGLProgramId) - -> WebGLProgram { - WebGLProgram { - webgl_object: WebGLObject::new_inherited(), + fn new_inherited(context: &WebGLRenderingContext, id: WebGLProgramId) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), id: id, - is_deleted: Cell::new(false), - link_called: Cell::new(false), - linked: Cell::new(false), + is_in_use: Default::default(), + marked_for_deletion: Default::default(), + link_called: Default::default(), + linked: Default::default(), + link_generation: Default::default(), fragment_shader: Default::default(), vertex_shader: Default::default(), - renderer: renderer, + active_attribs: DomRefCell::new(vec![].into()), + active_uniforms: DomRefCell::new(vec![].into()), + active_uniform_blocks: DomRefCell::new(vec![].into()), + transform_feedback_varyings_length: Default::default(), + transform_feedback_mode: Default::default(), } } - pub fn maybe_new(window: &Window, renderer: IpcSender<CanvasMsg>) - -> Option<Root<WebGLProgram>> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateProgram(sender))).unwrap(); - - let result = receiver.recv().unwrap(); - result.map(|program_id| WebGLProgram::new(window, renderer, program_id)) + pub fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::CreateProgram(sender)); + receiver + .recv() + .unwrap() + .map(|id| WebGLProgram::new(context, id)) } - pub fn new(window: &Window, - renderer: IpcSender<CanvasMsg>, - id: WebGLProgramId) - -> Root<WebGLProgram> { - reflect_dom_object(box WebGLProgram::new_inherited(renderer, id), - window, - WebGLProgramBinding::Wrap) + pub fn new(context: &WebGLRenderingContext, id: WebGLProgramId) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLProgram::new_inherited(context, id)), + &*context.global(), + ) } } - impl WebGLProgram { pub fn id(&self) -> WebGLProgramId { self.id } /// glDeleteProgram - pub fn delete(&self) { - if !self.is_deleted.get() { - self.is_deleted.set(true); - let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteProgram(self.id))); + pub fn mark_for_deletion(&self, operation_fallibility: Operation) { + if self.marked_for_deletion.get() { + return; + } + self.marked_for_deletion.set(true); + let cmd = WebGLCommand::DeleteProgram(self.id); + let context = self.upcast::<WebGLObject>().context(); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(cmd), + Operation::Infallible => context.send_command(cmd), + } + if self.is_deleted() { + self.detach_shaders(); + } + } - if let Some(shader) = self.fragment_shader.get() { - shader.decrement_attached_counter(); - } + pub fn in_use(&self, value: bool) { + if self.is_in_use.get() == value { + return; + } + self.is_in_use.set(value); + if self.is_deleted() { + self.detach_shaders(); + } + } - if let Some(shader) = self.vertex_shader.get() { - shader.decrement_attached_counter(); - } + fn detach_shaders(&self) { + assert!(self.is_deleted()); + if let Some(shader) = self.fragment_shader.get() { + shader.decrement_attached_counter(); + self.fragment_shader.set(None); + } + if let Some(shader) = self.vertex_shader.get() { + shader.decrement_attached_counter(); + self.vertex_shader.set(None); } } + pub fn is_in_use(&self) -> bool { + self.is_in_use.get() + } + + pub fn is_marked_for_deletion(&self) -> bool { + self.marked_for_deletion.get() + } + pub fn is_deleted(&self) -> bool { - self.is_deleted.get() + self.marked_for_deletion.get() && !self.is_in_use.get() } pub fn is_linked(&self) -> bool { @@ -100,12 +139,13 @@ impl WebGLProgram { } /// glLinkProgram - pub fn link(&self) -> WebGLResult<()> { - if self.is_deleted() { - return Err(WebGLError::InvalidOperation); - } + pub fn link(&self) -> WebGLResult<()> { self.linked.set(false); - self.link_called.set(true); + self.link_generation + .set(self.link_generation.get().checked_add(1).unwrap()); + *self.active_attribs.borrow_mut() = Box::new([]); + *self.active_uniforms.borrow_mut() = Box::new([]); + *self.active_uniform_blocks.borrow_mut() = Box::new([]); match self.fragment_shader.get() { Some(ref shader) if shader.successfully_compiled() => {}, @@ -117,22 +157,63 @@ impl WebGLProgram { _ => return Ok(()), // callers use gl.LINK_STATUS to check link errors } - self.linked.set(true); - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::LinkProgram(self.id))).unwrap(); + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::LinkProgram(self.id, sender)); + let link_info = receiver.recv().unwrap(); + + { + let mut used_locs = FnvHashSet::default(); + let mut used_names = FnvHashSet::default(); + for active_attrib in &*link_info.active_attribs { + if active_attrib.location == -1 { + continue; + } + let columns = match active_attrib.type_ { + constants::FLOAT_MAT2 => 2, + constants::FLOAT_MAT3 => 3, + constants::FLOAT_MAT4 => 4, + _ => 1, + }; + assert!(used_names.insert(&*active_attrib.name)); + for column in 0..columns { + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.31 + if !used_locs.insert(active_attrib.location as u32 + column) { + return Ok(()); + } + } + } + for active_uniform in &*link_info.active_uniforms { + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.41 + if !used_names.insert(&*active_uniform.base_name) { + return Ok(()); + } + } + } + + self.linked.set(link_info.linked); + self.link_called.set(true); + self.transform_feedback_varyings_length + .set(link_info.transform_feedback_length); + self.transform_feedback_mode + .set(link_info.transform_feedback_mode); + *self.active_attribs.borrow_mut() = link_info.active_attribs; + *self.active_uniforms.borrow_mut() = link_info.active_uniforms; + *self.active_uniform_blocks.borrow_mut() = link_info.active_uniform_blocks; Ok(()) } - /// glUseProgram - pub fn use_program(&self) -> WebGLResult<()> { - if self.is_deleted() { - return Err(WebGLError::InvalidOperation); - } - if !self.linked.get() { - return Err(WebGLError::InvalidOperation); - } + pub fn active_attribs(&self) -> Ref<[ActiveAttribInfo]> { + Ref::map(self.active_attribs.borrow(), |attribs| &**attribs) + } - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::UseProgram(self.id))).unwrap(); - Ok(()) + pub fn active_uniforms(&self) -> Ref<[ActiveUniformInfo]> { + Ref::map(self.active_uniforms.borrow(), |uniforms| &**uniforms) + } + + pub fn active_uniform_blocks(&self) -> Ref<[ActiveUniformBlockInfo]> { + Ref::map(self.active_uniform_blocks.borrow(), |blocks| &**blocks) } /// glValidateProgram @@ -140,7 +221,9 @@ impl WebGLProgram { if self.is_deleted() { return Err(WebGLError::InvalidOperation); } - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::ValidateProgram(self.id))).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::ValidateProgram(self.id)); Ok(()) } @@ -155,11 +238,9 @@ impl WebGLProgram { _ => { error!("detachShader: Unexpected shader type"); return Err(WebGLError::InvalidValue); - } + }, }; - // TODO(emilio): Differentiate between same shader already assigned and other previous - // shader. if shader_slot.get().is_some() { return Err(WebGLError::InvalidOperation); } @@ -167,7 +248,9 @@ impl WebGLProgram { shader_slot.set(Some(shader)); shader.increment_attached_counter(); - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::AttachShader(self.id, shader.id()))).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::AttachShader(self.id, shader.id())); Ok(()) } @@ -180,24 +263,23 @@ impl WebGLProgram { let shader_slot = match shader.gl_type() { constants::FRAGMENT_SHADER => &self.fragment_shader, constants::VERTEX_SHADER => &self.vertex_shader, - _ => { - error!("detachShader: Unexpected shader type"); - return Err(WebGLError::InvalidValue); - } + _ => return Err(WebGLError::InvalidValue), }; match shader_slot.get() { - Some(ref attached_shader) if attached_shader.id() != shader.id() => - return Err(WebGLError::InvalidOperation), - None => - return Err(WebGLError::InvalidOperation), - _ => {} + Some(ref attached_shader) if attached_shader.id() != shader.id() => { + return Err(WebGLError::InvalidOperation); + }, + None => return Err(WebGLError::InvalidOperation), + _ => {}, } shader_slot.set(None); shader.decrement_attached_counter(); - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DetachShader(self.id, shader.id()))).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::DetachShader(self.id, shader.id())); Ok(()) } @@ -207,123 +289,424 @@ impl WebGLProgram { if self.is_deleted() { return Err(WebGLError::InvalidOperation); } - if name.len() > MAX_UNIFORM_AND_ATTRIBUTE_LEN { - return Err(WebGLError::InvalidValue); - } - // Check if the name is reserved - if name.starts_with("gl_") || name.starts_with("webgl") || name.starts_with("_webgl_") { + if !validate_glsl_name(&name)? { + return Ok(()); + } + if name.starts_with("gl_") { return Err(WebGLError::InvalidOperation); } - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::BindAttribLocation(self.id, index, String::from(name)))) - .unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::BindAttribLocation( + self.id, + index, + name.into(), + )); Ok(()) } - pub fn get_active_uniform(&self, index: u32) -> WebGLResult<Root<WebGLActiveInfo>> { + pub fn get_active_uniform(&self, index: u32) -> WebGLResult<DomRoot<WebGLActiveInfo>> { if self.is_deleted() { return Err(WebGLError::InvalidValue); } - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::GetActiveUniform(self.id, index, sender))) - .unwrap(); - - receiver.recv().unwrap().map(|(size, ty, name)| - WebGLActiveInfo::new(self.global().as_window(), size, ty, DOMString::from(name))) + let uniforms = self.active_uniforms.borrow(); + let data = uniforms + .get(index as usize) + .ok_or(WebGLError::InvalidValue)?; + Ok(WebGLActiveInfo::new( + self.global().as_window(), + data.size.unwrap_or(1), + data.type_, + data.name().into(), + )) } /// glGetActiveAttrib - pub fn get_active_attrib(&self, index: u32) -> WebGLResult<Root<WebGLActiveInfo>> { + pub fn get_active_attrib(&self, index: u32) -> WebGLResult<DomRoot<WebGLActiveInfo>> { if self.is_deleted() { return Err(WebGLError::InvalidValue); } - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::GetActiveAttrib(self.id, index, sender))) - .unwrap(); - - receiver.recv().unwrap().map(|(size, ty, name)| - WebGLActiveInfo::new(self.global().as_window(), size, ty, DOMString::from(name))) + let attribs = self.active_attribs.borrow(); + let data = attribs + .get(index as usize) + .ok_or(WebGLError::InvalidValue)?; + Ok(WebGLActiveInfo::new( + self.global().as_window(), + data.size, + data.type_, + data.name.clone().into(), + )) } /// glGetAttribLocation - pub fn get_attrib_location(&self, name: DOMString) -> WebGLResult<Option<i32>> { + pub fn get_attrib_location(&self, name: DOMString) -> WebGLResult<i32> { if !self.is_linked() || self.is_deleted() { return Err(WebGLError::InvalidOperation); } - if name.len() > MAX_UNIFORM_AND_ATTRIBUTE_LEN { - return Err(WebGLError::InvalidValue); + + if !validate_glsl_name(&name)? { + return Ok(-1); + } + if name.starts_with("gl_") { + return Ok(-1); + } + + let location = self + .active_attribs + .borrow() + .iter() + .find(|attrib| attrib.name == &*name) + .map_or(-1, |attrib| attrib.location); + Ok(location) + } + + /// glGetFragDataLocation + pub fn get_frag_data_location(&self, name: DOMString) -> WebGLResult<i32> { + if !self.is_linked() || self.is_deleted() { + return Err(WebGLError::InvalidOperation); } - // Check if the name is reserved + if !validate_glsl_name(&name)? { + return Ok(-1); + } if name.starts_with("gl_") { + return Ok(-1); + } + + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::GetFragDataLocation( + self.id, + name.into(), + sender, + )); + Ok(receiver.recv().unwrap()) + } + + /// glGetUniformLocation + pub fn get_uniform_location( + &self, + name: DOMString, + ) -> WebGLResult<Option<DomRoot<WebGLUniformLocation>>> { + if !self.is_linked() || self.is_deleted() { return Err(WebGLError::InvalidOperation); } - if name.starts_with("webgl") || name.starts_with("_webgl_") { + if !validate_glsl_name(&name)? { + return Ok(None); + } + if name.starts_with("gl_") { return Ok(None); } - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::GetAttribLocation(self.id, String::from(name), sender))) - .unwrap(); + let (size, type_) = { + let (base_name, array_index) = match parse_uniform_name(&name) { + Some((name, index)) if index.map_or(true, |i| i >= 0) => (name, index), + _ => return Ok(None), + }; + + let uniforms = self.active_uniforms.borrow(); + match uniforms + .iter() + .find(|attrib| &*attrib.base_name == base_name) + { + Some(uniform) if array_index.is_none() || array_index < uniform.size => ( + uniform + .size + .map(|size| size - array_index.unwrap_or_default()), + uniform.type_, + ), + _ => return Ok(None), + } + }; + + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::GetUniformLocation( + self.id, + name.into(), + sender, + )); + let location = receiver.recv().unwrap(); + let context_id = self.upcast::<WebGLObject>().context().context_id(); + + Ok(Some(WebGLUniformLocation::new( + self.global().as_window(), + location, + context_id, + self.id, + self.link_generation.get(), + size, + type_, + ))) + } + + pub fn get_uniform_block_index(&self, name: DOMString) -> WebGLResult<u32> { + if !self.link_called.get() || self.is_deleted() { + return Err(WebGLError::InvalidOperation); + } + + if !validate_glsl_name(&name)? { + return Ok(constants2::INVALID_INDEX); + } + + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::GetUniformBlockIndex( + self.id, + name.into(), + sender, + )); Ok(receiver.recv().unwrap()) } - /// glGetUniformLocation - pub fn get_uniform_location(&self, name: DOMString) -> WebGLResult<Option<i32>> { + pub fn get_uniform_indices(&self, names: Vec<DOMString>) -> WebGLResult<Vec<u32>> { + if !self.link_called.get() || self.is_deleted() { + return Err(WebGLError::InvalidOperation); + } + + let validation_errors = names + .iter() + .map(|name| validate_glsl_name(&name)) + .collect::<Vec<_>>(); + let first_validation_error = validation_errors.iter().find(|result| result.is_err()); + if let Some(error) = first_validation_error { + return Err(error.unwrap_err()); + } + + let names = names + .iter() + .map(|name| name.to_string()) + .collect::<Vec<_>>(); + + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::GetUniformIndices(self.id, names, sender)); + Ok(receiver.recv().unwrap()) + } + + pub fn get_active_uniforms(&self, indices: Vec<u32>, pname: u32) -> WebGLResult<Vec<i32>> { if !self.is_linked() || self.is_deleted() { return Err(WebGLError::InvalidOperation); } - if name.len() > MAX_UNIFORM_AND_ATTRIBUTE_LEN { + + match pname { + constants2::UNIFORM_TYPE | + constants2::UNIFORM_SIZE | + constants2::UNIFORM_BLOCK_INDEX | + constants2::UNIFORM_OFFSET | + constants2::UNIFORM_ARRAY_STRIDE | + constants2::UNIFORM_MATRIX_STRIDE | + constants2::UNIFORM_IS_ROW_MAJOR => {}, + _ => return Err(WebGLError::InvalidEnum), + } + + if indices.len() > self.active_uniforms.borrow().len() { return Err(WebGLError::InvalidValue); } - // Check if the name is reserved - if name.starts_with("webgl") || name.starts_with("_webgl_") { - return Ok(None); + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::GetActiveUniforms( + self.id, indices, pname, sender, + )); + Ok(receiver.recv().unwrap()) + } + + pub fn get_active_uniform_block_parameter( + &self, + block_index: u32, + pname: u32, + ) -> WebGLResult<Vec<i32>> { + if !self.link_called.get() || self.is_deleted() { + return Err(WebGLError::InvalidOperation); + } + + if block_index as usize >= self.active_uniform_blocks.borrow().len() { + return Err(WebGLError::InvalidValue); } - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::GetUniformLocation(self.id, String::from(name), sender))) - .unwrap(); + match pname { + constants2::UNIFORM_BLOCK_BINDING | + constants2::UNIFORM_BLOCK_DATA_SIZE | + constants2::UNIFORM_BLOCK_ACTIVE_UNIFORMS | + constants2::UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES | + constants2::UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER | + constants2::UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER => {}, + _ => return Err(WebGLError::InvalidEnum), + } + + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>().context().send_command( + WebGLCommand::GetActiveUniformBlockParameter(self.id, block_index, pname, sender), + ); Ok(receiver.recv().unwrap()) } + pub fn get_active_uniform_block_name(&self, block_index: u32) -> WebGLResult<String> { + if !self.link_called.get() || self.is_deleted() { + return Err(WebGLError::InvalidOperation); + } + + if block_index as usize >= self.active_uniform_blocks.borrow().len() { + return Err(WebGLError::InvalidValue); + } + + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>().context().send_command( + WebGLCommand::GetActiveUniformBlockName(self.id, block_index, sender), + ); + Ok(receiver.recv().unwrap()) + } + + pub fn bind_uniform_block(&self, block_index: u32, block_binding: u32) -> WebGLResult<()> { + if block_index as usize >= self.active_uniform_blocks.borrow().len() { + return Err(WebGLError::InvalidValue); + } + + let mut active_uniforms = self.active_uniforms.borrow_mut(); + if active_uniforms.len() > block_binding as usize { + active_uniforms[block_binding as usize].bind_index = Some(block_binding); + } + + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::UniformBlockBinding( + self.id, + block_index, + block_binding, + )); + Ok(()) + } + /// glGetProgramInfoLog pub fn get_info_log(&self) -> WebGLResult<String> { if self.is_deleted() { - return Err(WebGLError::InvalidOperation); + return Err(WebGLError::InvalidValue); } if self.link_called.get() { let shaders_compiled = match (self.fragment_shader.get(), self.vertex_shader.get()) { (Some(fs), Some(vs)) => fs.successfully_compiled() && vs.successfully_compiled(), - _ => false + _ => false, }; if !shaders_compiled { return Ok("One or more shaders failed to compile".to_string()); } } - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::GetProgramInfoLog(self.id, sender))).unwrap(); + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::GetProgramInfoLog(self.id, sender)); Ok(receiver.recv().unwrap()) } - /// glGetProgramParameter - pub fn parameter(&self, param_id: u32) -> WebGLResult<WebGLParameter> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::GetProgramParameter(self.id, param_id, sender))).unwrap(); - receiver.recv().unwrap() + pub fn attached_shaders(&self) -> WebGLResult<Vec<DomRoot<WebGLShader>>> { + if self.marked_for_deletion.get() { + return Err(WebGLError::InvalidValue); + } + Ok( + match (self.vertex_shader.get(), self.fragment_shader.get()) { + (Some(vertex_shader), Some(fragment_shader)) => { + vec![vertex_shader, fragment_shader] + }, + (Some(shader), None) | (None, Some(shader)) => vec![shader], + (None, None) => vec![], + }, + ) + } + + pub fn link_generation(&self) -> u64 { + self.link_generation.get() + } + + pub fn transform_feedback_varyings_length(&self) -> i32 { + self.transform_feedback_varyings_length.get() + } + + pub fn transform_feedback_buffer_mode(&self) -> i32 { + self.transform_feedback_mode.get() } } impl Drop for WebGLProgram { fn drop(&mut self) { - self.delete(); + self.in_use(false); + self.mark_for_deletion(Operation::Fallible); } } + +fn validate_glsl_name(name: &str) -> WebGLResult<bool> { + if name.is_empty() { + return Ok(false); + } + if name.len() > MAX_UNIFORM_AND_ATTRIBUTE_LEN { + return Err(WebGLError::InvalidValue); + } + for c in name.chars() { + validate_glsl_char(c)?; + } + if name.starts_with("webgl_") || name.starts_with("_webgl_") { + return Err(WebGLError::InvalidOperation); + } + Ok(true) +} + +fn validate_glsl_char(c: char) -> WebGLResult<()> { + match c { + 'a'..='z' | + 'A'..='Z' | + '0'..='9' | + ' ' | + '\t' | + '\u{11}' | + '\u{12}' | + '\r' | + '\n' | + '_' | + '.' | + '+' | + '-' | + '/' | + '*' | + '%' | + '<' | + '>' | + '[' | + ']' | + '(' | + ')' | + '{' | + '}' | + '^' | + '|' | + '&' | + '~' | + '=' | + '!' | + ':' | + ';' | + ',' | + '?' => Ok(()), + _ => Err(WebGLError::InvalidValue), + } +} + +fn parse_uniform_name(name: &str) -> Option<(&str, Option<i32>)> { + if !name.ends_with(']') { + return Some((name, None)); + } + let bracket_pos = name[..name.len() - 1].rfind('[')?; + let index = name[(bracket_pos + 1)..(name.len() - 1)] + .parse::<i32>() + .ok()?; + Some((&name[..bracket_pos], Some(index))) +} + +pub const MAX_UNIFORM_AND_ATTRIBUTE_LEN: usize = 256; diff --git a/components/script/dom/webglquery.rs b/components/script/dom/webglquery.rs new file mode 100644 index 00000000000..454fe72954e --- /dev/null +++ b/components/script/dom/webglquery.rs @@ -0,0 +1,191 @@ +/* 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 crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use crate::task_source::TaskSource; +use canvas_traits::webgl::WebGLError::*; +use canvas_traits::webgl::{webgl_channel, WebGLCommand, WebGLQueryId}; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct WebGLQuery { + webgl_object: WebGLObject, + gl_id: WebGLQueryId, + gl_target: Cell<Option<u32>>, + marked_for_deletion: Cell<bool>, + query_result_available: Cell<Option<u32>>, + query_result: Cell<u32>, +} + +impl WebGLQuery { + fn new_inherited(context: &WebGLRenderingContext, id: WebGLQueryId) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), + gl_id: id, + gl_target: Cell::new(None), + marked_for_deletion: Cell::new(false), + query_result_available: Cell::new(None), + query_result: Cell::new(0), + } + } + + pub fn new(context: &WebGLRenderingContext) -> DomRoot<Self> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::GenerateQuery(sender)); + let id = receiver.recv().unwrap(); + + reflect_dom_object( + Box::new(Self::new_inherited(context, id)), + &*context.global(), + ) + } + + pub fn begin( + &self, + context: &WebGLRenderingContext, + target: u32, + ) -> Result<(), canvas_traits::webgl::WebGLError> { + if self.marked_for_deletion.get() { + return Err(InvalidOperation); + } + if let Some(current_target) = self.gl_target.get() { + if current_target != target { + return Err(InvalidOperation); + } + } + match target { + constants::ANY_SAMPLES_PASSED | + constants::ANY_SAMPLES_PASSED_CONSERVATIVE | + constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => (), + _ => return Err(InvalidEnum), + } + self.gl_target.set(Some(target)); + + context.send_command(WebGLCommand::BeginQuery(target, self.gl_id)); + Ok(()) + } + + pub fn end( + &self, + context: &WebGLRenderingContext, + target: u32, + ) -> Result<(), canvas_traits::webgl::WebGLError> { + if self.marked_for_deletion.get() { + return Err(InvalidOperation); + } + if let Some(current_target) = self.gl_target.get() { + if current_target != target { + return Err(InvalidOperation); + } + } + match target { + constants::ANY_SAMPLES_PASSED | + constants::ANY_SAMPLES_PASSED_CONSERVATIVE | + constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => (), + _ => return Err(InvalidEnum), + } + context.send_command(WebGLCommand::EndQuery(target)); + Ok(()) + } + + pub fn delete(&self, operation_fallibility: Operation) { + if !self.marked_for_deletion.get() { + self.marked_for_deletion.set(true); + + let context = self.upcast::<WebGLObject>().context(); + let command = WebGLCommand::DeleteQuery(self.gl_id); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(command), + Operation::Infallible => context.send_command(command), + } + } + } + + pub fn is_valid(&self) -> bool { + !self.marked_for_deletion.get() && self.target().is_some() + } + + pub fn target(&self) -> Option<u32> { + self.gl_target.get() + } + + fn update_results(&self, context: &WebGLRenderingContext) { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::GetQueryState( + sender, + self.gl_id, + constants::QUERY_RESULT_AVAILABLE, + )); + let is_available = receiver.recv().unwrap(); + if is_available == 0 { + self.query_result_available.set(None); + return; + } + + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::GetQueryState( + sender, + self.gl_id, + constants::QUERY_RESULT, + )); + + self.query_result.set(receiver.recv().unwrap()); + self.query_result_available.set(Some(is_available)); + } + + #[cfg_attr(rustfmt, rustfmt_skip)] + pub fn get_parameter( + &self, + context: &WebGLRenderingContext, + pname: u32, + ) -> Result<u32, canvas_traits::webgl::WebGLError> { + if !self.is_valid() { + return Err(InvalidOperation); + } + match pname { + constants::QUERY_RESULT | + constants::QUERY_RESULT_AVAILABLE => {}, + _ => return Err(InvalidEnum), + } + + if self.query_result_available.get().is_none() { + self.query_result_available.set(Some(0)); + + let this = Trusted::new(self); + let context = Trusted::new(context); + let task = task!(request_query_state: move || { + let this = this.root(); + let context = context.root(); + this.update_results(&context); + }); + + let global = self.global(); + global + .as_window() + .task_manager() + .dom_manipulation_task_source() + .queue(task, global.upcast()) + .unwrap(); + } + + match pname { + constants::QUERY_RESULT => Ok(self.query_result.get()), + constants::QUERY_RESULT_AVAILABLE => Ok(self.query_result_available.get().unwrap()), + _ => unreachable!(), + } + } +} + +impl Drop for WebGLQuery { + fn drop(&mut self) { + self.delete(Operation::Fallible); + } +} diff --git a/components/script/dom/webglrenderbuffer.rs b/components/script/dom/webglrenderbuffer.rs index 7094a6b3734..204fe9ab46e 100644 --- a/components/script/dom/webglrenderbuffer.rs +++ b/components/script/dom/webglrenderbuffer.rs @@ -1,20 +1,23 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use canvas_traits::CanvasMsg; -use dom::bindings::codegen::Bindings::WebGLRenderbufferBinding; -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::webglobject::WebGLObject; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::EXTColorBufferHalfFloatBinding::EXTColorBufferHalfFloatConstants; +use crate::dom::bindings::codegen::Bindings::WEBGLColorBufferFloatBinding::WEBGLColorBufferFloatConstants; +use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::webglframebuffer::WebGLFramebuffer; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use canvas_traits::webgl::{ + webgl_channel, GlType, InternalFormatIntVec, WebGLCommand, WebGLError, WebGLRenderbufferId, + WebGLResult, WebGLVersion, +}; use dom_struct::dom_struct; -use ipc_channel::ipc::IpcSender; use std::cell::Cell; -use webrender_traits; -use webrender_traits::{WebGLCommand, WebGLRenderbufferId, WebGLResult, WebGLError}; #[dom_struct] pub struct WebGLRenderbuffer { @@ -24,45 +27,41 @@ pub struct WebGLRenderbuffer { is_deleted: Cell<bool>, size: Cell<Option<(i32, i32)>>, internal_format: Cell<Option<u32>>, - #[ignore_heap_size_of = "Defined in ipc-channel"] - renderer: IpcSender<CanvasMsg>, + is_initialized: Cell<bool>, + attached_framebuffer: MutNullableDom<WebGLFramebuffer>, } impl WebGLRenderbuffer { - fn new_inherited(renderer: IpcSender<CanvasMsg>, - id: WebGLRenderbufferId) - -> WebGLRenderbuffer { - WebGLRenderbuffer { - webgl_object: WebGLObject::new_inherited(), + fn new_inherited(context: &WebGLRenderingContext, id: WebGLRenderbufferId) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), id: id, ever_bound: Cell::new(false), is_deleted: Cell::new(false), - renderer: renderer, internal_format: Cell::new(None), size: Cell::new(None), + is_initialized: Cell::new(false), + attached_framebuffer: Default::default(), } } - pub fn maybe_new(window: &Window, renderer: IpcSender<CanvasMsg>) - -> Option<Root<WebGLRenderbuffer>> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateRenderbuffer(sender))).unwrap(); - - let result = receiver.recv().unwrap(); - result.map(|renderbuffer_id| WebGLRenderbuffer::new(window, renderer, renderbuffer_id)) + pub fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::CreateRenderbuffer(sender)); + receiver + .recv() + .unwrap() + .map(|id| WebGLRenderbuffer::new(context, id)) } - pub fn new(window: &Window, - renderer: IpcSender<CanvasMsg>, - id: WebGLRenderbufferId) - -> Root<WebGLRenderbuffer> { - reflect_dom_object(box WebGLRenderbuffer::new_inherited(renderer, id), - window, - WebGLRenderbufferBinding::Wrap) + pub fn new(context: &WebGLRenderingContext, id: WebGLRenderbufferId) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLRenderbuffer::new_inherited(context, id)), + &*context.global(), + ) } } - impl WebGLRenderbuffer { pub fn id(&self) -> WebGLRenderbufferId { self.id @@ -72,16 +71,52 @@ impl WebGLRenderbuffer { self.size.get() } + pub fn internal_format(&self) -> u32 { + self.internal_format.get().unwrap_or(constants::RGBA4) + } + + pub fn mark_initialized(&self) { + self.is_initialized.set(true); + } + + pub fn is_initialized(&self) -> bool { + self.is_initialized.get() + } + pub fn bind(&self, target: u32) { self.ever_bound.set(true); - let msg = CanvasMsg::WebGL(WebGLCommand::BindRenderbuffer(target, Some(self.id))); - self.renderer.send(msg).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::BindRenderbuffer(target, Some(self.id))); } - pub fn delete(&self) { + pub fn delete(&self, operation_fallibility: Operation) { if !self.is_deleted.get() { self.is_deleted.set(true); - let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteRenderbuffer(self.id))); + + let context = self.upcast::<WebGLObject>().context(); + + /* + If a renderbuffer object is deleted while its image is attached to one or more + attachment points in a currently bound framebuffer object, then it is as if + FramebufferRenderbuffer had been called, with a renderbuffer of zero, for each + attachment point to which this image was attached in that framebuffer object. + In other words,the renderbuffer image is first detached from all attachment points + in that frame-buffer object. + - GLES 3.0, 4.4.2.3, "Attaching Renderbuffer Images to a Framebuffer" + */ + if let Some(fb) = context.get_draw_framebuffer_slot().get() { + let _ = fb.detach_renderbuffer(self); + } + if let Some(fb) = context.get_read_framebuffer_slot().get() { + let _ = fb.detach_renderbuffer(self); + } + + let cmd = WebGLCommand::DeleteRenderbuffer(self.id); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(cmd), + Operation::Infallible => context.send_command(cmd), + } } } @@ -93,29 +128,154 @@ impl WebGLRenderbuffer { self.ever_bound.get() } - pub fn storage(&self, internal_format: u32, width: i32, height: i32) -> WebGLResult<()> { + pub fn storage( + &self, + api_type: GlType, + sample_count: i32, + internal_format: u32, + width: i32, + height: i32, + ) -> WebGLResult<()> { + let is_gles = api_type == GlType::Gles; + let webgl_version = self.upcast().context().webgl_version(); + // Validate the internal_format, and save it for completeness // validation. - match internal_format { - constants::RGBA4 | - constants::DEPTH_STENCIL | - constants::DEPTH_COMPONENT16 | - constants::STENCIL_INDEX8 => - self.internal_format.set(Some(internal_format)), - + let actual_format = match internal_format { + constants::RGBA4 | constants::DEPTH_COMPONENT16 | constants::STENCIL_INDEX8 => { + internal_format + }, + constants::R8 | + constants::R8UI | + constants::R8I | + constants::R16UI | + constants::R16I | + constants::R32UI | + constants::R32I | + constants::RG8 | + constants::RG8UI | + constants::RG8I | + constants::RG16UI | + constants::RG16I | + constants::RG32UI | + constants::RG32I | + constants::RGB8 | + constants::RGBA8 | + constants::SRGB8_ALPHA8 | + constants::RGB10_A2 | + constants::RGBA8UI | + constants::RGBA8I | + constants::RGB10_A2UI | + constants::RGBA16UI | + constants::RGBA16I | + constants::RGBA32I | + constants::RGBA32UI | + constants::DEPTH_COMPONENT24 | + constants::DEPTH_COMPONENT32F | + constants::DEPTH24_STENCIL8 | + constants::DEPTH32F_STENCIL8 => match webgl_version { + WebGLVersion::WebGL1 => return Err(WebGLError::InvalidEnum), + _ => internal_format, + }, + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.8 + constants::DEPTH_STENCIL => constants::DEPTH24_STENCIL8, + constants::RGB5_A1 => { + // 16-bit RGBA formats are not supported on desktop GL. + if is_gles { + constants::RGB5_A1 + } else { + constants::RGBA8 + } + }, + constants::RGB565 => { + // RGB565 is not supported on desktop GL. + if is_gles { + constants::RGB565 + } else { + constants::RGB8 + } + }, + EXTColorBufferHalfFloatConstants::RGBA16F_EXT | + EXTColorBufferHalfFloatConstants::RGB16F_EXT => { + if !self + .upcast() + .context() + .extension_manager() + .is_half_float_buffer_renderable() + { + return Err(WebGLError::InvalidEnum); + } + internal_format + }, + WEBGLColorBufferFloatConstants::RGBA32F_EXT => { + if !self + .upcast() + .context() + .extension_manager() + .is_float_buffer_renderable() + { + return Err(WebGLError::InvalidEnum); + } + internal_format + }, _ => return Err(WebGLError::InvalidEnum), }; - // FIXME: Check that w/h are < MAX_RENDERBUFFER_SIZE + if webgl_version != WebGLVersion::WebGL1 { + let (sender, receiver) = webgl_channel().unwrap(); + self.upcast::<WebGLObject>().context().send_command( + WebGLCommand::GetInternalFormatIntVec( + constants::RENDERBUFFER, + internal_format, + InternalFormatIntVec::Samples, + sender, + ), + ); + let samples = receiver.recv().unwrap(); + if sample_count < 0 || sample_count > samples.get(0).cloned().unwrap_or(0) { + return Err(WebGLError::InvalidOperation); + } + } - // FIXME: Invalidate completeness after the call + self.internal_format.set(Some(internal_format)); + self.is_initialized.set(false); - let msg = CanvasMsg::WebGL(WebGLCommand::RenderbufferStorage(constants::RENDERBUFFER, - internal_format, width, height)); - self.renderer.send(msg).unwrap(); + if let Some(fb) = self.attached_framebuffer.get() { + fb.update_status(); + } - self.size.set(Some((width, height))); + let command = match sample_count { + 0 => WebGLCommand::RenderbufferStorage( + constants::RENDERBUFFER, + actual_format, + width, + height, + ), + _ => WebGLCommand::RenderbufferStorageMultisample( + constants::RENDERBUFFER, + sample_count, + actual_format, + width, + height, + ), + }; + self.upcast::<WebGLObject>().context().send_command(command); + self.size.set(Some((width, height))); Ok(()) } + + pub fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) { + self.attached_framebuffer.set(Some(fb)); + } + + pub fn detach_from_framebuffer(&self) { + self.attached_framebuffer.set(None); + } +} + +impl Drop for WebGLRenderbuffer { + fn drop(&mut self) { + self.delete(Operation::Fallible); + } } diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 34f5b368a42..de5d01909a9 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -1,73 +1,89 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; -use canvas_traits::{CanvasCommonMsg, CanvasMsg, byte_swap, multiply_u8_pixel}; -use core::nonzero::NonZero; -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{self, WebGLContextAttributes}; -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods; -use dom::bindings::codegen::UnionTypes::ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement; -use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible}; -use dom::bindings::error::{Error, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, LayoutJS, MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::globalscope::GlobalScope; -use dom::htmlcanvaselement::HTMLCanvasElement; -use dom::htmlcanvaselement::utils as canvas_utils; -use dom::node::{Node, NodeDamage, window_from_node}; -use dom::webgl_validations::WebGLValidator; -use dom::webgl_validations::tex_image_2d::{CommonTexImage2DValidator, CommonTexImage2DValidatorResult}; -use dom::webgl_validations::tex_image_2d::{TexImage2DValidator, TexImage2DValidatorResult}; -use dom::webgl_validations::types::{TexDataType, TexFormat, TexImageTarget}; -use dom::webglactiveinfo::WebGLActiveInfo; -use dom::webglbuffer::WebGLBuffer; -use dom::webglcontextevent::WebGLContextEvent; -use dom::webglframebuffer::WebGLFramebuffer; -use dom::webglprogram::WebGLProgram; -use dom::webglrenderbuffer::WebGLRenderbuffer; -use dom::webglshader::WebGLShader; -use dom::webglshaderprecisionformat::WebGLShaderPrecisionFormat; -use dom::webgltexture::{TexParameterValue, WebGLTexture}; -use dom::webgluniformlocation::WebGLUniformLocation; -use dom::window::Window; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut}; +use crate::dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysConstants; +use crate::dom::bindings::codegen::Bindings::EXTBlendMinmaxBinding::EXTBlendMinmaxConstants; +use crate::dom::bindings::codegen::Bindings::OESVertexArrayObjectBinding::OESVertexArrayObjectConstants; +use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::TexImageSource; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods; +use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer; +use crate::dom::bindings::codegen::UnionTypes::Float32ArrayOrUnrestrictedFloatSequence; +use crate::dom::bindings::codegen::UnionTypes::Int32ArrayOrLongSequence; +use crate::dom::bindings::conversions::{DerivedFrom, ToJSValConvertible}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::element::cors_setting_for_element; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::htmlcanvaselement::utils as canvas_utils; +use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutCanvasRenderingContextHelpers}; +use crate::dom::htmliframeelement::HTMLIFrameElement; +use crate::dom::node::{document_from_node, window_from_node, Node, NodeDamage}; +use crate::dom::promise::Promise; +use crate::dom::vertexarrayobject::VertexAttribData; +use crate::dom::webgl_extensions::WebGLExtensions; +use crate::dom::webgl_validations::tex_image_2d::{ + CommonCompressedTexImage2DValidatorResult, CommonTexImage2DValidator, + CommonTexImage2DValidatorResult, CompressedTexImage2DValidator, + CompressedTexSubImage2DValidator, TexImage2DValidator, TexImage2DValidatorResult, +}; +use crate::dom::webgl_validations::types::TexImageTarget; +use crate::dom::webgl_validations::WebGLValidator; +use crate::dom::webglactiveinfo::WebGLActiveInfo; +use crate::dom::webglbuffer::WebGLBuffer; +use crate::dom::webglcontextevent::WebGLContextEvent; +use crate::dom::webglframebuffer::{ + CompleteForRendering, WebGLFramebuffer, WebGLFramebufferAttachmentRoot, +}; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglprogram::WebGLProgram; +use crate::dom::webglrenderbuffer::WebGLRenderbuffer; +use crate::dom::webglshader::WebGLShader; +use crate::dom::webglshaderprecisionformat::WebGLShaderPrecisionFormat; +use crate::dom::webgltexture::{TexParameterValue, WebGLTexture}; +use crate::dom::webgluniformlocation::WebGLUniformLocation; +use crate::dom::webglvertexarrayobject::WebGLVertexArrayObject; +use crate::dom::webglvertexarrayobjectoes::WebGLVertexArrayObjectOES; +use crate::dom::window::Window; +use crate::script_runtime::JSContext as SafeJSContext; +#[cfg(feature = "webgl_backtrace")] +use backtrace::Backtrace; +use canvas_traits::webgl::WebGLError::*; +use canvas_traits::webgl::{ + webgl_channel, AlphaTreatment, DOMToTextureCommand, GLContextAttributes, GLLimits, GlType, + Parameter, SizedDataType, TexDataType, TexFormat, TexParameter, WebGLChan, WebGLCommand, + WebGLCommandBacktrace, WebGLContextId, WebGLError, WebGLFramebufferBindingRequest, WebGLMsg, + WebGLMsgSender, WebGLProgramId, WebGLResult, WebGLSLVersion, WebGLSendResult, WebGLSender, + WebGLVersion, YAxisTreatment, +}; use dom_struct::dom_struct; -use euclid::size::Size2D; -use ipc_channel::ipc::{self, IpcSender}; -use js::conversions::ConversionBehavior; -use js::jsapi::{JSContext, JSObject, Type, Rooted}; -use js::jsval::{BooleanValue, DoubleValue, Int32Value, JSVal, NullValue, UndefinedValue}; -use js::typedarray::{TypedArray, TypedArrayElement, Float32, Int32}; -use net_traits::image::base::PixelFormat; +use embedder_traits::EventLoopWaker; +use euclid::default::{Point2D, Rect, Size2D}; +use ipc_channel::ipc::{self, IpcSharedMemory}; +use js::jsapi::{JSContext, JSObject, Type}; +use js::jsval::{BooleanValue, DoubleValue, Int32Value, JSVal, UInt32Value}; +use js::jsval::{NullValue, ObjectValue, UndefinedValue}; +use js::rust::CustomAutoRooterGuard; +use js::typedarray::{ + ArrayBufferView, CreateWith, Float32, Float32Array, Int32, Int32Array, Uint32Array, +}; +use js::typedarray::{TypedArray, TypedArrayElementCreator}; use net_traits::image_cache::ImageResponse; -use offscreen_gl_context::{GLContextAttributes, GLLimits}; -use script_traits::ScriptMsg as ConstellationMsg; +use pixels::{self, PixelFormat}; +use script_layout_interface::HTMLCanvasDataSource; +use serde::{Deserialize, Serialize}; +use servo_config::pref; use std::cell::Cell; -use webrender_traits; -use webrender_traits::{WebGLCommand, WebGLError, WebGLFramebufferBindingRequest, WebGLParameter}; -use webrender_traits::WebGLError::*; - -type ImagePixelResult = Result<(Vec<u8>, Size2D<i32>), ()>; -pub const MAX_UNIFORM_AND_ATTRIBUTE_LEN: usize = 256; - -macro_rules! handle_potential_webgl_error { - ($context:ident, $call:expr, $return_on_error:expr) => { - match $call { - Ok(ret) => ret, - Err(error) => { - $context.webgl_error(error); - $return_on_error - } - } - }; - ($context:ident, $call:expr) => { - handle_potential_webgl_error!($context, $call, ()); - }; -} +use std::cmp; +use std::ptr::{self, NonNull}; +use std::rc::Rc; // From the GLES 2.0.25 spec, page 85: // @@ -82,25 +98,10 @@ macro_rules! handle_object_deletion { if let Some(bound_object) = $binding.get() { if bound_object.id() == $object.id() { $binding.set(None); + if let Some(command) = $unbind_command { + $self_.send_command(command); + } } - - if let Some(command) = $unbind_command { - $self_.ipc_renderer - .send(CanvasMsg::WebGL(command)) - .unwrap(); - } - } - }; -} - -macro_rules! object_binding_to_js_or_null { - ($cx: expr, $binding:expr) => { - { - rooted!(in($cx) let mut rval = NullValue()); - if let Some(bound_object) = $binding.get() { - bound_object.to_jsval($cx, rval.handle_mut()); - } - rval.get() } }; } @@ -111,143 +112,305 @@ fn has_invalid_blend_constants(arg1: u32, arg2: u32) -> bool { (constants::ONE_MINUS_CONSTANT_COLOR, constants::ONE_MINUS_CONSTANT_ALPHA) => true, (constants::ONE_MINUS_CONSTANT_COLOR, constants::CONSTANT_ALPHA) => true, (constants::CONSTANT_COLOR, constants::ONE_MINUS_CONSTANT_ALPHA) => true, - (_, _) => false + (_, _) => false, } } -/// Set of bitflags for texture unpacking (texImage2d, etc...) +pub fn uniform_get<T, F>(triple: (&WebGLRenderingContext, WebGLProgramId, i32), f: F) -> T +where + F: FnOnce(WebGLProgramId, i32, WebGLSender<T>) -> WebGLCommand, + T: for<'de> Deserialize<'de> + Serialize, +{ + let (sender, receiver) = webgl_channel().unwrap(); + triple.0.send_command(f(triple.1, triple.2, sender)); + receiver.recv().unwrap() +} + +#[allow(unsafe_code)] +pub unsafe fn uniform_typed<T>(cx: *mut JSContext, value: &[T::Element]) -> JSVal +where + T: TypedArrayElementCreator, +{ + rooted!(in(cx) let mut rval = ptr::null_mut::<JSObject>()); + <TypedArray<T, *mut JSObject>>::create(cx, CreateWith::Slice(&value), rval.handle_mut()) + .unwrap(); + ObjectValue(rval.get()) +} + bitflags! { - #[derive(HeapSizeOf, JSTraceable)] - flags TextureUnpacking: u8 { - const FLIP_Y_AXIS = 0x01, - const PREMULTIPLY_ALPHA = 0x02, - const CONVERT_COLORSPACE = 0x04, + /// Set of bitflags for texture unpacking (texImage2d, etc...) + #[derive(JSTraceable, MallocSizeOf)] + struct TextureUnpacking: u8 { + const FLIP_Y_AXIS = 0x01; + const PREMULTIPLY_ALPHA = 0x02; + const CONVERT_COLORSPACE = 0x04; } } +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)] +pub enum VertexAttrib { + Float(f32, f32, f32, f32), + Int(i32, i32, i32, i32), + Uint(u32, u32, u32, u32), +} + +#[derive(Clone, Copy, Debug)] +pub enum Operation { + Fallible, + Infallible, +} + #[dom_struct] pub struct WebGLRenderingContext { reflector_: Reflector, - #[ignore_heap_size_of = "Defined in ipc-channel"] - ipc_renderer: IpcSender<CanvasMsg>, - #[ignore_heap_size_of = "Defined in offscreen_gl_context"] + #[ignore_malloc_size_of = "Channels are hard"] + webgl_sender: WebGLMessageSender, + #[ignore_malloc_size_of = "Defined in webrender"] + webrender_image: webrender_api::ImageKey, + webgl_version: WebGLVersion, + glsl_version: WebGLSLVersion, + #[ignore_malloc_size_of = "Defined in surfman"] limits: GLLimits, - canvas: JS<HTMLCanvasElement>, - #[ignore_heap_size_of = "Defined in webrender_traits"] + canvas: Dom<HTMLCanvasElement>, + #[ignore_malloc_size_of = "Defined in canvas_traits"] last_error: Cell<Option<WebGLError>>, + texture_packing_alignment: Cell<u8>, texture_unpacking_settings: Cell<TextureUnpacking>, + // TODO(nox): Should be Cell<u8>. texture_unpacking_alignment: Cell<u32>, - bound_framebuffer: MutNullableJS<WebGLFramebuffer>, - bound_renderbuffer: MutNullableJS<WebGLRenderbuffer>, - bound_texture_2d: MutNullableJS<WebGLTexture>, - bound_texture_cube_map: MutNullableJS<WebGLTexture>, - bound_buffer_array: MutNullableJS<WebGLBuffer>, - bound_buffer_element_array: MutNullableJS<WebGLBuffer>, - current_program: MutNullableJS<WebGLProgram>, - #[ignore_heap_size_of = "Because it's small"] - current_vertex_attrib_0: Cell<(f32, f32, f32, f32)>, - #[ignore_heap_size_of = "Because it's small"] - current_scissor: Cell<(i32, i32, i32, i32)>, - #[ignore_heap_size_of = "Because it's small"] + bound_draw_framebuffer: MutNullableDom<WebGLFramebuffer>, + // TODO(mmatyas): This was introduced in WebGL2, but listed here because it's used by + // Textures and Renderbuffers, but such WebGLObjects have access only to the GL1 context. + bound_read_framebuffer: MutNullableDom<WebGLFramebuffer>, + bound_renderbuffer: MutNullableDom<WebGLRenderbuffer>, + bound_buffer_array: MutNullableDom<WebGLBuffer>, + current_program: MutNullableDom<WebGLProgram>, + current_vertex_attribs: DomRefCell<Box<[VertexAttrib]>>, + #[ignore_malloc_size_of = "Because it's small"] + current_scissor: Cell<(i32, i32, u32, u32)>, + #[ignore_malloc_size_of = "Because it's small"] current_clear_color: Cell<(f32, f32, f32, f32)>, + size: Cell<Size2D<u32>>, + extension_manager: WebGLExtensions, + capabilities: Capabilities, + default_vao: DomOnceCell<WebGLVertexArrayObjectOES>, + current_vao: MutNullableDom<WebGLVertexArrayObjectOES>, + default_vao_webgl2: DomOnceCell<WebGLVertexArrayObject>, + current_vao_webgl2: MutNullableDom<WebGLVertexArrayObject>, + textures: Textures, + api_type: GlType, } impl WebGLRenderingContext { - fn new_inherited(window: &Window, - canvas: &HTMLCanvasElement, - size: Size2D<i32>, - attrs: GLContextAttributes) - -> Result<WebGLRenderingContext, String> { - let (sender, receiver) = ipc::channel().unwrap(); - let constellation_chan = window.upcast::<GlobalScope>().constellation_chan(); - constellation_chan.send(ConstellationMsg::CreateWebGLPaintThread(size, attrs, sender)) - .unwrap(); + pub fn new_inherited( + window: &Window, + canvas: &HTMLCanvasElement, + webgl_version: WebGLVersion, + size: Size2D<u32>, + attrs: GLContextAttributes, + ) -> Result<WebGLRenderingContext, String> { + if pref!(webgl.testing.context_creation_error) { + return Err("WebGL context creation error forced by pref `webgl.testing.context_creation_error`".into()); + } + + let webgl_chan = match window.webgl_chan() { + Some(chan) => chan, + None => return Err("WebGL initialization failed early on".into()), + }; + + let (sender, receiver) = webgl_channel().unwrap(); + webgl_chan + .send(WebGLMsg::CreateContext(webgl_version, size, attrs, sender)) + .unwrap(); let result = receiver.recv().unwrap(); - result.map(|(ipc_renderer, context_limits)| { - WebGLRenderingContext { + result.map(|ctx_data| { + let max_combined_texture_image_units = ctx_data.limits.max_combined_texture_image_units; + let max_vertex_attribs = ctx_data.limits.max_vertex_attribs as usize; + Self { reflector_: Reflector::new(), - ipc_renderer: ipc_renderer, - limits: context_limits, - canvas: JS::from_ref(canvas), + webgl_sender: WebGLMessageSender::new( + ctx_data.sender, + window.get_event_loop_waker(), + ), + webrender_image: ctx_data.image_key, + webgl_version, + glsl_version: ctx_data.glsl_version, + limits: ctx_data.limits, + canvas: Dom::from_ref(canvas), last_error: Cell::new(None), - texture_unpacking_settings: Cell::new(CONVERT_COLORSPACE), + texture_packing_alignment: Cell::new(4), + texture_unpacking_settings: Cell::new(TextureUnpacking::CONVERT_COLORSPACE), texture_unpacking_alignment: Cell::new(4), - bound_framebuffer: MutNullableJS::new(None), - bound_texture_2d: MutNullableJS::new(None), - bound_texture_cube_map: MutNullableJS::new(None), - bound_buffer_array: MutNullableJS::new(None), - bound_buffer_element_array: MutNullableJS::new(None), - bound_renderbuffer: MutNullableJS::new(None), - current_program: MutNullableJS::new(None), - current_vertex_attrib_0: Cell::new((0f32, 0f32, 0f32, 1f32)), + bound_draw_framebuffer: MutNullableDom::new(None), + bound_read_framebuffer: MutNullableDom::new(None), + bound_buffer_array: MutNullableDom::new(None), + bound_renderbuffer: MutNullableDom::new(None), + current_program: MutNullableDom::new(None), + current_vertex_attribs: DomRefCell::new( + vec![VertexAttrib::Float(0f32, 0f32, 0f32, 1f32); max_vertex_attribs].into(), + ), current_scissor: Cell::new((0, 0, size.width, size.height)), - current_clear_color: Cell::new((0.0, 0.0, 0.0, 0.0)) + // FIXME(#21718) The backend is allowed to choose a size smaller than + // what was requested + size: Cell::new(size), + current_clear_color: Cell::new((0.0, 0.0, 0.0, 0.0)), + extension_manager: WebGLExtensions::new( + webgl_version, + ctx_data.api_type, + ctx_data.glsl_version, + ), + capabilities: Default::default(), + default_vao: Default::default(), + current_vao: Default::default(), + default_vao_webgl2: Default::default(), + current_vao_webgl2: Default::default(), + textures: Textures::new(max_combined_texture_image_units), + api_type: ctx_data.api_type, } }) } #[allow(unrooted_must_root)] - pub fn new(window: &Window, canvas: &HTMLCanvasElement, size: Size2D<i32>, attrs: GLContextAttributes) - -> Option<Root<WebGLRenderingContext>> { - match WebGLRenderingContext::new_inherited(window, canvas, size, attrs) { - Ok(ctx) => Some(reflect_dom_object(box ctx, window, WebGLRenderingContextBinding::Wrap)), + pub fn new( + window: &Window, + canvas: &HTMLCanvasElement, + webgl_version: WebGLVersion, + size: Size2D<u32>, + attrs: GLContextAttributes, + ) -> Option<DomRoot<WebGLRenderingContext>> { + match WebGLRenderingContext::new_inherited(window, canvas, webgl_version, size, attrs) { + Ok(ctx) => Some(reflect_dom_object(Box::new(ctx), window)), Err(msg) => { error!("Couldn't create WebGLRenderingContext: {}", msg); - let event = WebGLContextEvent::new(window, - atom!("webglcontextcreationerror"), - EventBubbles::DoesNotBubble, - EventCancelable::Cancelable, - DOMString::from(msg)); + let event = WebGLContextEvent::new( + window, + atom!("webglcontextcreationerror"), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable, + DOMString::from(msg), + ); event.upcast::<Event>().fire(canvas.upcast()); None - } + }, } } + pub fn webgl_version(&self) -> WebGLVersion { + self.webgl_version + } + pub fn limits(&self) -> &GLLimits { &self.limits } - pub fn bound_texture_for_target(&self, target: &TexImageTarget) -> Option<Root<WebGLTexture>> { - match *target { - TexImageTarget::Texture2D => self.bound_texture_2d.get(), - TexImageTarget::CubeMapPositiveX | - TexImageTarget::CubeMapNegativeX | - TexImageTarget::CubeMapPositiveY | - TexImageTarget::CubeMapNegativeY | - TexImageTarget::CubeMapPositiveZ | - TexImageTarget::CubeMapNegativeZ => self.bound_texture_cube_map.get(), - } + pub fn texture_unpacking_alignment(&self) -> u32 { + self.texture_unpacking_alignment.get() + } + + pub fn current_vao(&self) -> DomRoot<WebGLVertexArrayObjectOES> { + self.current_vao.or_init(|| { + DomRoot::from_ref( + self.default_vao + .init_once(|| WebGLVertexArrayObjectOES::new(self, None)), + ) + }) + } + + pub fn current_vao_webgl2(&self) -> DomRoot<WebGLVertexArrayObject> { + self.current_vao_webgl2.or_init(|| { + DomRoot::from_ref( + self.default_vao_webgl2 + .init_once(|| WebGLVertexArrayObject::new(self, None)), + ) + }) + } + + pub fn current_vertex_attribs(&self) -> RefMut<Box<[VertexAttrib]>> { + self.current_vertex_attribs.borrow_mut() } - pub fn recreate(&self, size: Size2D<i32>) { - self.ipc_renderer.send(CanvasMsg::Common(CanvasCommonMsg::Recreate(size))).unwrap(); + pub fn recreate(&self, size: Size2D<u32>) { + let (sender, receiver) = webgl_channel().unwrap(); + self.webgl_sender.send_resize(size, sender).unwrap(); + // FIXME(#21718) The backend is allowed to choose a size smaller than + // what was requested + self.size.set(size); + + if let Err(msg) = receiver.recv().unwrap() { + error!("Error resizing WebGLContext: {}", msg); + return; + }; // ClearColor needs to be restored because after a resize the GLContext is recreated // and the framebuffer is cleared using the default black transparent color. let color = self.current_clear_color.get(); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::ClearColor(color.0, color.1, color.2, color.3))) - .unwrap(); + self.send_command(WebGLCommand::ClearColor(color.0, color.1, color.2, color.3)); // WebGL Spec: Scissor rect must not change if the canvas is resized. // See: webgl/conformance-1.0.3/conformance/rendering/gl-scissor-canvas-dimensions.html // NativeContext handling library changes the scissor after a resize, so we need to reset the // default scissor when the canvas was created or the last scissor that the user set. let rect = self.current_scissor.get(); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Scissor(rect.0, rect.1, rect.2, rect.3))) + self.send_command(WebGLCommand::Scissor(rect.0, rect.1, rect.2, rect.3)); + + // Bound texture must not change when the canvas is resized. + // Right now surfman generates a new FBO and the bound texture is changed + // in order to create a new render to texture attachment. + // Send a command to re-bind the TEXTURE_2D, if any. + if let Some(texture) = self + .textures + .active_texture_slot(constants::TEXTURE_2D, self.webgl_version()) .unwrap() + .get() + { + self.send_command(WebGLCommand::BindTexture( + constants::TEXTURE_2D, + Some(texture.id()), + )); + } + + // Bound framebuffer must not change when the canvas is resized. + // Right now surfman generates a new FBO on resize. + // Send a command to re-bind the framebuffer, if any. + if let Some(fbo) = self.bound_draw_framebuffer.get() { + let id = WebGLFramebufferBindingRequest::Explicit(fbo.id()); + self.send_command(WebGLCommand::BindFramebuffer(constants::FRAMEBUFFER, id)); + } } - pub fn ipc_renderer(&self) -> IpcSender<CanvasMsg> { - self.ipc_renderer.clone() + pub(crate) fn webgl_sender(&self) -> WebGLMessageSender { + self.webgl_sender.clone() + } + + pub fn context_id(&self) -> WebGLContextId { + self.webgl_sender.context_id() + } + + pub fn onscreen(&self) -> bool { + self.canvas.upcast::<Node>().is_connected() + } + + #[inline] + pub fn send_command(&self, command: WebGLCommand) { + self.webgl_sender + .send(command, capture_webgl_backtrace(self)) + .unwrap(); + } + + pub fn send_command_ignored(&self, command: WebGLCommand) { + let _ = self + .webgl_sender + .send(command, capture_webgl_backtrace(self)); } pub fn webgl_error(&self, err: WebGLError) { // TODO(emilio): Add useful debug messages to this - warn!("WebGL error: {:?}, previous error was {:?}", err, self.last_error.get()); + warn!( + "WebGL error: {:?}, previous error was {:?}", + err, + self.last_error.get() + ); // If an error has been detected no further errors must be // recorded until `getError` has been called @@ -274,235 +437,274 @@ impl WebGLRenderingContext { // // The WebGL spec mentions a couple more operations that trigger // this: clear() and getParameter(IMPLEMENTATION_COLOR_READ_*). - fn validate_framebuffer_complete(&self) -> bool { - match self.bound_framebuffer.get() { - Some(fb) => match fb.check_status() { - constants::FRAMEBUFFER_COMPLETE => return true, - _ => { - self.webgl_error(InvalidFramebufferOperation); - return false; - } + pub fn validate_framebuffer(&self) -> WebGLResult<()> { + match self.bound_draw_framebuffer.get() { + Some(fb) => match fb.check_status_for_rendering() { + CompleteForRendering::Complete => Ok(()), + CompleteForRendering::Incomplete => Err(InvalidFramebufferOperation), + CompleteForRendering::MissingColorAttachment => Err(InvalidOperation), }, - // The default framebuffer is always complete. - None => return true, + None => Ok(()), } } - fn tex_parameter(&self, target: u32, name: u32, value: TexParameterValue) { - let texture = match target { - constants::TEXTURE_2D => self.bound_texture_2d.get(), - constants::TEXTURE_CUBE_MAP => self.bound_texture_cube_map.get(), - _ => return self.webgl_error(InvalidEnum), + pub fn validate_ownership<T>(&self, object: &T) -> WebGLResult<()> + where + T: DerivedFrom<WebGLObject>, + { + if self != object.upcast().context() { + return Err(InvalidOperation); + } + Ok(()) + } + + pub fn with_location<F>(&self, location: Option<&WebGLUniformLocation>, f: F) + where + F: FnOnce(&WebGLUniformLocation) -> WebGLResult<()>, + { + let location = match location { + Some(loc) => loc, + None => return, }; - if let Some(texture) = texture { - handle_potential_webgl_error!(self, texture.tex_parameter(target, name, value)); - } else { - self.webgl_error(InvalidOperation) + match self.current_program.get() { + Some(ref program) + if program.id() == location.program_id() && + program.link_generation() == location.link_generation() => {}, + _ => return self.webgl_error(InvalidOperation), } + handle_potential_webgl_error!(self, f(location)); + } + + pub fn textures(&self) -> &Textures { + &self.textures } - fn mark_as_dirty(&self) { - self.canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + fn tex_parameter(&self, target: u32, param: u32, value: TexParameterValue) { + let texture_slot = handle_potential_webgl_error!( + self, + self.textures + .active_texture_slot(target, self.webgl_version()), + return + ); + let texture = + handle_potential_webgl_error!(self, texture_slot.get().ok_or(InvalidOperation), return); + + if !self + .extension_manager + .is_get_tex_parameter_name_enabled(param) + { + return self.webgl_error(InvalidEnum); + } + + handle_potential_webgl_error!(self, texture.tex_parameter(param, value), return); + + // Validate non filterable TEXTURE_2D data_types + if target != constants::TEXTURE_2D { + return; + } + + let target = TexImageTarget::Texture2D; + if let Some(info) = texture.image_info_for_target(&target, 0) { + self.validate_filterable_texture( + &texture, + target, + 0, + info.internal_format(), + Size2D::new(info.width(), info.height()), + info.data_type().unwrap_or(TexDataType::UnsignedByte), + ); + } + } + + pub fn mark_as_dirty(&self) { + // If we have a bound framebuffer, then don't mark the canvas as dirty. + if self.bound_draw_framebuffer.get().is_some() { + return; + } + + // Dirtying the canvas is unnecessary if we're actively displaying immersive + // XR content right now. + if self.global().as_window().in_immersive_xr_session() { + return; + } + + self.canvas + .upcast::<Node>() + .dirty(NodeDamage::OtherNodeDamage); + + let document = document_from_node(&*self.canvas); + document.add_dirty_webgl_canvas(self); } fn vertex_attrib(&self, indx: u32, x: f32, y: f32, z: f32, w: f32) { - if indx > self.limits.max_vertex_attribs { + if indx >= self.limits.max_vertex_attribs { return self.webgl_error(InvalidValue); } - if indx == 0 { - self.current_vertex_attrib_0.set((x, y, z, w)) - } + match self.webgl_version() { + WebGLVersion::WebGL1 => self + .current_vao() + .set_vertex_attrib_type(indx, constants::FLOAT), + WebGLVersion::WebGL2 => self + .current_vao_webgl2() + .set_vertex_attrib_type(indx, constants::FLOAT), + }; + self.current_vertex_attribs.borrow_mut()[indx as usize] = VertexAttrib::Float(x, y, z, w); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::VertexAttrib(indx, x, y, z, w))) - .unwrap(); + self.send_command(WebGLCommand::VertexAttrib(indx, x, y, z, w)); } - fn get_current_framebuffer_size(&self) -> Option<(i32, i32)> { - match self.bound_framebuffer.get() { + pub fn get_current_framebuffer_size(&self) -> Option<(i32, i32)> { + match self.bound_draw_framebuffer.get() { Some(fb) => return fb.size(), // The window system framebuffer is bound - None => return Some((self.DrawingBufferWidth(), - self.DrawingBufferHeight())), + None => return Some((self.DrawingBufferWidth(), self.DrawingBufferHeight())), } } - fn validate_stencil_actions(&self, action: u32) -> bool { - match action { - 0 | constants::KEEP | constants::REPLACE | constants::INCR | constants::DECR | - constants::INVERT | constants::INCR_WRAP | constants::DECR_WRAP => true, - _ => false, - } + pub fn get_texture_packing_alignment(&self) -> u8 { + self.texture_packing_alignment.get() } - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glUniform.xml - // https://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.25.pdf#nameddest=section-2.10.4 - fn validate_uniform_parameters<T>(&self, - uniform: Option<&WebGLUniformLocation>, - uniform_type: UniformSetterType, - data: &[T]) -> bool { - let uniform = match uniform { - Some(uniform) => uniform, - None => return false, - }; - - let program = self.current_program.get(); - match program { - Some(ref program) if program.id() == uniform.program_id() => {}, - _ => { - self.webgl_error(InvalidOperation); - return false; - }, - }; + // LINEAR filtering may be forbidden when using WebGL extensions. + // https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/ + fn validate_filterable_texture( + &self, + texture: &WebGLTexture, + target: TexImageTarget, + level: u32, + internal_format: TexFormat, + size: Size2D<u32>, + data_type: TexDataType, + ) -> bool { + if self + .extension_manager + .is_filterable(data_type.as_gl_constant()) || + !texture.is_using_linear_filtering() + { + return true; + } - // TODO(emilio): Get more complex uniform info from ANGLE, and use it to - // properly validate that the uniform setter type is compatible with the - // uniform type, and that the uniform size matches. - if data.len() % uniform_type.element_count() != 0 { - self.webgl_error(InvalidOperation); - return false; - } - - true - } - - /// Translates an image in rgba8 (red in the first byte) format to - /// the format that was requested of TexImage. - /// - /// From the WebGL 1.0 spec, 5.14.8: - /// - /// "The source image data is conceptually first converted to - /// the data type and format specified by the format and type - /// arguments, and then transferred to the WebGL - /// implementation. If a packed pixel format is specified - /// which would imply loss of bits of precision from the image - /// data, this loss of precision must occur." - fn rgba8_image_to_tex_image_data(&self, - format: TexFormat, - data_type: TexDataType, - pixels: Vec<u8>) -> Vec<u8> { - // hint for vector allocation sizing. - let pixel_count = pixels.len() / 4; - - match (format, data_type) { - (TexFormat::RGBA, TexDataType::UnsignedByte) => pixels, - (TexFormat::RGB, TexDataType::UnsignedByte) => pixels, - - (TexFormat::RGBA, TexDataType::UnsignedShort4444) => { - let mut rgba4 = Vec::<u8>::with_capacity(pixel_count * 2); - for rgba8 in pixels.chunks(4) { - rgba4.write_u16::<NativeEndian>((rgba8[0] as u16 & 0xf0) << 8 | - (rgba8[1] as u16 & 0xf0) << 4 | - (rgba8[2] as u16 & 0xf0) | - (rgba8[3] as u16 & 0xf0) >> 4).unwrap(); - } - rgba4 - } + // Handle validation failed: LINEAR filtering not valid for this texture + // WebGL Conformance tests expect to fallback to [0, 0, 0, 255] RGBA UNSIGNED_BYTE + let data_type = TexDataType::UnsignedByte; + let expected_byte_length = size.area() * 4; + let mut pixels = vec![0u8; expected_byte_length as usize]; + for rgba8 in pixels.chunks_mut(4) { + rgba8[3] = 255u8; + } - (TexFormat::RGBA, TexDataType::UnsignedShort5551) => { - let mut rgba5551 = Vec::<u8>::with_capacity(pixel_count * 2); - for rgba8 in pixels.chunks(4) { - rgba5551.write_u16::<NativeEndian>((rgba8[0] as u16 & 0xf8) << 8 | - (rgba8[1] as u16 & 0xf8) << 3 | - (rgba8[2] as u16 & 0xf8) >> 2 | - (rgba8[3] as u16) >> 7).unwrap(); - } - rgba5551 - } + // TODO(nox): AFAICT here we construct a RGBA8 array and then we + // convert it to whatever actual format we need, we should probably + // construct the desired format from the start. + self.tex_image_2d( + texture, + target, + data_type, + internal_format, + internal_format.to_unsized(), + level, + 0, + 1, + size, + TexSource::Pixels(TexPixels::new( + IpcSharedMemory::from_bytes(&pixels), + size, + PixelFormat::RGBA8, + true, + )), + ); - (TexFormat::RGB, TexDataType::UnsignedShort565) => { - let mut rgb565 = Vec::<u8>::with_capacity(pixel_count * 2); - for rgba8 in pixels.chunks(4) { - rgb565.write_u16::<NativeEndian>((rgba8[0] as u16 & 0xf8) << 8 | - (rgba8[1] as u16 & 0xfc) << 3 | - (rgba8[2] as u16 & 0xf8) >> 3).unwrap(); - } - rgb565 - } + false + } - // Validation should have ensured that we only hit the - // above cases, but we haven't turned the (format, type) - // into an enum yet so there's a default case here. - _ => unreachable!() + fn validate_stencil_actions(&self, action: u32) -> bool { + match action { + 0 | + constants::KEEP | + constants::REPLACE | + constants::INCR | + constants::DECR | + constants::INVERT | + constants::INCR_WRAP | + constants::DECR_WRAP => true, + _ => false, } } - fn get_image_pixels(&self, - source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) - -> ImagePixelResult { - let source = match source { - Some(s) => s, - None => return Err(()), - }; + pub fn get_image_pixels(&self, source: TexImageSource) -> Fallible<Option<TexPixels>> { + Ok(Some(match source { + TexImageSource::ImageData(image_data) => TexPixels::new( + image_data.to_shared_memory(), + image_data.get_size(), + PixelFormat::RGBA8, + false, + ), + TexImageSource::HTMLImageElement(image) => { + let document = document_from_node(&*self.canvas); + if !image.same_origin(document.origin()) { + return Err(Error::Security); + } - // NOTE: Getting the pixels probably can be short-circuited if some - // parameter is invalid. - // - // Nontheless, since it's the error case, I'm not totally sure the - // complexity is worth it. - let (pixels, size) = match source { - ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::ImageData(image_data) => { - (image_data.get_data_array(), image_data.get_size()) - }, - ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLImageElement(image) => { let img_url = match image.get_url() { Some(url) => url, - None => return Err(()), + None => return Ok(None), }; let window = window_from_node(&*self.canvas); + let cors_setting = cors_setting_for_element(image.upcast()); - let img = match canvas_utils::request_image_from_cache(&window, img_url) { - ImageResponse::Loaded(img) => img, - ImageResponse::PlaceholderLoaded(_) | ImageResponse::None | - ImageResponse::MetadataLoaded(_) - => return Err(()), - }; - - let size = Size2D::new(img.width as i32, img.height as i32); - - // For now Servo's images are all stored as RGBA8 internally. - let mut data = match img.format { - PixelFormat::RGBA8 => img.bytes.to_vec(), - _ => unimplemented!(), - }; + 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), + }; - byte_swap(&mut data); + let size = Size2D::new(img.width, img.height); - (data, size) + TexPixels::new(img.bytes.clone(), 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 // WebGLContext (probably via GetPixels()). - ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLCanvasElement(canvas) => { - if let Some((mut data, size)) = canvas.fetch_all_data() { - byte_swap(&mut data); - (data, size) + TexImageSource::HTMLCanvasElement(canvas) => { + if !canvas.origin_is_clean() { + return Err(Error::Security); + } + if let Some((data, size)) = canvas.fetch_all_data() { + let data = data.unwrap_or_else(|| { + IpcSharedMemory::from_bytes(&vec![0; size.area() as usize * 4]) + }); + TexPixels::new(data, size, PixelFormat::BGRA8, true) } else { - return Err(()); + return Ok(None); } }, - ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLVideoElement(_rooted_video) - => unimplemented!(), - }; - - return Ok((pixels, size)); + 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), + }, + })) } // TODO(emilio): Move this logic to a validator. - #[allow(unsafe_code)] - unsafe fn validate_tex_image_2d_data(&self, - width: u32, - height: u32, - format: TexFormat, - data_type: TexDataType, - unpacking_alignment: u32, - data: *mut JSObject, - cx: *mut JSContext) - -> Result<u32, ()> { + pub fn validate_tex_image_2d_data( + &self, + width: u32, + height: u32, + format: TexFormat, + data_type: TexDataType, + unpacking_alignment: u32, + data: Option<&ArrayBufferView>, + ) -> Result<u32, ()> { let element_size = data_type.element_size(); let components_per_element = data_type.components_per_element(); let components = format.components(); @@ -512,23 +714,15 @@ impl WebGLRenderingContext { // If it is UNSIGNED_BYTE, a Uint8Array must be supplied; // if it is UNSIGNED_SHORT_5_6_5, UNSIGNED_SHORT_4_4_4_4, // or UNSIGNED_SHORT_5_5_5_1, a Uint16Array must be supplied. + // or FLOAT, a Float32Array must be supplied. // If the types do not match, an INVALID_OPERATION error is generated. - typedarray!(in(cx) let typedarray_u8: Uint8Array = data); - typedarray!(in(cx) let typedarray_u16: Uint16Array = data); - let received_size = if data.is_null() { - element_size - } else { - if typedarray_u16.is_ok() { - 2 - } else if typedarray_u8.is_ok() { - 1 - } else { - self.webgl_error(InvalidOperation); - return Err(()); - } - }; + let data_type_matches = data.as_ref().map_or(true, |buffer| { + Some(data_type.sized_data_type()) == + array_buffer_type_to_sized_type(buffer.get_array_type()) && + data_type.required_webgl_version() <= self.webgl_version() + }); - if received_size != element_size { + if !data_type_matches { self.webgl_error(InvalidOperation); return Err(()); } @@ -547,388 +741,1535 @@ impl WebGLRenderingContext { } } - /// Flips the pixels in the Vec on the Y axis if - /// UNPACK_FLIP_Y_WEBGL is currently enabled. - fn flip_teximage_y(&self, - pixels: Vec<u8>, - internal_format: TexFormat, - data_type: TexDataType, - width: usize, - height: usize, - unpacking_alignment: usize) -> Vec<u8> { - if !self.texture_unpacking_settings.get().contains(FLIP_Y_AXIS) { - return pixels; + pub fn tex_image_2d( + &self, + texture: &WebGLTexture, + target: TexImageTarget, + data_type: TexDataType, + internal_format: TexFormat, + format: TexFormat, + level: u32, + _border: u32, + unpacking_alignment: u32, + size: Size2D<u32>, + source: TexSource, + ) { + // TexImage2D depth is always equal to 1. + handle_potential_webgl_error!( + self, + texture.initialize( + target, + size.width, + size.height, + 1, + format, + level, + Some(data_type) + ) + ); + + let settings = self.texture_unpacking_settings.get(); + let dest_premultiplied = settings.contains(TextureUnpacking::PREMULTIPLY_ALPHA); + + let y_axis_treatment = if settings.contains(TextureUnpacking::FLIP_Y_AXIS) { + YAxisTreatment::Flipped + } else { + YAxisTreatment::AsIs + }; + + let internal_format = self + .extension_manager + .get_effective_tex_internal_format(internal_format, data_type.as_gl_constant()); + + let effective_data_type = self + .extension_manager + .effective_type(data_type.as_gl_constant()); + + match source { + TexSource::Pixels(pixels) => { + let alpha_treatment = match (pixels.premultiplied, dest_premultiplied) { + (true, false) => Some(AlphaTreatment::Unmultiply), + (false, true) => Some(AlphaTreatment::Premultiply), + _ => None, + }; + + // TODO(emilio): convert colorspace if requested. + self.send_command(WebGLCommand::TexImage2D { + target: target.as_gl_constant(), + level, + internal_format, + size, + format, + data_type, + effective_data_type, + unpacking_alignment, + alpha_treatment, + y_axis_treatment, + pixel_format: pixels.pixel_format, + data: pixels.data.into(), + }); + }, + TexSource::BufferOffset(offset) => { + self.send_command(WebGLCommand::TexImage2DPBO { + target: target.as_gl_constant(), + level, + internal_format, + size, + format, + effective_data_type, + unpacking_alignment, + offset, + }); + }, + } + + if let Some(fb) = self.bound_draw_framebuffer.get() { + fb.invalidate_texture(&*texture); + } + } + + fn tex_sub_image_2d( + &self, + texture: DomRoot<WebGLTexture>, + target: TexImageTarget, + level: u32, + xoffset: i32, + yoffset: i32, + format: TexFormat, + data_type: TexDataType, + unpacking_alignment: u32, + pixels: TexPixels, + ) { + // We have already validated level + let image_info = match texture.image_info_for_target(&target, level) { + Some(info) => info, + None => return self.webgl_error(InvalidOperation), + }; + + // GL_INVALID_VALUE is generated if: + // - xoffset or yoffset is less than 0 + // - x offset plus the width is greater than the texture width + // - y offset plus the height is greater than the texture height + if xoffset < 0 || + (xoffset as u32 + pixels.size().width) > image_info.width() || + yoffset < 0 || + (yoffset as u32 + pixels.size().height) > image_info.height() + { + return self.webgl_error(InvalidValue); + } + + // The unsized format must be compatible with the sized internal format + debug_assert!(!format.is_sized()); + if format != image_info.internal_format().to_unsized() { + return self.webgl_error(InvalidOperation); + } + + // See https://www.khronos.org/registry/webgl/specs/latest/2.0/#4.1.6 + if self.webgl_version() == WebGLVersion::WebGL1 { + if data_type != image_info.data_type().unwrap() { + return self.webgl_error(InvalidOperation); + } + } + + let settings = self.texture_unpacking_settings.get(); + let dest_premultiplied = settings.contains(TextureUnpacking::PREMULTIPLY_ALPHA); + + let alpha_treatment = match (pixels.premultiplied, dest_premultiplied) { + (true, false) => Some(AlphaTreatment::Unmultiply), + (false, true) => Some(AlphaTreatment::Premultiply), + _ => None, + }; + + let y_axis_treatment = if settings.contains(TextureUnpacking::FLIP_Y_AXIS) { + YAxisTreatment::Flipped + } else { + YAxisTreatment::AsIs + }; + + let effective_data_type = self + .extension_manager + .effective_type(data_type.as_gl_constant()); + + // TODO(emilio): convert colorspace if requested. + self.send_command(WebGLCommand::TexSubImage2D { + target: target.as_gl_constant(), + level, + xoffset, + yoffset, + size: pixels.size(), + format, + data_type, + effective_data_type, + unpacking_alignment, + alpha_treatment, + y_axis_treatment, + pixel_format: pixels.pixel_format, + data: pixels.data.into(), + }); + } + + fn get_gl_extensions(&self) -> String { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetExtensions(sender)); + receiver.recv().unwrap() + } + + pub(crate) fn layout_handle(&self) -> HTMLCanvasDataSource { + let image_key = self.webrender_image; + HTMLCanvasDataSource::WebGL(image_key) + } + + // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ + pub fn draw_arrays_instanced( + &self, + mode: u32, + first: i32, + count: i32, + primcount: i32, + ) -> WebGLResult<()> { + match mode { + constants::POINTS | + constants::LINE_STRIP | + constants::LINE_LOOP | + constants::LINES | + constants::TRIANGLE_STRIP | + constants::TRIANGLE_FAN | + constants::TRIANGLES => {}, + _ => { + return Err(InvalidEnum); + }, + } + if first < 0 || count < 0 || primcount < 0 { + return Err(InvalidValue); } - let cpp = (data_type.element_size() * - internal_format.components() / data_type.components_per_element()) as usize; + let current_program = self.current_program.get().ok_or(InvalidOperation)?; - let stride = (width * cpp + unpacking_alignment - 1) & !(unpacking_alignment - 1); + let required_len = if count > 0 { + first + .checked_add(count) + .map(|len| len as u32) + .ok_or(InvalidOperation)? + } else { + 0 + }; - let mut flipped = Vec::<u8>::with_capacity(pixels.len()); + match self.webgl_version() { + WebGLVersion::WebGL1 => self.current_vao().validate_for_draw( + required_len, + primcount as u32, + ¤t_program.active_attribs(), + )?, + WebGLVersion::WebGL2 => self.current_vao_webgl2().validate_for_draw( + required_len, + primcount as u32, + ¤t_program.active_attribs(), + )?, + }; - for y in 0..height { - let flipped_y = height - 1 - y; - let start = flipped_y * stride; + self.validate_framebuffer()?; - flipped.extend_from_slice(&pixels[start..(start + width * cpp)]); - flipped.extend(vec![0u8; stride - width * cpp]); + if count == 0 || primcount == 0 { + return Ok(()); } - flipped + self.send_command(if primcount == 1 { + WebGLCommand::DrawArrays { mode, first, count } + } else { + WebGLCommand::DrawArraysInstanced { + mode, + first, + count, + primcount, + } + }); + self.mark_as_dirty(); + Ok(()) } - /// Performs premultiplication of the pixels if - /// UNPACK_PREMULTIPLY_ALPHA_WEBGL is currently enabled. - fn premultiply_pixels(&self, - format: TexFormat, - data_type: TexDataType, - pixels: Vec<u8>) -> Vec<u8> { - if !self.texture_unpacking_settings.get().contains(PREMULTIPLY_ALPHA) { - return pixels; + // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ + pub fn draw_elements_instanced( + &self, + mode: u32, + count: i32, + type_: u32, + offset: i64, + primcount: i32, + ) -> WebGLResult<()> { + match mode { + constants::POINTS | + constants::LINE_STRIP | + constants::LINE_LOOP | + constants::LINES | + constants::TRIANGLE_STRIP | + constants::TRIANGLE_FAN | + constants::TRIANGLES => {}, + _ => { + return Err(InvalidEnum); + }, + } + if count < 0 || offset < 0 || primcount < 0 { + return Err(InvalidValue); + } + let type_size = match type_ { + constants::UNSIGNED_BYTE => 1, + constants::UNSIGNED_SHORT => 2, + constants::UNSIGNED_INT => match self.webgl_version() { + WebGLVersion::WebGL1 if self.extension_manager.is_element_index_uint_enabled() => 4, + WebGLVersion::WebGL2 => 4, + _ => return Err(InvalidEnum), + }, + _ => return Err(InvalidEnum), + }; + if offset % type_size != 0 { + return Err(InvalidOperation); + } + + let current_program = self.current_program.get().ok_or(InvalidOperation)?; + let array_buffer = match self.webgl_version() { + WebGLVersion::WebGL1 => self.current_vao().element_array_buffer().get(), + WebGLVersion::WebGL2 => self.current_vao_webgl2().element_array_buffer().get(), } + .ok_or(InvalidOperation)?; - match (format, data_type) { - (TexFormat::RGBA, TexDataType::UnsignedByte) => { - let mut premul = Vec::<u8>::with_capacity(pixels.len()); - for rgba in pixels.chunks(4) { - premul.push(multiply_u8_pixel(rgba[0], rgba[3])); - premul.push(multiply_u8_pixel(rgba[1], rgba[3])); - premul.push(multiply_u8_pixel(rgba[2], rgba[3])); - premul.push(rgba[3]); - } - premul + if count > 0 && primcount > 0 { + // This operation cannot overflow in u64 and we know all those values are nonnegative. + let val = offset as u64 + (count as u64 * type_size as u64); + if val > array_buffer.capacity() as u64 { + return Err(InvalidOperation); } - (TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => { - let mut premul = Vec::<u8>::with_capacity(pixels.len()); - for la in pixels.chunks(2) { - premul.push(multiply_u8_pixel(la[0], la[1])); - premul.push(la[1]); - } - premul + } + + // TODO(nox): Pass the correct number of vertices required. + match self.webgl_version() { + WebGLVersion::WebGL1 => self.current_vao().validate_for_draw( + 0, + primcount as u32, + ¤t_program.active_attribs(), + )?, + WebGLVersion::WebGL2 => self.current_vao_webgl2().validate_for_draw( + 0, + primcount as u32, + ¤t_program.active_attribs(), + )?, + }; + + self.validate_framebuffer()?; + + if count == 0 || primcount == 0 { + return Ok(()); + } + + let offset = offset as u32; + self.send_command(if primcount == 1 { + WebGLCommand::DrawElements { + mode, + count, + type_, + offset, } + } else { + WebGLCommand::DrawElementsInstanced { + mode, + count, + type_, + offset, + primcount, + } + }); + self.mark_as_dirty(); + Ok(()) + } - (TexFormat::RGBA, TexDataType::UnsignedShort5551) => { - let mut premul = Vec::<u8>::with_capacity(pixels.len()); - for mut rgba in pixels.chunks(2) { - let pix = rgba.read_u16::<NativeEndian>().unwrap(); - if pix & (1 << 15) != 0 { - premul.write_u16::<NativeEndian>(pix).unwrap(); - } else { - premul.write_u16::<NativeEndian>(0).unwrap(); - } - } - premul + pub fn vertex_attrib_divisor(&self, index: u32, divisor: u32) { + if index >= self.limits.max_vertex_attribs { + return self.webgl_error(InvalidValue); + } + + match self.webgl_version() { + WebGLVersion::WebGL1 => self.current_vao().vertex_attrib_divisor(index, divisor), + WebGLVersion::WebGL2 => self + .current_vao_webgl2() + .vertex_attrib_divisor(index, divisor), + }; + self.send_command(WebGLCommand::VertexAttribDivisor { index, divisor }); + } + + // Used by HTMLCanvasElement.toDataURL + // + // This emits errors quite liberally, but the spec says that this operation + // can fail and that it is UB what happens in that case. + // + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#2.2 + pub fn get_image_data(&self, mut size: Size2D<u32>) -> Option<Vec<u8>> { + handle_potential_webgl_error!(self, self.validate_framebuffer(), return None); + + let (fb_width, fb_height) = handle_potential_webgl_error!( + self, + self.get_current_framebuffer_size().ok_or(InvalidOperation), + return None + ); + size.width = cmp::min(size.width, fb_width as u32); + size.height = cmp::min(size.height, fb_height as u32); + + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.send_command(WebGLCommand::ReadPixels( + Rect::from_size(size), + constants::RGBA, + constants::UNSIGNED_BYTE, + sender, + )); + Some(receiver.recv().unwrap()) + } + + pub fn array_buffer(&self) -> Option<DomRoot<WebGLBuffer>> { + self.bound_buffer_array.get() + } + + pub fn array_buffer_slot(&self) -> &MutNullableDom<WebGLBuffer> { + &self.bound_buffer_array + } + + pub fn bound_buffer(&self, target: u32) -> WebGLResult<Option<DomRoot<WebGLBuffer>>> { + match target { + constants::ARRAY_BUFFER => Ok(self.bound_buffer_array.get()), + constants::ELEMENT_ARRAY_BUFFER => Ok(self.current_vao().element_array_buffer().get()), + _ => Err(WebGLError::InvalidEnum), + } + } + + pub fn buffer_usage(&self, usage: u32) -> WebGLResult<u32> { + match usage { + constants::STREAM_DRAW | constants::STATIC_DRAW | constants::DYNAMIC_DRAW => Ok(usage), + _ => Err(WebGLError::InvalidEnum), + } + } + + pub fn create_vertex_array(&self) -> Option<DomRoot<WebGLVertexArrayObjectOES>> { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::CreateVertexArray(sender)); + receiver + .recv() + .unwrap() + .map(|id| WebGLVertexArrayObjectOES::new(self, Some(id))) + } + + pub fn create_vertex_array_webgl2(&self) -> Option<DomRoot<WebGLVertexArrayObject>> { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::CreateVertexArray(sender)); + receiver + .recv() + .unwrap() + .map(|id| WebGLVertexArrayObject::new(self, Some(id))) + } + + pub fn delete_vertex_array(&self, vao: Option<&WebGLVertexArrayObjectOES>) { + if let Some(vao) = vao { + handle_potential_webgl_error!(self, self.validate_ownership(vao), return); + // The default vertex array has no id and should never be passed around. + assert!(vao.id().is_some()); + if vao.is_deleted() { + return; + } + if vao == &*self.current_vao() { + // Setting it to None will make self.current_vao() reset it to the default one + // next time it is called. + self.current_vao.set(None); + self.send_command(WebGLCommand::BindVertexArray(None)); } + vao.delete(Operation::Infallible); + } + } - (TexFormat::RGBA, TexDataType::UnsignedShort4444) => { - let mut premul = Vec::<u8>::with_capacity(pixels.len()); - for mut rgba in pixels.chunks(2) { - let pix = rgba.read_u16::<NativeEndian>().unwrap(); - let extend_to_8_bits = |val| { (val | val << 4) as u8 }; - let r = extend_to_8_bits(pix & 0x000f); - let g = extend_to_8_bits((pix & 0x00f0) >> 4); - let b = extend_to_8_bits((pix & 0x0f00) >> 8); - let a = extend_to_8_bits((pix & 0xf000) >> 12); - - premul.write_u16::<NativeEndian>((multiply_u8_pixel(r, a) & 0xf0) as u16 >> 4 | - (multiply_u8_pixel(g, a) & 0xf0) as u16 | - ((multiply_u8_pixel(b, a) & 0xf0) as u16) << 4 | - pix & 0xf000).unwrap(); - } - premul + pub fn delete_vertex_array_webgl2(&self, vao: Option<&WebGLVertexArrayObject>) { + if let Some(vao) = vao { + handle_potential_webgl_error!(self, self.validate_ownership(vao), return); + // The default vertex array has no id and should never be passed around. + assert!(vao.id().is_some()); + if vao.is_deleted() { + return; } + if vao == &*self.current_vao_webgl2() { + // Setting it to None will make self.current_vao() reset it to the default one + // next time it is called. + self.current_vao_webgl2.set(None); + self.send_command(WebGLCommand::BindVertexArray(None)); + } + vao.delete(Operation::Infallible); + } + } - // Other formats don't have alpha, so return their data untouched. - _ => pixels - } - } - - fn tex_image_2d(&self, - texture: Root<WebGLTexture>, - target: TexImageTarget, - data_type: TexDataType, - internal_format: TexFormat, - level: u32, - width: u32, - height: u32, - _border: u32, - unpacking_alignment: u32, - pixels: Vec<u8>) { // NB: pixels should NOT be premultipied - // FINISHME: Consider doing premultiply and flip in a single mutable Vec. - let pixels = self.premultiply_pixels(internal_format, data_type, pixels); - - let pixels = self.flip_teximage_y(pixels, internal_format, data_type, - width as usize, height as usize, unpacking_alignment as usize); - - // TexImage2D depth is always equal to 1 - handle_potential_webgl_error!(self, texture.initialize(target, - width, - height, 1, - internal_format, - level, - Some(data_type))); - - // Set the unpack alignment. For textures coming from arrays, - // this will be the current value of the context's - // GL_UNPACK_ALIGNMENT, while for textures from images or - // canvas (produced by rgba8_image_to_tex_image_data()), it - // will be 1. - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::PixelStorei(constants::UNPACK_ALIGNMENT, unpacking_alignment as i32))) - .unwrap(); + pub fn is_vertex_array(&self, vao: Option<&WebGLVertexArrayObjectOES>) -> bool { + vao.map_or(false, |vao| { + // The default vertex array has no id and should never be passed around. + assert!(vao.id().is_some()); + self.validate_ownership(vao).is_ok() && vao.ever_bound() && !vao.is_deleted() + }) + } - // TODO(emilio): convert colorspace if requested - let msg = WebGLCommand::TexImage2D(target.as_gl_constant(), level as i32, - internal_format.as_gl_constant() as i32, - width as i32, height as i32, - internal_format.as_gl_constant(), - data_type.as_gl_constant(), pixels); + pub fn is_vertex_array_webgl2(&self, vao: Option<&WebGLVertexArrayObject>) -> bool { + vao.map_or(false, |vao| { + // The default vertex array has no id and should never be passed around. + assert!(vao.id().is_some()); + self.validate_ownership(vao).is_ok() && vao.ever_bound() && !vao.is_deleted() + }) + } - self.ipc_renderer - .send(CanvasMsg::WebGL(msg)) - .unwrap(); + pub fn bind_vertex_array(&self, vao: Option<&WebGLVertexArrayObjectOES>) { + if let Some(vao) = vao { + // The default vertex array has no id and should never be passed around. + assert!(vao.id().is_some()); + handle_potential_webgl_error!(self, self.validate_ownership(vao), return); + if vao.is_deleted() { + return self.webgl_error(InvalidOperation); + } + vao.set_ever_bound(); + } + self.send_command(WebGLCommand::BindVertexArray(vao.and_then(|vao| vao.id()))); + // Setting it to None will make self.current_vao() reset it to the default one + // next time it is called. + self.current_vao.set(vao); + } - if let Some(fb) = self.bound_framebuffer.get() { - fb.invalidate_texture(&*texture); + pub fn bind_vertex_array_webgl2(&self, vao: Option<&WebGLVertexArrayObject>) { + if let Some(vao) = vao { + // The default vertex array has no id and should never be passed around. + assert!(vao.id().is_some()); + handle_potential_webgl_error!(self, self.validate_ownership(vao), return); + if vao.is_deleted() { + return self.webgl_error(InvalidOperation); + } + vao.set_ever_bound(); } + self.send_command(WebGLCommand::BindVertexArray(vao.and_then(|vao| vao.id()))); + // Setting it to None will make self.current_vao() reset it to the default one + // next time it is called. + self.current_vao_webgl2.set(vao); } - fn tex_sub_image_2d(&self, - texture: Root<WebGLTexture>, - target: TexImageTarget, - level: u32, - xoffset: i32, - yoffset: i32, - width: u32, - height: u32, - format: TexFormat, - data_type: TexDataType, - unpacking_alignment: u32, - pixels: Vec<u8>) { // NB: pixels should NOT be premultipied - // We have already validated level - let image_info = texture.image_info_for_target(&target, level); + fn validate_blend_mode(&self, mode: u32) -> WebGLResult<()> { + match mode { + constants::FUNC_ADD | constants::FUNC_SUBTRACT | constants::FUNC_REVERSE_SUBTRACT => { + Ok(()) + }, + EXTBlendMinmaxConstants::MIN_EXT | EXTBlendMinmaxConstants::MAX_EXT + if self.extension_manager.is_blend_minmax_enabled() => + { + Ok(()) + }, + _ => Err(InvalidEnum), + } + } - // GL_INVALID_VALUE is generated if: - // - xoffset or yoffset is less than 0 - // - x offset plus the width is greater than the texture width - // - y offset plus the height is greater than the texture height - if xoffset < 0 || (xoffset as u32 + width) > image_info.width() || - yoffset < 0 || (yoffset as u32 + height) > image_info.height() { + pub fn initialize_framebuffer(&self, clear_bits: u32) { + if clear_bits == 0 { + return; + } + self.send_command(WebGLCommand::InitializeFramebuffer { + color: clear_bits & constants::COLOR_BUFFER_BIT != 0, + depth: clear_bits & constants::DEPTH_BUFFER_BIT != 0, + stencil: clear_bits & constants::STENCIL_BUFFER_BIT != 0, + }); + } + + pub fn extension_manager(&self) -> &WebGLExtensions { + &self.extension_manager + } + + #[allow(unsafe_code)] + pub fn buffer_data( + &self, + target: u32, + data: Option<ArrayBufferViewOrArrayBuffer>, + usage: u32, + bound_buffer: Option<DomRoot<WebGLBuffer>>, + ) { + let data = handle_potential_webgl_error!(self, data.ok_or(InvalidValue), return); + let bound_buffer = + handle_potential_webgl_error!(self, bound_buffer.ok_or(InvalidOperation), return); + + let data = unsafe { + // Safe because we don't do anything with JS until the end of the method. + match data { + ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref data) => data.as_slice(), + ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref data) => data.as_slice(), + } + }; + handle_potential_webgl_error!(self, bound_buffer.buffer_data(target, data, usage)); + } + + pub fn buffer_data_( + &self, + target: u32, + size: i64, + usage: u32, + bound_buffer: Option<DomRoot<WebGLBuffer>>, + ) { + let bound_buffer = + handle_potential_webgl_error!(self, bound_buffer.ok_or(InvalidOperation), return); + + if size < 0 { + return self.webgl_error(InvalidValue); + } + + // FIXME: Allocating a buffer based on user-requested size is + // not great, but we don't have a fallible allocation to try. + let data = vec![0u8; size as usize]; + handle_potential_webgl_error!(self, bound_buffer.buffer_data(target, &data, usage)); + } + + #[allow(unsafe_code)] + pub fn buffer_sub_data( + &self, + target: u32, + offset: i64, + data: ArrayBufferViewOrArrayBuffer, + bound_buffer: Option<DomRoot<WebGLBuffer>>, + ) { + let bound_buffer = + handle_potential_webgl_error!(self, bound_buffer.ok_or(InvalidOperation), return); + + if offset < 0 { return self.webgl_error(InvalidValue); } - // NB: format and internal_format must match. - if format != image_info.internal_format().unwrap() || - data_type != image_info.data_type().unwrap() { + let data = unsafe { + // Safe because we don't do anything with JS until the end of the method. + match data { + ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref data) => data.as_slice(), + ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref data) => data.as_slice(), + } + }; + if (offset as u64) + data.len() as u64 > bound_buffer.capacity() as u64 { + return self.webgl_error(InvalidValue); + } + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.send_command(WebGLCommand::BufferSubData( + target, + offset as isize, + receiver, + )); + sender.send(data).unwrap(); + } + + pub fn bind_buffer_maybe( + &self, + slot: &MutNullableDom<WebGLBuffer>, + target: u32, + buffer: Option<&WebGLBuffer>, + ) { + if let Some(buffer) = buffer { + handle_potential_webgl_error!(self, self.validate_ownership(buffer), return); + + if buffer.is_marked_for_deletion() { + return self.webgl_error(InvalidOperation); + } + handle_potential_webgl_error!(self, buffer.set_target_maybe(target), return); + buffer.increment_attached_counter(); + } + + self.send_command(WebGLCommand::BindBuffer(target, buffer.map(|b| b.id()))); + if let Some(old) = slot.get() { + old.decrement_attached_counter(Operation::Infallible); + } + + slot.set(buffer); + } + + pub fn current_program(&self) -> Option<DomRoot<WebGLProgram>> { + self.current_program.get() + } + + pub fn uniform_check_program( + &self, + program: &WebGLProgram, + location: &WebGLUniformLocation, + ) -> WebGLResult<()> { + self.validate_ownership(program)?; + + if program.is_deleted() || + !program.is_linked() || + self.context_id() != location.context_id() || + program.id() != location.program_id() || + program.link_generation() != location.link_generation() + { + return Err(InvalidOperation); + } + + Ok(()) + } + + fn uniform_vec_section_int( + &self, + vec: Int32ArrayOrLongSequence, + offset: u32, + length: u32, + uniform_size: usize, + uniform_location: &WebGLUniformLocation, + ) -> WebGLResult<Vec<i32>> { + let vec = match vec { + Int32ArrayOrLongSequence::Int32Array(v) => v.to_vec(), + Int32ArrayOrLongSequence::LongSequence(v) => v, + }; + self.uniform_vec_section::<i32>(vec, offset, length, uniform_size, uniform_location) + } + + fn uniform_vec_section_float( + &self, + vec: Float32ArrayOrUnrestrictedFloatSequence, + offset: u32, + length: u32, + uniform_size: usize, + uniform_location: &WebGLUniformLocation, + ) -> WebGLResult<Vec<f32>> { + let vec = match vec { + Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => v.to_vec(), + Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, + }; + self.uniform_vec_section::<f32>(vec, offset, length, uniform_size, uniform_location) + } + + pub fn uniform_vec_section<T: Clone>( + &self, + vec: Vec<T>, + offset: u32, + length: u32, + uniform_size: usize, + uniform_location: &WebGLUniformLocation, + ) -> WebGLResult<Vec<T>> { + let offset = offset as usize; + if offset > vec.len() { + return Err(InvalidValue); + } + + let length = if length > 0 { + length as usize + } else { + vec.len() - offset + }; + if offset + length > vec.len() { + return Err(InvalidValue); + } + + let vec = if offset == 0 && length == vec.len() { + vec + } else { + vec[offset..offset + length].to_vec() + }; + + if vec.len() < uniform_size || vec.len() % uniform_size != 0 { + return Err(InvalidValue); + } + if uniform_location.size().is_none() && vec.len() != uniform_size { + return Err(InvalidOperation); + } + + Ok(vec) + } + + pub fn uniform_matrix_section( + &self, + vec: Float32ArrayOrUnrestrictedFloatSequence, + offset: u32, + length: u32, + transpose: bool, + uniform_size: usize, + uniform_location: &WebGLUniformLocation, + ) -> WebGLResult<Vec<f32>> { + let vec = match vec { + Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => v.to_vec(), + Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, + }; + if transpose { + return Err(InvalidValue); + } + self.uniform_vec_section::<f32>(vec, offset, length, uniform_size, uniform_location) + } + + pub fn get_draw_framebuffer_slot(&self) -> &MutNullableDom<WebGLFramebuffer> { + &self.bound_draw_framebuffer + } + + pub fn get_read_framebuffer_slot(&self) -> &MutNullableDom<WebGLFramebuffer> { + &self.bound_read_framebuffer + } + + pub fn validate_new_framebuffer_binding( + &self, + framebuffer: Option<&WebGLFramebuffer>, + ) -> WebGLResult<()> { + if let Some(fb) = framebuffer { + self.validate_ownership(fb)?; + if fb.is_deleted() { + // From the WebGL spec: + // + // "An attempt to bind a deleted framebuffer will + // generate an INVALID_OPERATION error, and the + // current binding will remain untouched." + return Err(InvalidOperation); + } + } + Ok(()) + } + + pub fn bind_framebuffer_to( + &self, + target: u32, + framebuffer: Option<&WebGLFramebuffer>, + slot: &MutNullableDom<WebGLFramebuffer>, + ) { + match framebuffer { + Some(framebuffer) => framebuffer.bind(target), + None => { + // Bind the default framebuffer + let cmd = + WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Default); + self.send_command(cmd); + }, + } + slot.set(framebuffer); + } + + pub fn renderbuffer_storage( + &self, + target: u32, + samples: i32, + internal_format: u32, + width: i32, + height: i32, + ) { + if target != constants::RENDERBUFFER { + return self.webgl_error(InvalidEnum); + } + + let max = self.limits.max_renderbuffer_size; + + if samples < 0 || width < 0 || width as u32 > max || height < 0 || height as u32 > max { + return self.webgl_error(InvalidValue); + } + + let rb = handle_potential_webgl_error!( + self, + self.bound_renderbuffer.get().ok_or(InvalidOperation), + return + ); + handle_potential_webgl_error!( + self, + rb.storage(self.api_type, samples, internal_format, width, height) + ); + if let Some(fb) = self.bound_draw_framebuffer.get() { + fb.invalidate_renderbuffer(&*rb); + } + + // FIXME: https://github.com/servo/servo/issues/13710 + } + + pub fn valid_color_attachment_enum(&self, attachment: u32) -> bool { + let last_slot = constants::COLOR_ATTACHMENT0 + self.limits().max_color_attachments - 1; + constants::COLOR_ATTACHMENT0 <= attachment && attachment <= last_slot + } + + pub fn compressed_tex_image_2d<'a>( + &self, + target: u32, + level: i32, + internal_format: u32, + width: i32, + height: i32, + border: i32, + data: &'a [u8], + ) { + let validator = CompressedTexImage2DValidator::new( + self, + target, + level, + width, + height, + border, + internal_format, + data.len(), + ); + let CommonCompressedTexImage2DValidatorResult { + texture, + target, + level, + width, + height, + compression, + } = match validator.validate() { + Ok(result) => result, + Err(_) => return, + }; + + if texture.is_immutable() { return self.webgl_error(InvalidOperation); } - // FINISHME: Consider doing premultiply and flip in a single mutable Vec. - let pixels = self.premultiply_pixels(format, data_type, pixels); + let size = Size2D::new(width, height); + let buff = IpcSharedMemory::from_bytes(data); + let pixels = TexPixels::from_array(buff, size); + let data = pixels.data; + + handle_potential_webgl_error!( + self, + texture.initialize( + target, + size.width, + size.height, + 1, + compression.format, + level, + Some(TexDataType::UnsignedByte) + ) + ); + + self.send_command(WebGLCommand::CompressedTexImage2D { + target: target.as_gl_constant(), + level, + internal_format, + size: Size2D::new(width, height), + data: data.into(), + }); - let pixels = self.flip_teximage_y(pixels, format, data_type, - width as usize, height as usize, unpacking_alignment as usize); + if let Some(fb) = self.bound_draw_framebuffer.get() { + fb.invalidate_texture(&*texture); + } + } - // Set the unpack alignment. For textures coming from arrays, - // this will be the current value of the context's - // GL_UNPACK_ALIGNMENT, while for textures from images or - // canvas (produced by rgba8_image_to_tex_image_data()), it - // will be 1. - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::PixelStorei(constants::UNPACK_ALIGNMENT, unpacking_alignment as i32))) - .unwrap(); + pub fn compressed_tex_sub_image_2d<'a>( + &self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + width: i32, + height: i32, + format: u32, + data: &'a [u8], + ) { + let validator = CompressedTexSubImage2DValidator::new( + self, + target, + level, + xoffset, + yoffset, + width, + height, + format, + data.len(), + ); + let CommonCompressedTexImage2DValidatorResult { + texture: _, + target, + level, + width, + height, + .. + } = match validator.validate() { + Ok(result) => result, + Err(_) => return, + }; - // TODO(emilio): convert colorspace if requested - let msg = WebGLCommand::TexSubImage2D(target.as_gl_constant(), - level as i32, xoffset, yoffset, - width as i32, height as i32, - format.as_gl_constant(), - data_type.as_gl_constant(), pixels); + let buff = IpcSharedMemory::from_bytes(data); + let pixels = TexPixels::from_array(buff, Size2D::new(width, height)); + let data = pixels.data; - self.ipc_renderer - .send(CanvasMsg::WebGL(msg)) - .unwrap() + self.send_command(WebGLCommand::CompressedTexSubImage2D { + target: target.as_gl_constant(), + level: level as i32, + xoffset, + yoffset, + size: Size2D::new(width, height), + format, + data: data.into(), + }); + } + + pub fn uniform1iv( + &self, + location: Option<&WebGLUniformLocation>, + val: Int32ArrayOrLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL | + constants::INT | + constants::SAMPLER_2D | + constants::SAMPLER_CUBE => {}, + _ => return Err(InvalidOperation), + } + + let val = self.uniform_vec_section_int(val, src_offset, src_length, 1, location)?; + + match location.type_() { + constants::SAMPLER_2D | constants::SAMPLER_CUBE => { + for &v in val + .iter() + .take(cmp::min(location.size().unwrap_or(1) as usize, val.len())) + { + if v < 0 || v as u32 >= self.limits.max_combined_texture_image_units { + return Err(InvalidValue); + } + } + }, + _ => {}, + } + self.send_command(WebGLCommand::Uniform1iv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform1fv( + &self, + location: Option<&WebGLUniformLocation>, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL | constants::FLOAT => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_float(val, src_offset, src_length, 1, location)?; + self.send_command(WebGLCommand::Uniform1fv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform2fv( + &self, + location: Option<&WebGLUniformLocation>, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC2 | constants::FLOAT_VEC2 => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_float(val, src_offset, src_length, 2, location)?; + self.send_command(WebGLCommand::Uniform2fv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform2iv( + &self, + location: Option<&WebGLUniformLocation>, + val: Int32ArrayOrLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC2 | constants::INT_VEC2 => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_int(val, src_offset, src_length, 2, location)?; + self.send_command(WebGLCommand::Uniform2iv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform3fv( + &self, + location: Option<&WebGLUniformLocation>, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC3 | constants::FLOAT_VEC3 => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_float(val, src_offset, src_length, 3, location)?; + self.send_command(WebGLCommand::Uniform3fv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform3iv( + &self, + location: Option<&WebGLUniformLocation>, + val: Int32ArrayOrLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC3 | constants::INT_VEC3 => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_int(val, src_offset, src_length, 3, location)?; + self.send_command(WebGLCommand::Uniform3iv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform4iv( + &self, + location: Option<&WebGLUniformLocation>, + val: Int32ArrayOrLongSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC4 | constants::INT_VEC4 => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_int(val, src_offset, src_length, 4, location)?; + self.send_command(WebGLCommand::Uniform4iv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform4fv( + &self, + location: Option<&WebGLUniformLocation>, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC4 | constants::FLOAT_VEC4 => {}, + _ => return Err(InvalidOperation), + } + let val = self.uniform_vec_section_float(val, src_offset, src_length, 4, location)?; + self.send_command(WebGLCommand::Uniform4fv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform_matrix_2fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::FLOAT_MAT2 => {}, + _ => return Err(InvalidOperation), + } + let val = + self.uniform_matrix_section(val, src_offset, src_length, transpose, 4, location)?; + self.send_command(WebGLCommand::UniformMatrix2fv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform_matrix_3fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::FLOAT_MAT3 => {}, + _ => return Err(InvalidOperation), + } + let val = + self.uniform_matrix_section(val, src_offset, src_length, transpose, 9, location)?; + self.send_command(WebGLCommand::UniformMatrix3fv(location.id(), val)); + Ok(()) + }); + } + + pub fn uniform_matrix_4fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + src_offset: u32, + src_length: u32, + ) { + self.with_location(location, |location| { + match location.type_() { + constants::FLOAT_MAT4 => {}, + _ => return Err(InvalidOperation), + } + let val = + self.uniform_matrix_section(val, src_offset, src_length, transpose, 16, location)?; + self.send_command(WebGLCommand::UniformMatrix4fv(location.id(), val)); + Ok(()) + }); } - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14 - fn validate_feature_enum(&self, cap: u32) -> bool { - match cap { - constants::BLEND | constants::CULL_FACE | constants::DEPTH_TEST | constants::DITHER | - constants::POLYGON_OFFSET_FILL | constants::SAMPLE_ALPHA_TO_COVERAGE | constants::SAMPLE_COVERAGE | - constants::SAMPLE_COVERAGE_INVERT | constants::SCISSOR_TEST | constants::STENCIL_TEST => true, + pub fn get_buffer_param(&self, buffer: Option<DomRoot<WebGLBuffer>>, parameter: u32) -> JSVal { + let buffer = + handle_potential_webgl_error!(self, buffer.ok_or(InvalidOperation), return NullValue()); + + match parameter { + constants::BUFFER_SIZE => Int32Value(buffer.capacity() as i32), + constants::BUFFER_USAGE => Int32Value(buffer.usage() as i32), _ => { self.webgl_error(InvalidEnum); - false + NullValue() }, } } } -impl Drop for WebGLRenderingContext { - fn drop(&mut self) { - self.ipc_renderer.send(CanvasMsg::Common(CanvasCommonMsg::Close)).unwrap(); - } +#[cfg(not(feature = "webgl_backtrace"))] +#[inline] +pub fn capture_webgl_backtrace<T: DomObject>(_: &T) -> WebGLCommandBacktrace { + WebGLCommandBacktrace {} } -// FIXME: After [1] lands and the relevant Servo and codegen PR too, we should -// convert all our raw JSObject pointers to proper types. -// -// [1]: https://github.com/servo/rust-mozjs/pull/304 -#[allow(unsafe_code)] -unsafe fn typed_array_or_sequence_to_vec<T>(cx: *mut JSContext, - sequence_or_abv: *mut JSObject, - config: <T::Element as FromJSValConvertible>::Config) - -> Result<Vec<T::Element>, Error> - where T: TypedArrayElement, - T::Element: FromJSValConvertible + Clone, - <T::Element as FromJSValConvertible>::Config: Clone, -{ - // TODO(servo/rust-mozjs#330): replace this with a macro that supports generic types. - let mut typed_array_root = Rooted::new_unrooted(); - let typed_array: Option<TypedArray<T>> = - TypedArray::from(cx, &mut typed_array_root, sequence_or_abv).ok(); - if let Some(mut typed_array) = typed_array { - return Ok(typed_array.as_slice().to_vec()); - } - assert!(!sequence_or_abv.is_null()); - rooted!(in(cx) let mut val = UndefinedValue()); - sequence_or_abv.to_jsval(cx, val.handle_mut()); - - match Vec::<T::Element>::from_jsval(cx, val.handle(), config) { - Ok(ConversionResult::Success(v)) => Ok(v), - Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())), - // FIXME: What to do here? Generated code only aborts the execution of - // the script. - Err(err) => panic!("unexpected conversion error: {:?}", err), +#[cfg(feature = "webgl_backtrace")] +#[cfg_attr(feature = "webgl_backtrace", allow(unsafe_code))] +pub fn capture_webgl_backtrace<T: DomObject>(obj: &T) -> WebGLCommandBacktrace { + let bt = Backtrace::new(); + unsafe { + capture_stack!(in(*obj.global().get_cx()) let stack); + WebGLCommandBacktrace { + backtrace: format!("{:?}", bt), + js_backtrace: stack.and_then(|s| s.as_string(None, js::jsapi::StackFormat::Default)), + } } } -#[allow(unsafe_code)] -unsafe fn fallible_array_buffer_view_to_vec(cx: *mut JSContext, abv: *mut JSObject) -> Result<Vec<u8>, Error> -{ - assert!(!abv.is_null()); - typedarray!(in(cx) let array_buffer_view: ArrayBufferView = abv); - match array_buffer_view { - Ok(mut v) => Ok(v.as_slice().to_vec()), - Err(_) => Err(Error::Type("Not an ArrayBufferView".to_owned())), +impl Drop for WebGLRenderingContext { + fn drop(&mut self) { + let _ = self.webgl_sender.send_remove(); } } impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1 - fn Canvas(&self) -> Root<HTMLCanvasElement> { - Root::from_ref(&*self.canvas) + fn Canvas(&self) -> DomRoot<HTMLCanvasElement> { + DomRoot::from_ref(&*self.canvas) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn Flush(&self) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Flush)) - .unwrap(); + self.send_command(WebGLCommand::Flush); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn Finish(&self) { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Finish(sender))) - .unwrap(); + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::Finish(sender)); receiver.recv().unwrap() } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1 fn DrawingBufferWidth(&self) -> i32 { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::DrawingBufferWidth(sender))) - .unwrap(); + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::DrawingBufferWidth(sender)); receiver.recv().unwrap() } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1 fn DrawingBufferHeight(&self) -> i32 { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::DrawingBufferHeight(sender))) - .unwrap(); + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::DrawingBufferHeight(sender)); receiver.recv().unwrap() } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 - unsafe fn GetBufferParameter(&self, _cx: *mut JSContext, target: u32, parameter: u32) -> JSVal { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::GetBufferParameter(target, parameter, sender))) - .unwrap(); - match handle_potential_webgl_error!(self, receiver.recv().unwrap(), WebGLParameter::Invalid) { - WebGLParameter::Int(val) => Int32Value(val), - WebGLParameter::Bool(_) => panic!("Buffer parameter should not be bool"), - WebGLParameter::Float(_) => panic!("Buffer parameter should not be float"), - WebGLParameter::FloatArray(_) => panic!("Buffer parameter should not be float array"), - WebGLParameter::String(_) => panic!("Buffer parameter should not be string"), - WebGLParameter::Invalid => NullValue(), - } + fn GetBufferParameter(&self, _cx: SafeJSContext, target: u32, parameter: u32) -> JSVal { + let buffer = + handle_potential_webgl_error!(self, self.bound_buffer(target), return NullValue()); + self.get_buffer_param(buffer, parameter) } #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 - unsafe fn GetParameter(&self, cx: *mut JSContext, parameter: u32) -> JSVal { - // Handle the GL_*_BINDING without going all the way - // to the GL, since we would just need to map back from GL's - // returned ID to the WebGL* object we're tracking. - match parameter { - constants::ARRAY_BUFFER_BINDING => - return object_binding_to_js_or_null!(cx, &self.bound_buffer_array), - constants::ELEMENT_ARRAY_BUFFER_BINDING => - return object_binding_to_js_or_null!(cx, &self.bound_buffer_element_array), - constants::FRAMEBUFFER_BINDING => - return object_binding_to_js_or_null!(cx, &self.bound_framebuffer), - constants::RENDERBUFFER_BINDING => - return object_binding_to_js_or_null!(cx, &self.bound_renderbuffer), - constants::TEXTURE_BINDING_2D => - return object_binding_to_js_or_null!(cx, &self.bound_texture_2d), - constants::TEXTURE_BINDING_CUBE_MAP => - return object_binding_to_js_or_null!(cx, &self.bound_texture_cube_map), + fn GetParameter(&self, cx: SafeJSContext, parameter: u32) -> JSVal { + if !self + .extension_manager + .is_get_parameter_name_enabled(parameter) + { + self.webgl_error(WebGLError::InvalidEnum); + return NullValue(); + } + match parameter { + constants::ARRAY_BUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!(*cx, &self.bound_buffer_array.get()); + }, + constants::CURRENT_PROGRAM => unsafe { + return optional_root_object_to_js_or_null!(*cx, &self.current_program.get()); + }, + constants::ELEMENT_ARRAY_BUFFER_BINDING => unsafe { + let buffer = self.current_vao().element_array_buffer().get(); + return optional_root_object_to_js_or_null!(*cx, buffer); + }, + constants::FRAMEBUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!( + *cx, + &self.bound_draw_framebuffer.get() + ); + }, + constants::RENDERBUFFER_BINDING => unsafe { + return optional_root_object_to_js_or_null!(*cx, &self.bound_renderbuffer.get()); + }, + constants::TEXTURE_BINDING_2D => unsafe { + let texture = self + .textures + .active_texture_slot(constants::TEXTURE_2D, self.webgl_version()) + .unwrap() + .get(); + return optional_root_object_to_js_or_null!(*cx, texture); + }, + constants::TEXTURE_BINDING_CUBE_MAP => unsafe { + let texture = self + .textures + .active_texture_slot(constants::TEXTURE_CUBE_MAP, self.webgl_version()) + .unwrap() + .get(); + return optional_root_object_to_js_or_null!(*cx, texture); + }, + OESVertexArrayObjectConstants::VERTEX_ARRAY_BINDING_OES => unsafe { + let vao = self.current_vao.get().filter(|vao| vao.id().is_some()); + return optional_root_object_to_js_or_null!(*cx, vao); + }, // In readPixels we currently support RGBA/UBYTE only. If // we wanted to support other formats, we could ask the // driver, but we would need to check for // GL_OES_read_format support (assuming an underlying GLES // driver. Desktop is happy to format convert for us). constants::IMPLEMENTATION_COLOR_READ_FORMAT => { - if !self.validate_framebuffer_complete() { + if self.validate_framebuffer().is_err() { + self.webgl_error(InvalidOperation); return NullValue(); - } else { - return Int32Value(constants::RGBA as i32); } - } + return Int32Value(constants::RGBA as i32); + }, constants::IMPLEMENTATION_COLOR_READ_TYPE => { - if !self.validate_framebuffer_complete() { + if self.validate_framebuffer().is_err() { + self.webgl_error(InvalidOperation); return NullValue(); - } else { - return Int32Value(constants::UNSIGNED_BYTE as i32); } - } - _ => {} + return Int32Value(constants::UNSIGNED_BYTE as i32); + }, + constants::COMPRESSED_TEXTURE_FORMATS => unsafe { + let format_ids = self.extension_manager.get_tex_compression_ids(); + + rooted!(in(*cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Uint32Array::create(*cx, CreateWith::Slice(&format_ids), rval.handle_mut()) + .unwrap(); + return ObjectValue(rval.get()); + }, + constants::VERSION => unsafe { + rooted!(in(*cx) let mut rval = UndefinedValue()); + "WebGL 1.0".to_jsval(*cx, rval.handle_mut()); + return rval.get(); + }, + constants::RENDERER | constants::VENDOR => unsafe { + rooted!(in(*cx) let mut rval = UndefinedValue()); + "Mozilla/Servo".to_jsval(*cx, rval.handle_mut()); + return rval.get(); + }, + constants::SHADING_LANGUAGE_VERSION => unsafe { + rooted!(in(*cx) let mut rval = UndefinedValue()); + "WebGL GLSL ES 1.0".to_jsval(*cx, rval.handle_mut()); + return rval.get(); + }, + constants::UNPACK_FLIP_Y_WEBGL => { + let unpack = self.texture_unpacking_settings.get(); + return BooleanValue(unpack.contains(TextureUnpacking::FLIP_Y_AXIS)); + }, + constants::UNPACK_PREMULTIPLY_ALPHA_WEBGL => { + let unpack = self.texture_unpacking_settings.get(); + return BooleanValue(unpack.contains(TextureUnpacking::PREMULTIPLY_ALPHA)); + }, + constants::PACK_ALIGNMENT => { + return UInt32Value(self.texture_packing_alignment.get() as u32); + }, + constants::UNPACK_ALIGNMENT => { + return UInt32Value(self.texture_unpacking_alignment.get()); + }, + constants::UNPACK_COLORSPACE_CONVERSION_WEBGL => { + let unpack = self.texture_unpacking_settings.get(); + return UInt32Value(if unpack.contains(TextureUnpacking::CONVERT_COLORSPACE) { + constants::BROWSER_DEFAULT_WEBGL + } else { + constants::NONE + }); + }, + _ => {}, } - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::GetParameter(parameter, sender))) - .unwrap(); - match handle_potential_webgl_error!(self, receiver.recv().unwrap(), WebGLParameter::Invalid) { - WebGLParameter::Int(val) => Int32Value(val), - WebGLParameter::Bool(val) => BooleanValue(val), - WebGLParameter::Float(val) => DoubleValue(val as f64), - WebGLParameter::FloatArray(_) => panic!("Parameter should not be float array"), - WebGLParameter::String(val) => { - rooted!(in(cx) let mut rval = UndefinedValue()); - val.to_jsval(cx, rval.handle_mut()); + // Handle any MAX_ parameters by retrieving the limits that were stored + // when this context was created. + let limit = match parameter { + constants::MAX_VERTEX_ATTRIBS => Some(self.limits.max_vertex_attribs), + constants::MAX_TEXTURE_SIZE => Some(self.limits.max_tex_size), + constants::MAX_CUBE_MAP_TEXTURE_SIZE => Some(self.limits.max_cube_map_tex_size), + constants::MAX_COMBINED_TEXTURE_IMAGE_UNITS => { + Some(self.limits.max_combined_texture_image_units) + }, + constants::MAX_FRAGMENT_UNIFORM_VECTORS => { + Some(self.limits.max_fragment_uniform_vectors) + }, + constants::MAX_RENDERBUFFER_SIZE => Some(self.limits.max_renderbuffer_size), + constants::MAX_TEXTURE_IMAGE_UNITS => Some(self.limits.max_texture_image_units), + constants::MAX_VARYING_VECTORS => Some(self.limits.max_varying_vectors), + constants::MAX_VERTEX_TEXTURE_IMAGE_UNITS => { + Some(self.limits.max_vertex_texture_image_units) + }, + constants::MAX_VERTEX_UNIFORM_VECTORS => Some(self.limits.max_vertex_uniform_vectors), + _ => None, + }; + if let Some(limit) = limit { + return UInt32Value(limit); + } + + if let Ok(value) = self.capabilities.is_enabled(parameter) { + return BooleanValue(value); + } + + match handle_potential_webgl_error!( + self, + Parameter::from_u32(parameter), + return NullValue() + ) { + Parameter::Bool(param) => { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetParameterBool(param, sender)); + BooleanValue(receiver.recv().unwrap()) + }, + Parameter::Bool4(param) => unsafe { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetParameterBool4(param, sender)); + rooted!(in(*cx) let mut rval = UndefinedValue()); + receiver.recv().unwrap().to_jsval(*cx, rval.handle_mut()); rval.get() + }, + Parameter::Int(param) => { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetParameterInt(param, sender)); + Int32Value(receiver.recv().unwrap()) + }, + Parameter::Int2(param) => unsafe { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetParameterInt2(param, sender)); + rooted!(in(*cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Int32Array::create( + *cx, + CreateWith::Slice(&receiver.recv().unwrap()), + rval.handle_mut(), + ) + .unwrap(); + ObjectValue(rval.get()) + }, + Parameter::Int4(param) => unsafe { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetParameterInt4(param, sender)); + rooted!(in(*cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Int32Array::create( + *cx, + CreateWith::Slice(&receiver.recv().unwrap()), + rval.handle_mut(), + ) + .unwrap(); + ObjectValue(rval.get()) + }, + Parameter::Float(param) => { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetParameterFloat(param, sender)); + DoubleValue(receiver.recv().unwrap() as f64) + }, + Parameter::Float2(param) => unsafe { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetParameterFloat2(param, sender)); + rooted!(in(*cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Float32Array::create( + *cx, + CreateWith::Slice(&receiver.recv().unwrap()), + rval.handle_mut(), + ) + .unwrap(); + ObjectValue(rval.get()) + }, + Parameter::Float4(param) => unsafe { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetParameterFloat4(param, sender)); + rooted!(in(*cx) let mut rval = ptr::null_mut::<JSObject>()); + let _ = Float32Array::create( + *cx, + CreateWith::Slice(&receiver.recv().unwrap()), + rval.handle_mut(), + ) + .unwrap(); + ObjectValue(rval.get()) + }, + } + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + fn GetTexParameter(&self, _cx: SafeJSContext, target: u32, pname: u32) -> JSVal { + let texture_slot = handle_potential_webgl_error!( + self, + self.textures + .active_texture_slot(target, self.webgl_version()), + return NullValue() + ); + let texture = handle_potential_webgl_error!( + self, + texture_slot.get().ok_or(InvalidOperation), + return NullValue() + ); + + if !self + .extension_manager + .is_get_tex_parameter_name_enabled(pname) + { + self.webgl_error(InvalidEnum); + return NullValue(); + } + + match pname { + constants::TEXTURE_MAG_FILTER => return UInt32Value(texture.mag_filter()), + constants::TEXTURE_MIN_FILTER => return UInt32Value(texture.min_filter()), + _ => {}, + } + + let texparam = + handle_potential_webgl_error!(self, TexParameter::from_u32(pname), return NullValue()); + if self.webgl_version() < texparam.required_webgl_version() { + self.webgl_error(InvalidEnum); + return NullValue(); + } + + if let Some(value) = texture.maybe_get_tex_parameter(texparam) { + match value { + TexParameterValue::Float(v) => return DoubleValue(v as f64), + TexParameterValue::Int(v) => return Int32Value(v), + TexParameterValue::Bool(v) => return BooleanValue(v), } - WebGLParameter::Invalid => NullValue(), + } + + match texparam { + TexParameter::Float(param) => { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetTexParameterFloat(target, param, sender)); + DoubleValue(receiver.recv().unwrap() as f64) + }, + TexParameter::Int(param) => { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetTexParameterInt(target, param, sender)); + Int32Value(receiver.recv().unwrap()) + }, + TexParameter::Bool(param) => { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetTexParameterBool(target, param, sender)); + BooleanValue(receiver.recv().unwrap()) + }, } } @@ -952,13 +2293,18 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.2 fn GetContextAttributes(&self) -> Option<WebGLContextAttributes> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); + let (sender, receiver) = webgl_channel().unwrap(); // If the send does not succeed, assume context lost - if let Err(_) = self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::GetContextAttributes(sender))) { + let backtrace = capture_webgl_backtrace(self); + if self + .webgl_sender + .send(WebGLCommand::GetContextAttributes(sender), backtrace) + .is_err() + { return None; } + let attrs = receiver.recv().unwrap(); Some(WebGLContextAttributes { @@ -969,50 +2315,57 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { preferLowPowerToHighPerformance: false, premultipliedAlpha: attrs.premultiplied_alpha, preserveDrawingBuffer: attrs.preserve_drawing_buffer, - stencil: attrs.stencil + stencil: attrs.stencil, }) } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.13 + fn IsContextLost(&self) -> bool { + false + } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14 fn GetSupportedExtensions(&self) -> Option<Vec<DOMString>> { - Some(vec![]) + self.extension_manager + .init_once(|| self.get_gl_extensions()); + let extensions = self.extension_manager.get_supported_extensions(); + Some( + extensions + .iter() + .map(|name| DOMString::from(*name)) + .collect(), + ) } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14 - unsafe fn GetExtension(&self, _cx: *mut JSContext, _name: DOMString) - -> Option<NonZero<*mut JSObject>> { - None + fn GetExtension(&self, _cx: SafeJSContext, name: DOMString) -> Option<NonNull<JSObject>> { + self.extension_manager + .init_once(|| self.get_gl_extensions()); + self.extension_manager.get_or_init_extension(&name, self) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn ActiveTexture(&self, texture: u32) { - self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::ActiveTexture(texture))).unwrap(); + handle_potential_webgl_error!(self, self.textures.set_active_unit_enum(texture), return); + self.send_command(WebGLCommand::ActiveTexture(texture)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn BlendColor(&self, r: f32, g: f32, b: f32, a: f32) { - self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::BlendColor(r, g, b, a))).unwrap(); + self.send_command(WebGLCommand::BlendColor(r, g, b, a)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn BlendEquation(&self, mode: u32) { - if mode != constants::FUNC_ADD { - return self.webgl_error(InvalidEnum); - } - - self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::BlendEquation(mode))).unwrap(); + handle_potential_webgl_error!(self, self.validate_blend_mode(mode), return); + self.send_command(WebGLCommand::BlendEquation(mode)) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn BlendEquationSeparate(&self, mode_rgb: u32, mode_alpha: u32) { - if mode_rgb != constants::FUNC_ADD || mode_alpha != constants::FUNC_ADD { - return self.webgl_error(InvalidEnum); - } - - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::BlendEquationSeparate(mode_rgb, mode_alpha))) - .unwrap(); + handle_potential_webgl_error!(self, self.validate_blend_mode(mode_rgb), return); + handle_potential_webgl_error!(self, self.validate_blend_mode(mode_alpha), return); + self.send_command(WebGLCommand::BlendEquationSeparate(mode_rgb, mode_alpha)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 @@ -1029,9 +2382,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return self.webgl_error(InvalidOperation); } - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::BlendFunc(src_factor, dest_factor))) - .unwrap(); + self.send_command(WebGLCommand::BlendFunc(src_factor, dest_factor)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 @@ -1048,87 +2399,66 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return self.webgl_error(InvalidOperation); } - self.ipc_renderer.send( - CanvasMsg::WebGL(WebGLCommand::BlendFuncSeparate(src_rgb, dest_rgb, src_alpha, dest_alpha))).unwrap(); + self.send_command(WebGLCommand::BlendFuncSeparate( + src_rgb, dest_rgb, src_alpha, dest_alpha, + )); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn AttachShader(&self, program: Option<&WebGLProgram>, shader: Option<&WebGLShader>) { - if let Some(program) = program { - if let Some(shader) = shader { - handle_potential_webgl_error!(self, program.attach_shader(shader)); - } - } + fn AttachShader(&self, program: &WebGLProgram, shader: &WebGLShader) { + handle_potential_webgl_error!(self, self.validate_ownership(program), return); + handle_potential_webgl_error!(self, self.validate_ownership(shader), return); + handle_potential_webgl_error!(self, program.attach_shader(shader)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn DetachShader(&self, program: Option<&WebGLProgram>, shader: Option<&WebGLShader>) { - if let Some(program) = program { - if let Some(shader) = shader { - handle_potential_webgl_error!(self, program.detach_shader(shader)); - } - } + fn DetachShader(&self, program: &WebGLProgram, shader: &WebGLShader) { + handle_potential_webgl_error!(self, self.validate_ownership(program), return); + handle_potential_webgl_error!(self, self.validate_ownership(shader), return); + handle_potential_webgl_error!(self, program.detach_shader(shader)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn BindAttribLocation(&self, program: Option<&WebGLProgram>, - index: u32, name: DOMString) { - if let Some(program) = program { - handle_potential_webgl_error!(self, program.bind_attrib_location(index, name)); - } + fn BindAttribLocation(&self, program: &WebGLProgram, index: u32, name: DOMString) { + handle_potential_webgl_error!(self, self.validate_ownership(program), return); + handle_potential_webgl_error!(self, program.bind_attrib_location(index, name)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 fn BindBuffer(&self, target: u32, buffer: Option<&WebGLBuffer>) { + let current_vao; let slot = match target { constants::ARRAY_BUFFER => &self.bound_buffer_array, - constants::ELEMENT_ARRAY_BUFFER => &self.bound_buffer_element_array, - + constants::ELEMENT_ARRAY_BUFFER => { + current_vao = self.current_vao(); + current_vao.element_array_buffer() + }, _ => return self.webgl_error(InvalidEnum), }; - - if let Some(buffer) = buffer { - match buffer.bind(target) { - Ok(_) => slot.set(Some(buffer)), - Err(e) => return self.webgl_error(e), - } - } else { - slot.set(None); - // Unbind the current buffer - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::BindBuffer(target, None))) - .unwrap() - } + self.bind_buffer_maybe(&slot, target, buffer); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 fn BindFramebuffer(&self, target: u32, framebuffer: Option<&WebGLFramebuffer>) { + handle_potential_webgl_error!( + self, + self.validate_new_framebuffer_binding(framebuffer), + return + ); + if target != constants::FRAMEBUFFER { - return self.webgl_error(InvalidOperation); + return self.webgl_error(InvalidEnum); } - if let Some(framebuffer) = framebuffer { - if framebuffer.is_deleted() { - // From the WebGL spec: - // - // "An attempt to bind a deleted framebuffer will - // generate an INVALID_OPERATION error, and the - // current binding will remain untouched." - return self.webgl_error(InvalidOperation); - } else { - framebuffer.bind(target); - self.bound_framebuffer.set(Some(framebuffer)); - } - } else { - // Bind the default framebuffer - let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Default); - self.ipc_renderer.send(CanvasMsg::WebGL(cmd)).unwrap(); - self.bound_framebuffer.set(framebuffer); - } + self.bind_framebuffer_to(target, framebuffer, &self.bound_draw_framebuffer) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 fn BindRenderbuffer(&self, target: u32, renderbuffer: Option<&WebGLRenderbuffer>) { + if let Some(rb) = renderbuffer { + handle_potential_webgl_error!(self, self.validate_ownership(rb), return); + } + if target != constants::RENDERBUFFER { return self.webgl_error(InvalidEnum); } @@ -1140,194 +2470,132 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Some(renderbuffer) if !renderbuffer.is_deleted() => { self.bound_renderbuffer.set(Some(renderbuffer)); renderbuffer.bind(target); - } + }, _ => { + if renderbuffer.is_some() { + self.webgl_error(InvalidOperation); + } + self.bound_renderbuffer.set(None); // Unbind the currently bound renderbuffer - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::BindRenderbuffer(target, None))) - .unwrap() - } + self.send_command(WebGLCommand::BindRenderbuffer(target, None)); + }, } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 fn BindTexture(&self, target: u32, texture: Option<&WebGLTexture>) { - let slot = match target { - constants::TEXTURE_2D => &self.bound_texture_2d, - constants::TEXTURE_CUBE_MAP => &self.bound_texture_cube_map, - _ => return self.webgl_error(InvalidEnum), - }; + if let Some(texture) = texture { + handle_potential_webgl_error!(self, self.validate_ownership(texture), return); + } + + let texture_slot = handle_potential_webgl_error!( + self, + self.textures + .active_texture_slot(target, self.webgl_version()), + return + ); if let Some(texture) = texture { - match texture.bind(target) { - Ok(_) => slot.set(Some(texture)), - Err(err) => return self.webgl_error(err), - } + handle_potential_webgl_error!(self, texture.bind(target), return); } else { - slot.set(None); - // Unbind the currently bound texture - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::BindTexture(target, None))) - .unwrap() + self.send_command(WebGLCommand::BindTexture(target, None)); } + texture_slot.set(texture); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 fn GenerateMipmap(&self, target: u32) { - let slot = match target { - constants::TEXTURE_2D => &self.bound_texture_2d, - constants::TEXTURE_CUBE_MAP => &self.bound_texture_cube_map, - - _ => return self.webgl_error(InvalidEnum), - }; - - match slot.get() { - Some(texture) => handle_potential_webgl_error!(self, texture.generate_mipmap()), - None => self.webgl_error(InvalidOperation) - } + let texture_slot = handle_potential_webgl_error!( + self, + self.textures + .active_texture_slot(target, self.webgl_version()), + return + ); + let texture = + handle_potential_webgl_error!(self, texture_slot.get().ok_or(InvalidOperation), return); + handle_potential_webgl_error!(self, texture.generate_mipmap()); } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 - unsafe fn BufferData(&self, cx: *mut JSContext, target: u32, data: *mut JSObject, usage: u32) -> Fallible<()> { - if data.is_null() { - return Ok(self.webgl_error(InvalidValue)); - } - - typedarray!(in(cx) let array_buffer: ArrayBuffer = data); - let data_vec = match array_buffer { - Ok(mut data) => data.as_slice().to_vec(), - Err(_) => try!(fallible_array_buffer_view_to_vec(cx, data)), - }; - - let bound_buffer = match target { - constants::ARRAY_BUFFER => self.bound_buffer_array.get(), - constants::ELEMENT_ARRAY_BUFFER => self.bound_buffer_element_array.get(), - _ => return Ok(self.webgl_error(InvalidEnum)), - }; - - let bound_buffer = match bound_buffer { - Some(bound_buffer) => bound_buffer, - None => return Ok(self.webgl_error(InvalidValue)), - }; - - match usage { - constants::STREAM_DRAW | - constants::STATIC_DRAW | - constants::DYNAMIC_DRAW => (), - _ => return Ok(self.webgl_error(InvalidEnum)), - } - - handle_potential_webgl_error!(self, bound_buffer.buffer_data(target, &data_vec, usage)); - - Ok(()) + fn BufferData_(&self, target: u32, data: Option<ArrayBufferViewOrArrayBuffer>, usage: u32) { + let usage = handle_potential_webgl_error!(self, self.buffer_usage(usage), return); + let bound_buffer = handle_potential_webgl_error!(self, self.bound_buffer(target), return); + self.buffer_data(target, data, usage, bound_buffer) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 - fn BufferData_(&self, target: u32, size: i64, usage: u32) -> Fallible<()> { - let bound_buffer = match target { - constants::ARRAY_BUFFER => self.bound_buffer_array.get(), - constants::ELEMENT_ARRAY_BUFFER => self.bound_buffer_element_array.get(), - _ => return Ok(self.webgl_error(InvalidEnum)), - }; - - let bound_buffer = match bound_buffer { - Some(bound_buffer) => bound_buffer, - None => return Ok(self.webgl_error(InvalidValue)), - }; - - if size < 0 { - return Ok(self.webgl_error(InvalidValue)); - } - - match usage { - constants::STREAM_DRAW | - constants::STATIC_DRAW | - constants::DYNAMIC_DRAW => (), - _ => return Ok(self.webgl_error(InvalidEnum)), - } - - // FIXME: Allocating a buffer based on user-requested size is - // not great, but we don't have a fallible allocation to try. - let data = vec![0u8; size as usize]; - handle_potential_webgl_error!(self, bound_buffer.buffer_data(target, &data, usage)); - - Ok(()) + fn BufferData(&self, target: u32, size: i64, usage: u32) { + let usage = handle_potential_webgl_error!(self, self.buffer_usage(usage), return); + let bound_buffer = handle_potential_webgl_error!(self, self.bound_buffer(target), return); + self.buffer_data_(target, size, usage, bound_buffer) } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 - unsafe fn BufferSubData(&self, cx: *mut JSContext, target: u32, offset: i64, data: *mut JSObject) -> Fallible<()> { - if data.is_null() { - return Ok(self.webgl_error(InvalidValue)); - } - - typedarray!(in(cx) let array_buffer: ArrayBuffer = data); - let data_vec = match array_buffer { - Ok(mut data) => data.as_slice().to_vec(), - Err(_) => try!(fallible_array_buffer_view_to_vec(cx, data)), - }; - - let bound_buffer = match target { - constants::ARRAY_BUFFER => self.bound_buffer_array.get(), - constants::ELEMENT_ARRAY_BUFFER => self.bound_buffer_element_array.get(), - _ => return Ok(self.webgl_error(InvalidEnum)), - }; - - let bound_buffer = match bound_buffer { - Some(bound_buffer) => bound_buffer, - None => return Ok(self.webgl_error(InvalidOperation)), - }; - - if offset < 0 { - return Ok(self.webgl_error(InvalidValue)); - } - - if (offset as usize) + data_vec.len() > bound_buffer.capacity() { - return Ok(self.webgl_error(InvalidValue)); - } - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::BufferSubData(target, offset as isize, data_vec))) - .unwrap(); - - Ok(()) + #[allow(unsafe_code)] + fn BufferSubData(&self, target: u32, offset: i64, data: ArrayBufferViewOrArrayBuffer) { + let bound_buffer = handle_potential_webgl_error!(self, self.bound_buffer(target), return); + self.buffer_sub_data(target, offset, data, bound_buffer) } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 - unsafe fn CompressedTexImage2D(&self, cx: *mut JSContext, _target: u32, _level: i32, _internal_format: u32, - _width: i32, _height: i32, _border: i32, pixels: *mut JSObject) -> Fallible<()> { - let _data = try!(fallible_array_buffer_view_to_vec(cx, pixels) ); - // FIXME: No compressed texture format is currently supported, so error out as per - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#COMPRESSED_TEXTURE_SUPPORT - self.webgl_error(InvalidEnum); - Ok(()) + #[allow(unsafe_code)] + fn CompressedTexImage2D( + &self, + target: u32, + level: i32, + internal_format: u32, + width: i32, + height: i32, + border: i32, + data: CustomAutoRooterGuard<ArrayBufferView>, + ) { + let data = unsafe { data.as_slice() }; + self.compressed_tex_image_2d(target, level, internal_format, width, height, border, data) } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 - unsafe fn CompressedTexSubImage2D(&self, cx: *mut JSContext, _target: u32, _level: i32, - _xoffset: i32, _yoffset: i32, _width: i32, _height: i32, - _format: u32, pixels: *mut JSObject) -> Fallible<()> { - let _data = try!(fallible_array_buffer_view_to_vec(cx, pixels)); - // FIXME: No compressed texture format is currently supported, so error out as per - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#COMPRESSED_TEXTURE_SUPPORT - self.webgl_error(InvalidEnum); - - Ok(()) + #[allow(unsafe_code)] + fn CompressedTexSubImage2D( + &self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + width: i32, + height: i32, + format: u32, + data: CustomAutoRooterGuard<ArrayBufferView>, + ) { + let data = unsafe { data.as_slice() }; + self.compressed_tex_sub_image_2d( + target, level, xoffset, yoffset, width, height, format, data, + ) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 - fn CopyTexImage2D(&self, target: u32, level: i32, internal_format: u32, - x: i32, y: i32, width: i32, height: i32, border: i32) { - if !self.validate_framebuffer_complete() { - return; - } - - let validator = CommonTexImage2DValidator::new(self, target, level, - internal_format, width, - height, border); + fn CopyTexImage2D( + &self, + target: u32, + level: i32, + internal_format: u32, + x: i32, + y: i32, + width: i32, + height: i32, + border: i32, + ) { + handle_potential_webgl_error!(self, self.validate_framebuffer(), return); + + let validator = CommonTexImage2DValidator::new( + self, + target, + level, + internal_format, + width, + height, + border, + ); let CommonTexImage2DValidatorResult { texture, target, @@ -1341,52 +2609,104 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Err(_) => return, }; - let image_info = texture.image_info_for_target(&target, level); + if texture.is_immutable() { + return self.webgl_error(InvalidOperation); + } - // The color buffer components can be dropped during the conversion to - // the internal_format, but new components cannot be added. - // - // Note that this only applies if we're copying to an already - // initialized texture. - // - // GL_INVALID_OPERATION is generated if the color buffer cannot be - // converted to the internal_format. - if let Some(old_internal_format) = image_info.internal_format() { - if old_internal_format.components() > internal_format.components() { - return self.webgl_error(InvalidOperation); - } + let framebuffer_format = match self.bound_draw_framebuffer.get() { + Some(fb) => match fb.attachment(constants::COLOR_ATTACHMENT0) { + Some(WebGLFramebufferAttachmentRoot::Renderbuffer(rb)) => { + TexFormat::from_gl_constant(rb.internal_format()) + }, + Some(WebGLFramebufferAttachmentRoot::Texture(texture)) => texture + .image_info_for_target(&target, 0) + .map(|info| info.internal_format()), + None => None, + }, + None => { + let attrs = self.GetContextAttributes().unwrap(); + Some(if attrs.alpha { + TexFormat::RGBA + } else { + TexFormat::RGB + }) + }, + }; + + let framebuffer_format = match framebuffer_format { + Some(f) => f, + None => { + self.webgl_error(InvalidOperation); + return; + }, + }; + + match (framebuffer_format, internal_format) { + (a, b) if a == b => (), + (TexFormat::RGBA, TexFormat::RGB) => (), + (TexFormat::RGBA, TexFormat::Alpha) => (), + (TexFormat::RGBA, TexFormat::Luminance) => (), + (TexFormat::RGBA, TexFormat::LuminanceAlpha) => (), + (TexFormat::RGB, TexFormat::Luminance) => (), + _ => { + self.webgl_error(InvalidOperation); + return; + }, } // NB: TexImage2D depth is always equal to 1 - handle_potential_webgl_error!(self, texture.initialize(target, - width as u32, - height as u32, 1, - internal_format, - level as u32, - None)); + handle_potential_webgl_error!( + self, + texture.initialize( + target, + width as u32, + height as u32, + 1, + internal_format, + level as u32, + None + ) + ); - let msg = WebGLCommand::CopyTexImage2D(target.as_gl_constant(), - level as i32, - internal_format.as_gl_constant(), - x, y, - width as i32, height as i32, - border as i32); + let msg = WebGLCommand::CopyTexImage2D( + target.as_gl_constant(), + level as i32, + internal_format.as_gl_constant(), + x, + y, + width as i32, + height as i32, + border as i32, + ); - self.ipc_renderer.send(CanvasMsg::WebGL(msg)).unwrap() + self.send_command(msg); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 - fn CopyTexSubImage2D(&self, target: u32, level: i32, xoffset: i32, yoffset: i32, - x: i32, y: i32, width: i32, height: i32) { - if !self.validate_framebuffer_complete() { - return; - } + fn CopyTexSubImage2D( + &self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + x: i32, + y: i32, + width: i32, + height: i32, + ) { + handle_potential_webgl_error!(self, self.validate_framebuffer(), return); // NB: We use a dummy (valid) format and border in order to reuse the // common validations, but this should have its own validator. - let validator = CommonTexImage2DValidator::new(self, target, level, - TexFormat::RGBA.as_gl_constant(), - width, height, 0); + let validator = CommonTexImage2DValidator::new( + self, + target, + level, + TexFormat::RGBA.as_gl_constant(), + width, + height, + 0, + ); let CommonTexImage2DValidatorResult { texture, target, @@ -1399,72 +2719,81 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Err(_) => return, }; - let image_info = texture.image_info_for_target(&target, level); + let image_info = match texture.image_info_for_target(&target, level) { + Some(info) => info, + None => return self.webgl_error(InvalidOperation), + }; // GL_INVALID_VALUE is generated if: // - xoffset or yoffset is less than 0 // - x offset plus the width is greater than the texture width // - y offset plus the height is greater than the texture height - if xoffset < 0 || (xoffset as u32 + width) > image_info.width() || - yoffset < 0 || (yoffset as u32 + height) > image_info.height() { - self.webgl_error(InvalidValue); - return; + if xoffset < 0 || + (xoffset as u32 + width) > image_info.width() || + yoffset < 0 || + (yoffset as u32 + height) > image_info.height() + { + self.webgl_error(InvalidValue); + return; } - let msg = WebGLCommand::CopyTexSubImage2D(target.as_gl_constant(), - level as i32, xoffset, yoffset, - x, y, - width as i32, height as i32); + let msg = WebGLCommand::CopyTexSubImage2D( + target.as_gl_constant(), + level as i32, + xoffset, + yoffset, + x, + y, + width as i32, + height as i32, + ); - self.ipc_renderer.send(CanvasMsg::WebGL(msg)).unwrap(); + self.send_command(msg); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn Clear(&self, mask: u32) { - if !self.validate_framebuffer_complete() { - return; + handle_potential_webgl_error!(self, self.validate_framebuffer(), return); + if mask & + !(constants::DEPTH_BUFFER_BIT | + constants::STENCIL_BUFFER_BIT | + constants::COLOR_BUFFER_BIT) != + 0 + { + return self.webgl_error(InvalidValue); } - self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::Clear(mask))).unwrap(); + self.send_command(WebGLCommand::Clear(mask)); self.mark_as_dirty(); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn ClearColor(&self, red: f32, green: f32, blue: f32, alpha: f32) { self.current_clear_color.set((red, green, blue, alpha)); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::ClearColor(red, green, blue, alpha))) - .unwrap() + self.send_command(WebGLCommand::ClearColor(red, green, blue, alpha)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn ClearDepth(&self, depth: f32) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::ClearDepth(depth as f64))) - .unwrap() + self.send_command(WebGLCommand::ClearDepth(depth)) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn ClearStencil(&self, stencil: i32) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::ClearStencil(stencil))) - .unwrap() + self.send_command(WebGLCommand::ClearStencil(stencil)) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn ColorMask(&self, r: bool, g: bool, b: bool, a: bool) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::ColorMask(r, g, b, a))) - .unwrap() + self.send_command(WebGLCommand::ColorMask(r, g, b, a)) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn CullFace(&self, mode: u32) { match mode { - constants::FRONT | constants::BACK | constants::FRONT_AND_BACK => - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::CullFace(mode))) - .unwrap(), + constants::FRONT | constants::BACK | constants::FRONT_AND_BACK => { + self.send_command(WebGLCommand::CullFace(mode)) + }, _ => self.webgl_error(InvalidEnum), } } @@ -1472,557 +2801,770 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn FrontFace(&self, mode: u32) { match mode { - constants::CW | constants::CCW => - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::FrontFace(mode))) - .unwrap(), + constants::CW | constants::CCW => self.send_command(WebGLCommand::FrontFace(mode)), _ => self.webgl_error(InvalidEnum), } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn DepthFunc(&self, func: u32) { match func { - constants::NEVER | constants::LESS | - constants::EQUAL | constants::LEQUAL | - constants::GREATER | constants::NOTEQUAL | - constants::GEQUAL | constants::ALWAYS => - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::DepthFunc(func))) - .unwrap(), + constants::NEVER | + constants::LESS | + constants::EQUAL | + constants::LEQUAL | + constants::GREATER | + constants::NOTEQUAL | + constants::GEQUAL | + constants::ALWAYS => self.send_command(WebGLCommand::DepthFunc(func)), _ => self.webgl_error(InvalidEnum), } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn DepthMask(&self, flag: bool) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::DepthMask(flag))) - .unwrap() + self.send_command(WebGLCommand::DepthMask(flag)) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn DepthRange(&self, near: f32, far: f32) { - // From the WebGL 1.0 spec, 6.12: Viewport Depth Range: - // - // "A call to depthRange will generate an - // INVALID_OPERATION error if zNear is greater than - // zFar." + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#VIEWPORT_DEPTH_RANGE if near > far { return self.webgl_error(InvalidOperation); } - - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::DepthRange(near as f64, far as f64))) - .unwrap() + self.send_command(WebGLCommand::DepthRange(near, far)) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn Enable(&self, cap: u32) { - if self.validate_feature_enum(cap) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Enable(cap))) - .unwrap(); + if handle_potential_webgl_error!(self, self.capabilities.set(cap, true), return) { + self.send_command(WebGLCommand::Enable(cap)); } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn Disable(&self, cap: u32) { - if self.validate_feature_enum(cap) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Disable(cap))) - .unwrap() + if handle_potential_webgl_error!(self, self.capabilities.set(cap, false), return) { + self.send_command(WebGLCommand::Disable(cap)); } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn CompileShader(&self, shader: Option<&WebGLShader>) { - if let Some(shader) = shader { - shader.compile() - } + fn CompileShader(&self, shader: &WebGLShader) { + handle_potential_webgl_error!(self, self.validate_ownership(shader), return); + handle_potential_webgl_error!( + self, + shader.compile( + self.api_type, + self.webgl_version, + self.glsl_version, + &self.limits, + &self.extension_manager, + ) + ) } - // TODO(emilio): Probably in the future we should keep track of the - // generated objects, either here or in the webgl thread // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 - fn CreateBuffer(&self) -> Option<Root<WebGLBuffer>> { - WebGLBuffer::maybe_new(self.global().as_window(), self.ipc_renderer.clone()) + fn CreateBuffer(&self) -> Option<DomRoot<WebGLBuffer>> { + WebGLBuffer::maybe_new(self) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 - fn CreateFramebuffer(&self) -> Option<Root<WebGLFramebuffer>> { - WebGLFramebuffer::maybe_new(self.global().as_window(), self.ipc_renderer.clone()) + fn CreateFramebuffer(&self) -> Option<DomRoot<WebGLFramebuffer>> { + WebGLFramebuffer::maybe_new(self) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 - fn CreateRenderbuffer(&self) -> Option<Root<WebGLRenderbuffer>> { - WebGLRenderbuffer::maybe_new(self.global().as_window(), self.ipc_renderer.clone()) + fn CreateRenderbuffer(&self) -> Option<DomRoot<WebGLRenderbuffer>> { + WebGLRenderbuffer::maybe_new(self) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 - fn CreateTexture(&self) -> Option<Root<WebGLTexture>> { - WebGLTexture::maybe_new(self.global().as_window(), self.ipc_renderer.clone()) + fn CreateTexture(&self) -> Option<DomRoot<WebGLTexture>> { + WebGLTexture::maybe_new(self) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn CreateProgram(&self) -> Option<Root<WebGLProgram>> { - WebGLProgram::maybe_new(self.global().as_window(), self.ipc_renderer.clone()) + fn CreateProgram(&self) -> Option<DomRoot<WebGLProgram>> { + WebGLProgram::maybe_new(self) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn CreateShader(&self, shader_type: u32) -> Option<Root<WebGLShader>> { + fn CreateShader(&self, shader_type: u32) -> Option<DomRoot<WebGLShader>> { match shader_type { constants::VERTEX_SHADER | constants::FRAGMENT_SHADER => {}, _ => { self.webgl_error(InvalidEnum); return None; - } + }, } - WebGLShader::maybe_new(self.global().as_window(), self.ipc_renderer.clone(), shader_type) + WebGLShader::maybe_new(self, shader_type) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 fn DeleteBuffer(&self, buffer: Option<&WebGLBuffer>) { - if let Some(buffer) = buffer { - handle_object_deletion!(self, self.bound_buffer_array, buffer, - Some(WebGLCommand::BindBuffer(constants::ARRAY_BUFFER, None))); - handle_object_deletion!(self, self.bound_buffer_element_array, buffer, - Some(WebGLCommand::BindBuffer(constants::ELEMENT_ARRAY_BUFFER, None))); - buffer.delete() + let buffer = match buffer { + Some(buffer) => buffer, + None => return, + }; + handle_potential_webgl_error!(self, self.validate_ownership(buffer), return); + if buffer.is_marked_for_deletion() { + return; } + self.current_vao().unbind_buffer(buffer); + if self + .bound_buffer_array + .get() + .map_or(false, |b| buffer == &*b) + { + self.bound_buffer_array.set(None); + buffer.decrement_attached_counter(Operation::Infallible); + } + buffer.mark_for_deletion(Operation::Infallible); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 fn DeleteFramebuffer(&self, framebuffer: Option<&WebGLFramebuffer>) { if let Some(framebuffer) = framebuffer { - handle_object_deletion!(self, self.bound_framebuffer, framebuffer, - Some(WebGLCommand::BindFramebuffer(constants::FRAMEBUFFER, - WebGLFramebufferBindingRequest::Default))); - framebuffer.delete() + // https://immersive-web.github.io/webxr/#opaque-framebuffer + // Can opaque framebuffers be deleted? + // https://github.com/immersive-web/webxr/issues/855 + handle_potential_webgl_error!(self, framebuffer.validate_transparent(), return); + handle_potential_webgl_error!(self, self.validate_ownership(framebuffer), return); + handle_object_deletion!( + self, + self.bound_draw_framebuffer, + framebuffer, + Some(WebGLCommand::BindFramebuffer( + framebuffer.target().unwrap(), + WebGLFramebufferBindingRequest::Default + )) + ); + framebuffer.delete(Operation::Infallible) } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 fn DeleteRenderbuffer(&self, renderbuffer: Option<&WebGLRenderbuffer>) { if let Some(renderbuffer) = renderbuffer { - handle_object_deletion!(self, self.bound_renderbuffer, renderbuffer, - Some(WebGLCommand::BindRenderbuffer(constants::RENDERBUFFER, None))); - // From the GLES 2.0.25 spec, page 113: - // - // "If a renderbuffer object is deleted while its - // image is attached to the currently bound - // framebuffer, then it is as if - // FramebufferRenderbuffer had been called, with a - // renderbuffer of 0, for each attachment point to - // which this image was attached in the currently - // bound framebuffer." - // - if let Some(fb) = self.bound_framebuffer.get() { - fb.detach_renderbuffer(renderbuffer); - } - - renderbuffer.delete() + handle_potential_webgl_error!(self, self.validate_ownership(renderbuffer), return); + handle_object_deletion!( + self, + self.bound_renderbuffer, + renderbuffer, + Some(WebGLCommand::BindRenderbuffer( + constants::RENDERBUFFER, + None + )) + ); + renderbuffer.delete(Operation::Infallible) } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 fn DeleteTexture(&self, texture: Option<&WebGLTexture>) { if let Some(texture) = texture { - handle_object_deletion!(self, self.bound_texture_2d, texture, - Some(WebGLCommand::BindTexture(constants::TEXTURE_2D, None))); - handle_object_deletion!(self, self.bound_texture_cube_map, texture, - Some(WebGLCommand::BindTexture(constants::TEXTURE_CUBE_MAP, None))); + handle_potential_webgl_error!(self, self.validate_ownership(texture), return); - // From the GLES 2.0.25 spec, page 113: + // From the GLES 2.0.25 spec, page 85: + // + // "If a texture that is currently bound to one of the targets + // TEXTURE_2D, or TEXTURE_CUBE_MAP is deleted, it is as though + // BindTexture had been executed with the same target and texture + // zero." // - // "If a texture object is deleted while its image is - // attached to the currently bound framebuffer, then - // it is as if FramebufferTexture2D had been called, - // with a texture of 0, for each attachment point to - // which this image was attached in the currently - // bound framebuffer." - if let Some(fb) = self.bound_framebuffer.get() { - fb.detach_texture(texture); + // The same texture may be bound to multiple texture units. + let mut active_unit_enum = self.textures.active_unit_enum(); + for (unit_enum, slot) in self.textures.iter() { + if let Some(target) = slot.unbind(texture) { + if unit_enum != active_unit_enum { + self.send_command(WebGLCommand::ActiveTexture(unit_enum)); + active_unit_enum = unit_enum; + } + self.send_command(WebGLCommand::BindTexture(target, None)); + } + } + + // Restore bound texture unit if it has been changed. + if active_unit_enum != self.textures.active_unit_enum() { + self.send_command(WebGLCommand::ActiveTexture( + self.textures.active_unit_enum(), + )); } - texture.delete() + + texture.delete(Operation::Infallible) } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn DeleteProgram(&self, program: Option<&WebGLProgram>) { if let Some(program) = program { - // FIXME: We should call glUseProgram(0), but - // WebGLCommand::UseProgram() doesn't take an Option - // currently. This is also a problem for useProgram(null) - handle_object_deletion!(self, self.current_program, program, None); - program.delete() + handle_potential_webgl_error!(self, self.validate_ownership(program), return); + program.mark_for_deletion(Operation::Infallible) } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn DeleteShader(&self, shader: Option<&WebGLShader>) { if let Some(shader) = shader { - shader.delete() + handle_potential_webgl_error!(self, self.validate_ownership(shader), return); + shader.mark_for_deletion(Operation::Infallible) } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn DrawArrays(&self, mode: u32, first: i32, count: i32) { - match mode { - constants::POINTS | constants::LINE_STRIP | - constants::LINE_LOOP | constants::LINES | - constants::TRIANGLE_STRIP | constants::TRIANGLE_FAN | - constants::TRIANGLES => { - if self.current_program.get().is_none() { - return self.webgl_error(InvalidOperation); - } - - if first < 0 || count < 0 { - return self.webgl_error(InvalidValue); - } - - if !self.validate_framebuffer_complete() { - return; - } - - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::DrawArrays(mode, first, count))) - .unwrap(); - self.mark_as_dirty(); - }, - _ => self.webgl_error(InvalidEnum), - } + handle_potential_webgl_error!(self, self.draw_arrays_instanced(mode, first, count, 1)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11 fn DrawElements(&self, mode: u32, count: i32, type_: u32, offset: i64) { - // From the GLES 2.0.25 spec, page 21: - // - // "type must be one of UNSIGNED_BYTE or UNSIGNED_SHORT" - let type_size = match type_ { - constants::UNSIGNED_BYTE => 1, - constants::UNSIGNED_SHORT => 2, - _ => return self.webgl_error(InvalidEnum), - }; - - if offset % type_size != 0 { - return self.webgl_error(InvalidOperation); - } - - if count < 0 { - return self.webgl_error(InvalidValue); - } - - if offset < 0 { - return self.webgl_error(InvalidValue); - } - - if self.current_program.get().is_none() { - // From the WebGL spec - // - // If the CURRENT_PROGRAM is null, an INVALID_OPERATION error will be generated. - // WebGL performs additional error checking beyond that specified - // in OpenGL ES 2.0 during calls to drawArrays and drawElements. - // - return self.webgl_error(InvalidOperation); - } - - if let Some(array_buffer) = self.bound_buffer_element_array.get() { - // WebGL Spec: check buffer overflows, must be a valid multiple of the size. - let val = offset as u64 + (count as u64 * type_size as u64); - if val > array_buffer.capacity() as u64 { - return self.webgl_error(InvalidOperation); - } - } else { - // From the WebGL spec - // - // a non-null WebGLBuffer must be bound to the ELEMENT_ARRAY_BUFFER binding point - // or an INVALID_OPERATION error will be generated. - // - return self.webgl_error(InvalidOperation); - } - - if !self.validate_framebuffer_complete() { - return; - } - - match mode { - constants::POINTS | constants::LINE_STRIP | - constants::LINE_LOOP | constants::LINES | - constants::TRIANGLE_STRIP | constants::TRIANGLE_FAN | - constants::TRIANGLES => { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::DrawElements(mode, count, type_, offset))) - .unwrap(); - self.mark_as_dirty(); - }, - _ => self.webgl_error(InvalidEnum), - } + handle_potential_webgl_error!( + self, + self.draw_elements_instanced(mode, count, type_, offset, 1) + ); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 fn EnableVertexAttribArray(&self, attrib_id: u32) { - if attrib_id > self.limits.max_vertex_attribs { + if attrib_id >= self.limits.max_vertex_attribs { return self.webgl_error(InvalidValue); } - - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::EnableVertexAttribArray(attrib_id))) - .unwrap() + match self.webgl_version() { + WebGLVersion::WebGL1 => self + .current_vao() + .enabled_vertex_attrib_array(attrib_id, true), + WebGLVersion::WebGL2 => self + .current_vao_webgl2() + .enabled_vertex_attrib_array(attrib_id, true), + }; + self.send_command(WebGLCommand::EnableVertexAttribArray(attrib_id)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 fn DisableVertexAttribArray(&self, attrib_id: u32) { - if attrib_id > self.limits.max_vertex_attribs { + if attrib_id >= self.limits.max_vertex_attribs { return self.webgl_error(InvalidValue); } - - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::DisableVertexAttribArray(attrib_id))) - .unwrap() + match self.webgl_version() { + WebGLVersion::WebGL1 => self + .current_vao() + .enabled_vertex_attrib_array(attrib_id, false), + WebGLVersion::WebGL2 => self + .current_vao_webgl2() + .enabled_vertex_attrib_array(attrib_id, false), + }; + self.send_command(WebGLCommand::DisableVertexAttribArray(attrib_id)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn GetActiveUniform(&self, program: Option<&WebGLProgram>, index: u32) -> Option<Root<WebGLActiveInfo>> { - let program = match program { - Some(program) => program, - None => { - // Reasons to generate InvalidValue error - // From the GLES 2.0 spec - // - // "INVALID_VALUE is generated if index is greater than or equal - // to the number of active uniform variables in program" - // - // A null program has no uniforms so any index is always greater than the active uniforms - // WebGl conformance expects error with null programs. Check tests in get-active-test.html - self.webgl_error(InvalidValue); - return None; - } - }; - + fn GetActiveUniform( + &self, + program: &WebGLProgram, + index: u32, + ) -> Option<DomRoot<WebGLActiveInfo>> { + handle_potential_webgl_error!(self, self.validate_ownership(program), return None); match program.get_active_uniform(index) { Ok(ret) => Some(ret), Err(e) => { self.webgl_error(e); return None; - } + }, } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn GetActiveAttrib(&self, program: Option<&WebGLProgram>, index: u32) -> Option<Root<WebGLActiveInfo>> { - let program = match program { - Some(program) => program, - None => { - // Reasons to generate InvalidValue error - // From the GLES 2.0 spec - // - // "INVALID_VALUE is generated if index is greater than or equal - // to the number of active attribute variables in program" - // - // A null program has no attributes so any index is always greater than the active uniforms - // WebGl conformance expects error with null programs. Check tests in get-active-test.html - self.webgl_error(InvalidValue); - return None; - } + fn GetActiveAttrib( + &self, + program: &WebGLProgram, + index: u32, + ) -> Option<DomRoot<WebGLActiveInfo>> { + handle_potential_webgl_error!(self, self.validate_ownership(program), return None); + handle_potential_webgl_error!(self, program.get_active_attrib(index).map(Some), None) + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn GetAttribLocation(&self, program: &WebGLProgram, name: DOMString) -> i32 { + handle_potential_webgl_error!(self, self.validate_ownership(program), return -1); + handle_potential_webgl_error!(self, program.get_attrib_location(name), -1) + } + + #[allow(unsafe_code)] + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn GetFramebufferAttachmentParameter( + &self, + cx: SafeJSContext, + target: u32, + attachment: u32, + pname: u32, + ) -> JSVal { + // Check if currently bound framebuffer is non-zero as per spec. + if let Some(fb) = self.bound_draw_framebuffer.get() { + // Opaque framebuffers cannot have their attachments inspected + // https://immersive-web.github.io/webxr/#opaque-framebuffer + handle_potential_webgl_error!(self, fb.validate_transparent(), return NullValue()); + } else { + self.webgl_error(InvalidOperation); + return NullValue(); + } + + // Note: commented out stuff is for the WebGL2 standard. + let target_matches = match target { + // constants::READ_FRAMEBUFFER | + // constants::DRAW_FRAMEBUFFER => true, + constants::FRAMEBUFFER => true, + _ => false, + }; + let attachment_matches = match attachment { + // constants::MAX_COLOR_ATTACHMENTS ... gl::COLOR_ATTACHMENT0 | + // constants::BACK | + constants::COLOR_ATTACHMENT0 | + constants::DEPTH_STENCIL_ATTACHMENT | + constants::DEPTH_ATTACHMENT | + constants::STENCIL_ATTACHMENT => true, + _ => false, + }; + let pname_matches = match pname { + // constants::FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE | + // constants::FRAMEBUFFER_ATTACHMENT_BLUE_SIZE | + // constants::FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING | + // constants::FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE | + // constants::FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE | + // constants::FRAMEBUFFER_ATTACHMENT_GREEN_SIZE | + // constants::FRAMEBUFFER_ATTACHMENT_RED_SIZE | + // constants::FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE | + // constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER | + constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME | + constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE | + constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE | + constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL => true, + _ => false, }; - match program.get_active_attrib(index) { - Ok(ret) => Some(ret), - Err(e) => { - self.webgl_error(e); - return None; + let bound_attachment_matches = match self + .bound_draw_framebuffer + .get() + .unwrap() + .attachment(attachment) + { + Some(attachment_root) => match attachment_root { + WebGLFramebufferAttachmentRoot::Renderbuffer(_) => match pname { + constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE | + constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME => true, + _ => false, + }, + WebGLFramebufferAttachmentRoot::Texture(_) => match pname { + constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE | + constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME | + constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL | + constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE => true, + _ => false, + }, + }, + _ => match pname { + constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE => true, + _ => false, + }, + }; + + if !target_matches || !attachment_matches || !pname_matches || !bound_attachment_matches { + self.webgl_error(InvalidEnum); + return NullValue(); + } + + // From the GLES2 spec: + // + // If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE, + // then querying any other pname will generate INVALID_ENUM. + // + // otherwise, return `WebGLRenderbuffer` or `WebGLTexture` dom object + if pname == constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME { + // if fb is None, an INVALID_OPERATION is returned + // at the beggining of the function, so `.unwrap()` will never panic + let fb = self.bound_draw_framebuffer.get().unwrap(); + if let Some(webgl_attachment) = fb.attachment(attachment) { + match webgl_attachment { + WebGLFramebufferAttachmentRoot::Renderbuffer(rb) => unsafe { + rooted!(in(*cx) let mut rval = NullValue()); + rb.to_jsval(*cx, rval.handle_mut()); + return rval.get(); + }, + WebGLFramebufferAttachmentRoot::Texture(texture) => unsafe { + rooted!(in(*cx) let mut rval = NullValue()); + texture.to_jsval(*cx, rval.handle_mut()); + return rval.get(); + }, + } } + self.webgl_error(InvalidEnum); + return NullValue(); } + + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetFramebufferAttachmentParameter( + target, attachment, pname, sender, + )); + + Int32Value(receiver.recv().unwrap()) } - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn GetAttribLocation(&self, program: Option<&WebGLProgram>, name: DOMString) -> i32 { - if let Some(program) = program { - handle_potential_webgl_error!(self, program.get_attrib_location(name), None).unwrap_or(-1) - } else { - -1 + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 + fn GetRenderbufferParameter(&self, _cx: SafeJSContext, target: u32, pname: u32) -> JSVal { + // We do not check to see if the renderbuffer came from an opaque framebuffer + // https://github.com/immersive-web/webxr/issues/862 + let target_matches = target == constants::RENDERBUFFER; + + let pname_matches = match pname { + constants::RENDERBUFFER_WIDTH | + constants::RENDERBUFFER_HEIGHT | + constants::RENDERBUFFER_INTERNAL_FORMAT | + constants::RENDERBUFFER_RED_SIZE | + constants::RENDERBUFFER_GREEN_SIZE | + constants::RENDERBUFFER_BLUE_SIZE | + constants::RENDERBUFFER_ALPHA_SIZE | + constants::RENDERBUFFER_DEPTH_SIZE | + constants::RENDERBUFFER_STENCIL_SIZE => true, + _ => false, + }; + + if !target_matches || !pname_matches { + self.webgl_error(InvalidEnum); + return NullValue(); + } + + if self.bound_renderbuffer.get().is_none() { + self.webgl_error(InvalidOperation); + return NullValue(); } + + let result = if pname == constants::RENDERBUFFER_INTERNAL_FORMAT { + let rb = self.bound_renderbuffer.get().unwrap(); + rb.internal_format() as i32 + } else { + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetRenderbufferParameter( + target, pname, sender, + )); + receiver.recv().unwrap() + }; + + Int32Value(result) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn GetProgramInfoLog(&self, program: Option<&WebGLProgram>) -> Option<DOMString> { - if let Some(program) = program { - match program.get_info_log() { - Ok(value) => Some(DOMString::from(value)), - Err(e) => { - self.webgl_error(e); - None - } - } - } else { - self.webgl_error(WebGLError::InvalidValue); - None + fn GetProgramInfoLog(&self, program: &WebGLProgram) -> Option<DOMString> { + handle_potential_webgl_error!(self, self.validate_ownership(program), return None); + match program.get_info_log() { + Ok(value) => Some(DOMString::from(value)), + Err(e) => { + self.webgl_error(e); + None + }, } } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - unsafe fn GetProgramParameter(&self, _: *mut JSContext, program: Option<&WebGLProgram>, param_id: u32) -> JSVal { - if let Some(program) = program { - match handle_potential_webgl_error!(self, program.parameter(param_id), WebGLParameter::Invalid) { - WebGLParameter::Int(val) => Int32Value(val), - WebGLParameter::Bool(val) => BooleanValue(val), - WebGLParameter::String(_) => panic!("Program parameter should not be string"), - WebGLParameter::Float(_) => panic!("Program parameter should not be float"), - WebGLParameter::FloatArray(_) => { - panic!("Program paramenter should not be float array") - } - WebGLParameter::Invalid => NullValue(), - } - } else { - NullValue() + fn GetProgramParameter(&self, _: SafeJSContext, program: &WebGLProgram, param: u32) -> JSVal { + handle_potential_webgl_error!(self, self.validate_ownership(program), return NullValue()); + if program.is_deleted() { + self.webgl_error(InvalidOperation); + return NullValue(); + } + match param { + constants::DELETE_STATUS => BooleanValue(program.is_marked_for_deletion()), + constants::LINK_STATUS => BooleanValue(program.is_linked()), + constants::VALIDATE_STATUS => { + // FIXME(nox): This could be cached on the DOM side when we call validateProgram + // but I'm not sure when the value should be reset. + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetProgramValidateStatus(program.id(), sender)); + BooleanValue(receiver.recv().unwrap()) + }, + constants::ATTACHED_SHADERS => { + // FIXME(nox): This allocates a vector and roots a couple of shaders for nothing. + Int32Value( + program + .attached_shaders() + .map(|shaders| shaders.len() as i32) + .unwrap_or(0), + ) + }, + constants::ACTIVE_ATTRIBUTES => Int32Value(program.active_attribs().len() as i32), + constants::ACTIVE_UNIFORMS => Int32Value(program.active_uniforms().len() as i32), + _ => { + self.webgl_error(InvalidEnum); + NullValue() + }, } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn GetShaderInfoLog(&self, shader: Option<&WebGLShader>) -> Option<DOMString> { - shader.and_then(|s| s.info_log()).map(DOMString::from) + fn GetShaderInfoLog(&self, shader: &WebGLShader) -> Option<DOMString> { + handle_potential_webgl_error!(self, self.validate_ownership(shader), return None); + Some(shader.info_log()) } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - unsafe fn GetShaderParameter(&self, _: *mut JSContext, shader: Option<&WebGLShader>, param_id: u32) -> JSVal { - if let Some(shader) = shader { - match handle_potential_webgl_error!(self, shader.parameter(param_id), WebGLParameter::Invalid) { - WebGLParameter::Int(val) => Int32Value(val), - WebGLParameter::Bool(val) => BooleanValue(val), - WebGLParameter::String(_) => panic!("Shader parameter should not be string"), - WebGLParameter::Float(_) => panic!("Shader parameter should not be float"), - WebGLParameter::FloatArray(_) => { - panic!("Shader paramenter should not be float array") - } - WebGLParameter::Invalid => NullValue(), - } - } else { - NullValue() + fn GetShaderParameter(&self, _: SafeJSContext, shader: &WebGLShader, param: u32) -> JSVal { + handle_potential_webgl_error!(self, self.validate_ownership(shader), return NullValue()); + if shader.is_deleted() { + self.webgl_error(InvalidValue); + return NullValue(); + } + match param { + constants::DELETE_STATUS => BooleanValue(shader.is_marked_for_deletion()), + constants::COMPILE_STATUS => BooleanValue(shader.successfully_compiled()), + constants::SHADER_TYPE => UInt32Value(shader.gl_type()), + _ => { + self.webgl_error(InvalidEnum); + NullValue() + }, } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn GetShaderPrecisionFormat(&self, - shader_type: u32, - precision_type: u32) - -> Option<Root<WebGLShaderPrecisionFormat>> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::GetShaderPrecisionFormat(shader_type, - precision_type, - sender))) - .unwrap(); - match receiver.recv().unwrap() { - Ok((range_min, range_max, precision)) => { - Some(WebGLShaderPrecisionFormat::new(self.global().as_window(), range_min, range_max, precision)) - }, - Err(error) => { - self.webgl_error(error); - None - } + fn GetShaderPrecisionFormat( + &self, + shader_type: u32, + precision_type: u32, + ) -> Option<DomRoot<WebGLShaderPrecisionFormat>> { + match shader_type { + constants::FRAGMENT_SHADER | constants::VERTEX_SHADER => (), + _ => { + self.webgl_error(InvalidEnum); + return None; + }, } + + match precision_type { + constants::LOW_FLOAT | + constants::MEDIUM_FLOAT | + constants::HIGH_FLOAT | + constants::LOW_INT | + constants::MEDIUM_INT | + constants::HIGH_INT => (), + _ => { + self.webgl_error(InvalidEnum); + return None; + }, + } + + let (sender, receiver) = webgl_channel().unwrap(); + self.send_command(WebGLCommand::GetShaderPrecisionFormat( + shader_type, + precision_type, + sender, + )); + + let (range_min, range_max, precision) = receiver.recv().unwrap(); + Some(WebGLShaderPrecisionFormat::new( + self.global().as_window(), + range_min, + range_max, + precision, + )) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn GetUniformLocation(&self, - program: Option<&WebGLProgram>, - name: DOMString) -> Option<Root<WebGLUniformLocation>> { - program.and_then(|p| { - handle_potential_webgl_error!(self, p.get_uniform_location(name), None) - .map(|location| WebGLUniformLocation::new(self.global().as_window(), location, p.id())) - }) + fn GetUniformLocation( + &self, + program: &WebGLProgram, + name: DOMString, + ) -> Option<DomRoot<WebGLUniformLocation>> { + handle_potential_webgl_error!(self, self.validate_ownership(program), return None); + handle_potential_webgl_error!(self, program.get_uniform_location(name), None) } #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - unsafe fn GetVertexAttrib(&self, cx: *mut JSContext, index: u32, pname: u32) -> JSVal { - if index == 0 && pname == constants::CURRENT_VERTEX_ATTRIB { - rooted!(in(cx) let mut result = UndefinedValue()); - let (x, y, z, w) = self.current_vertex_attrib_0.get(); - let attrib = vec![x, y, z, w]; - attrib.to_jsval(cx, result.handle_mut()); - return result.get() - } - - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::GetVertexAttrib(index, pname, sender))).unwrap(); - - match handle_potential_webgl_error!(self, receiver.recv().unwrap(), WebGLParameter::Invalid) { - WebGLParameter::Int(val) => Int32Value(val), - WebGLParameter::Bool(val) => BooleanValue(val), - WebGLParameter::String(_) => panic!("Vertex attrib should not be string"), - WebGLParameter::Float(_) => panic!("Vertex attrib should not be float"), - WebGLParameter::FloatArray(val) => { - rooted!(in(cx) let mut result = UndefinedValue()); - val.to_jsval(cx, result.handle_mut()); - result.get() + fn GetVertexAttrib(&self, cx: SafeJSContext, index: u32, param: u32) -> JSVal { + let get_attrib = |data: Ref<VertexAttribData>| -> JSVal { + if param == constants::CURRENT_VERTEX_ATTRIB { + let attrib = self.current_vertex_attribs.borrow()[index as usize]; + match attrib { + VertexAttrib::Float(x, y, z, w) => { + let value = [x, y, z, w]; + unsafe { + rooted!(in(*cx) let mut result = ptr::null_mut::<JSObject>()); + let _ = Float32Array::create( + *cx, + CreateWith::Slice(&value), + result.handle_mut(), + ) + .unwrap(); + return ObjectValue(result.get()); + } + }, + VertexAttrib::Int(x, y, z, w) => { + let value = [x, y, z, w]; + unsafe { + rooted!(in(*cx) let mut result = ptr::null_mut::<JSObject>()); + let _ = Int32Array::create( + *cx, + CreateWith::Slice(&value), + result.handle_mut(), + ) + .unwrap(); + return ObjectValue(result.get()); + } + }, + VertexAttrib::Uint(x, y, z, w) => { + let value = [x, y, z, w]; + unsafe { + rooted!(in(*cx) let mut result = ptr::null_mut::<JSObject>()); + let _ = Uint32Array::create( + *cx, + CreateWith::Slice(&value), + result.handle_mut(), + ) + .unwrap(); + return ObjectValue(result.get()); + } + }, + }; + } + if !self + .extension_manager + .is_get_vertex_attrib_name_enabled(param) + { + self.webgl_error(WebGLError::InvalidEnum); + return NullValue(); } - WebGLParameter::Invalid => NullValue(), + + match param { + constants::VERTEX_ATTRIB_ARRAY_ENABLED => BooleanValue(data.enabled_as_array), + constants::VERTEX_ATTRIB_ARRAY_SIZE => Int32Value(data.size as i32), + constants::VERTEX_ATTRIB_ARRAY_TYPE => Int32Value(data.type_ as i32), + constants::VERTEX_ATTRIB_ARRAY_NORMALIZED => BooleanValue(data.normalized), + constants::VERTEX_ATTRIB_ARRAY_STRIDE => Int32Value(data.stride as i32), + constants::VERTEX_ATTRIB_ARRAY_BUFFER_BINDING => unsafe { + rooted!(in(*cx) let mut jsval = NullValue()); + if let Some(buffer) = data.buffer() { + buffer.to_jsval(*cx, jsval.handle_mut()); + } + jsval.get() + }, + ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE => { + UInt32Value(data.divisor) + }, + _ => { + self.webgl_error(InvalidEnum); + NullValue() + }, + } + }; + + match self.webgl_version() { + WebGLVersion::WebGL1 => { + let current_vao = self.current_vao(); + let data = handle_potential_webgl_error!( + self, + current_vao.get_vertex_attrib(index).ok_or(InvalidValue), + return NullValue() + ); + get_attrib(data) + }, + WebGLVersion::WebGL2 => { + let current_vao = self.current_vao_webgl2(); + let data = handle_potential_webgl_error!( + self, + current_vao.get_vertex_attrib(index).ok_or(InvalidValue), + return NullValue() + ); + get_attrib(data) + }, + } + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn GetVertexAttribOffset(&self, index: u32, pname: u32) -> i64 { + if pname != constants::VERTEX_ATTRIB_ARRAY_POINTER { + self.webgl_error(InvalidEnum); + return 0; + } + match self.webgl_version() { + WebGLVersion::WebGL1 => { + let current_vao = self.current_vao(); + let data = handle_potential_webgl_error!( + self, + current_vao.get_vertex_attrib(index).ok_or(InvalidValue), + return 0 + ); + data.offset as i64 + }, + WebGLVersion::WebGL2 => { + let current_vao = self.current_vao_webgl2(); + let data = handle_potential_webgl_error!( + self, + current_vao.get_vertex_attrib(index).ok_or(InvalidValue), + return 0 + ); + data.offset as i64 + }, } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn Hint(&self, target: u32, mode: u32) { - if target != constants::GENERATE_MIPMAP_HINT { + if target != constants::GENERATE_MIPMAP_HINT && + !self.extension_manager.is_hint_target_enabled(target) + { return self.webgl_error(InvalidEnum); } match mode { - constants::FASTEST | - constants::NICEST | - constants::DONT_CARE => (), + constants::FASTEST | constants::NICEST | constants::DONT_CARE => (), _ => return self.webgl_error(InvalidEnum), } - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Hint(target, mode))) - .unwrap() + self.send_command(WebGLCommand::Hint(target, mode)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 fn IsBuffer(&self, buffer: Option<&WebGLBuffer>) -> bool { - buffer.map_or(false, |buf| buf.target().is_some() && !buf.is_deleted()) + buffer.map_or(false, |buf| { + self.validate_ownership(buf).is_ok() && buf.target().is_some() && !buf.is_deleted() + }) } - // TODO: We could write this without IPC, recording the calls to `enable` and `disable`. // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn IsEnabled(&self, cap: u32) -> bool { - if self.validate_feature_enum(cap) { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::IsEnabled(cap, sender))) - .unwrap(); - return receiver.recv().unwrap(); - } - - false + handle_potential_webgl_error!(self, self.capabilities.is_enabled(cap), false) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 fn IsFramebuffer(&self, frame_buffer: Option<&WebGLFramebuffer>) -> bool { - frame_buffer.map_or(false, |buf| buf.target().is_some() && !buf.is_deleted()) + frame_buffer.map_or(false, |buf| { + self.validate_ownership(buf).is_ok() && buf.target().is_some() && !buf.is_deleted() + }) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn IsProgram(&self, program: Option<&WebGLProgram>) -> bool { - program.map_or(false, |p| !p.is_deleted()) + program.map_or(false, |p| { + self.validate_ownership(p).is_ok() && !p.is_deleted() + }) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 fn IsRenderbuffer(&self, render_buffer: Option<&WebGLRenderbuffer>) -> bool { - render_buffer.map_or(false, |buf| buf.ever_bound() && !buf.is_deleted()) + render_buffer.map_or(false, |buf| { + self.validate_ownership(buf).is_ok() && buf.ever_bound() && !buf.is_deleted() + }) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn IsShader(&self, shader: Option<&WebGLShader>) -> bool { - shader.map_or(false, |s| !s.is_deleted() || s.is_attached()) + shader.map_or(false, |s| { + self.validate_ownership(s).is_ok() && !s.is_deleted() + }) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 fn IsTexture(&self, texture: Option<&WebGLTexture>) -> bool { - texture.map_or(false, |tex| tex.target().is_some() && !tex.is_deleted()) + texture.map_or(false, |tex| { + self.validate_ownership(tex).is_ok() && tex.target().is_some() && !tex.is_invalid() + }) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 @@ -2031,9 +3573,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return self.webgl_error(InvalidValue); } - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::LineWidth(width))) - .unwrap() + self.send_command(WebGLCommand::LineWidth(width)) } // NOTE: Usage of this function could affect rendering while we keep using @@ -2043,196 +3583,184 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { let mut texture_settings = self.texture_unpacking_settings.get(); match param_name { constants::UNPACK_FLIP_Y_WEBGL => { - if param_value != 0 { - texture_settings.insert(FLIP_Y_AXIS) - } else { - texture_settings.remove(FLIP_Y_AXIS) - } - - self.texture_unpacking_settings.set(texture_settings); - return; + texture_settings.set(TextureUnpacking::FLIP_Y_AXIS, param_value != 0); }, constants::UNPACK_PREMULTIPLY_ALPHA_WEBGL => { - if param_value != 0 { - texture_settings.insert(PREMULTIPLY_ALPHA) - } else { - texture_settings.remove(PREMULTIPLY_ALPHA) - } - - self.texture_unpacking_settings.set(texture_settings); - return; + texture_settings.set(TextureUnpacking::PREMULTIPLY_ALPHA, param_value != 0); }, constants::UNPACK_COLORSPACE_CONVERSION_WEBGL => { - match param_value as u32 { - constants::BROWSER_DEFAULT_WEBGL - => texture_settings.insert(CONVERT_COLORSPACE), - constants::NONE - => texture_settings.remove(CONVERT_COLORSPACE), + let convert = match param_value as u32 { + constants::BROWSER_DEFAULT_WEBGL => true, + constants::NONE => false, _ => return self.webgl_error(InvalidEnum), + }; + texture_settings.set(TextureUnpacking::CONVERT_COLORSPACE, convert); + }, + constants::UNPACK_ALIGNMENT => { + match param_value { + 1 | 2 | 4 | 8 => (), + _ => return self.webgl_error(InvalidValue), } - - self.texture_unpacking_settings.set(texture_settings); + self.texture_unpacking_alignment.set(param_value as u32); return; }, - constants::UNPACK_ALIGNMENT | constants::PACK_ALIGNMENT => { match param_value { 1 | 2 | 4 | 8 => (), _ => return self.webgl_error(InvalidValue), } - self.texture_unpacking_alignment.set(param_value as u32); + // We never actually change the actual value on the GL side + // because it's better to receive the pixels without the padding + // and then write the result at the right place in ReadPixels. + self.texture_packing_alignment.set(param_value as u8); return; }, _ => return self.webgl_error(InvalidEnum), } + self.texture_unpacking_settings.set(texture_settings); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn PolygonOffset(&self, factor: f32, units: f32) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::PolygonOffset(factor, units))) - .unwrap() + self.send_command(WebGLCommand::PolygonOffset(factor, units)) } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12 - unsafe fn ReadPixels(&self, cx: *mut JSContext, x: i32, y: i32, width: i32, height: i32, - format: u32, pixel_type: u32, pixels: *mut JSObject) -> Fallible<()> { - if pixels.is_null() { - return Ok(self.webgl_error(InvalidValue)); - } - - typedarray!(in(cx) let mut pixels_data: ArrayBufferView = pixels); - let (array_type, mut data) = match { pixels_data.as_mut() } { - Ok(data) => (data.get_array_type(), data.as_mut_slice()), - Err(_) => return Err(Error::Type("Not an ArrayBufferView".to_owned())), - }; - - if !self.validate_framebuffer_complete() { - return Ok(()); - } + #[allow(unsafe_code)] + fn ReadPixels( + &self, + x: i32, + y: i32, + width: i32, + height: i32, + format: u32, + pixel_type: u32, + mut pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>, + ) { + handle_potential_webgl_error!(self, self.validate_framebuffer(), return); + + let pixels = + handle_potential_webgl_error!(self, pixels.as_mut().ok_or(InvalidValue), return); - match array_type { - Type::Uint8 => (), - _ => return Ok(self.webgl_error(InvalidOperation)), + if width < 0 || height < 0 { + return self.webgl_error(InvalidValue); } - // From the WebGL specification, 5.14.12 Reading back pixels - // - // "Only two combinations of format and type are - // accepted. The first is format RGBA and type - // UNSIGNED_BYTE. The second is an implementation-chosen - // format. The values of format and type for this format - // may be determined by calling getParameter with the - // symbolic constants IMPLEMENTATION_COLOR_READ_FORMAT - // and IMPLEMENTATION_COLOR_READ_TYPE, respectively. The - // implementation-chosen format may vary depending on the - // format of the currently bound rendering - // surface. Unsupported combinations of format and type - // will generate an INVALID_OPERATION error." - // - // To avoid having to support general format packing math, we - // always report RGBA/UNSIGNED_BYTE as our only supported - // format. if format != constants::RGBA || pixel_type != constants::UNSIGNED_BYTE { - return Ok(self.webgl_error(InvalidOperation)); + return self.webgl_error(InvalidOperation); } - let cpp = 4; - - // "If pixels is non-null, but is not large enough to - // retrieve all of the pixels in the specified rectangle - // taking into account pixel store modes, an - // INVALID_OPERATION error is generated." - let stride = match width.checked_mul(cpp) { - Some(stride) => stride, - _ => return Ok(self.webgl_error(InvalidOperation)), - }; - match height.checked_mul(stride) { - Some(size) if size <= data.len() as i32 => {} - _ => return Ok(self.webgl_error(InvalidOperation)), + if pixels.get_array_type() != Type::Uint8 { + return self.webgl_error(InvalidOperation); } - // "For any pixel lying outside the frame buffer, the - // corresponding destination buffer range remains - // untouched; see Reading Pixels Outside the - // Framebuffer." - let mut x = x; - let mut y = y; - let mut width = width; - let mut height = height; - let mut dst_offset = 0; + let (fb_width, fb_height) = handle_potential_webgl_error!( + self, + self.get_current_framebuffer_size().ok_or(InvalidOperation), + return + ); - if x < 0 { - dst_offset += cpp * -x; - width += x; - x = 0; + if width == 0 || height == 0 { + return; } - if y < 0 { - dst_offset += stride * -y; - height += y; - y = 0; - } + let bytes_per_pixel = 4; - if width < 0 || height < 0 { - return Ok(self.webgl_error(InvalidValue)); - } + let row_len = handle_potential_webgl_error!( + self, + width.checked_mul(bytes_per_pixel).ok_or(InvalidOperation), + return + ); - match self.get_current_framebuffer_size() { - Some((fb_width, fb_height)) => { - if x + width > fb_width { - width = fb_width - x; - } - if y + height > fb_height { - height = fb_height - y; - } - } - _ => return Ok(self.webgl_error(InvalidOperation)), + let pack_alignment = self.texture_packing_alignment.get() as i32; + let dest_padding = match row_len % pack_alignment { + 0 => 0, + remainder => pack_alignment - remainder, }; + let dest_stride = row_len + dest_padding; + + let full_rows_len = handle_potential_webgl_error!( + self, + dest_stride.checked_mul(height - 1).ok_or(InvalidOperation), + return + ); + let required_dest_len = handle_potential_webgl_error!( + self, + full_rows_len.checked_add(row_len).ok_or(InvalidOperation), + return + ); + + let dest = unsafe { pixels.as_mut_slice() }; + if dest.len() < required_dest_len as usize { + return self.webgl_error(InvalidOperation); + } - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::ReadPixels(x, y, width, height, format, pixel_type, sender))) - .unwrap(); + let src_origin = Point2D::new(x, y); + let src_size = Size2D::new(width as u32, height as u32); + let fb_size = Size2D::new(fb_width as u32, fb_height as u32); + let src_rect = match pixels::clip(src_origin, src_size.to_u64(), fb_size.to_u64()) { + Some(rect) => rect, + None => return, + }; - let result = receiver.recv().unwrap(); + // Note: we're casting a Rect<u64> back into a Rect<u32> here, but it's okay because + // it used u32 data types to begin with. It just got converted to Rect<u64> in + // pixels::clip + let src_rect = src_rect.to_u32(); - for i in 0..height { - for j in 0..(width * cpp) { - data[(dst_offset + i * stride + j) as usize] = - result[(i * width * cpp + j) as usize]; - } + let mut dest_offset = 0; + if x < 0 { + dest_offset += -x * bytes_per_pixel; + } + if y < 0 { + dest_offset += -y * row_len; } - Ok(()) + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.send_command(WebGLCommand::ReadPixels( + src_rect, format, pixel_type, sender, + )); + let src = receiver.recv().unwrap(); + + let src_row_len = src_rect.size.width as usize * bytes_per_pixel as usize; + for i in 0..src_rect.size.height { + let dest_start = dest_offset as usize + i as usize * dest_stride as usize; + let dest_end = dest_start + src_row_len; + let src_start = i as usize * src_row_len; + let src_end = src_start + src_row_len; + dest[dest_start..dest_end].copy_from_slice(&src[src_start..src_end]); + } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn SampleCoverage(&self, value: f32, invert: bool) { - self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::SampleCoverage(value, invert))).unwrap(); + self.send_command(WebGLCommand::SampleCoverage(value, invert)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4 fn Scissor(&self, x: i32, y: i32, width: i32, height: i32) { if width < 0 || height < 0 { - return self.webgl_error(InvalidValue) + return self.webgl_error(InvalidValue); } + let width = width as u32; + let height = height as u32; + self.current_scissor.set((x, y, width, height)); - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Scissor(x, y, width, height))) - .unwrap() + self.send_command(WebGLCommand::Scissor(x, y, width, height)); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn StencilFunc(&self, func: u32, ref_: i32, mask: u32) { match func { - constants::NEVER | constants::LESS | constants::EQUAL | constants::LEQUAL | - constants::GREATER | constants::NOTEQUAL | constants::GEQUAL | constants::ALWAYS => - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::StencilFunc(func, ref_, mask))) - .unwrap(), + constants::NEVER | + constants::LESS | + constants::EQUAL | + constants::LEQUAL | + constants::GREATER | + constants::NOTEQUAL | + constants::GEQUAL | + constants::ALWAYS => self.send_command(WebGLCommand::StencilFunc(func, ref_, mask)), _ => self.webgl_error(InvalidEnum), } } @@ -2245,40 +3773,42 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } match func { - constants::NEVER | constants::LESS | constants::EQUAL | constants::LEQUAL | - constants::GREATER | constants::NOTEQUAL | constants::GEQUAL | constants::ALWAYS => - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::StencilFuncSeparate(face, func, ref_, mask))) - .unwrap(), + constants::NEVER | + constants::LESS | + constants::EQUAL | + constants::LEQUAL | + constants::GREATER | + constants::NOTEQUAL | + constants::GEQUAL | + constants::ALWAYS => { + self.send_command(WebGLCommand::StencilFuncSeparate(face, func, ref_, mask)) + }, _ => self.webgl_error(InvalidEnum), } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn StencilMask(&self, mask: u32) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::StencilMask(mask))) - .unwrap() + self.send_command(WebGLCommand::StencilMask(mask)) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn StencilMaskSeparate(&self, face: u32, mask: u32) { match face { - constants::FRONT | constants::BACK | constants::FRONT_AND_BACK => - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::StencilMaskSeparate(face, mask))) - .unwrap(), + constants::FRONT | constants::BACK | constants::FRONT_AND_BACK => { + self.send_command(WebGLCommand::StencilMaskSeparate(face, mask)) + }, _ => return self.webgl_error(InvalidEnum), - } + }; } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3 fn StencilOp(&self, fail: u32, zfail: u32, zpass: u32) { - if self.validate_stencil_actions(fail) && self.validate_stencil_actions(zfail) && - self.validate_stencil_actions(zpass) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::StencilOp(fail, zfail, zpass))) - .unwrap() + if self.validate_stencil_actions(fail) && + self.validate_stencil_actions(zfail) && + self.validate_stencil_actions(zpass) + { + self.send_command(WebGLCommand::StencilOp(fail, zfail, zpass)); } else { self.webgl_error(InvalidEnum) } @@ -2291,368 +3821,316 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { _ => return self.webgl_error(InvalidEnum), } - if self.validate_stencil_actions(fail) && self.validate_stencil_actions(zfail) && - self.validate_stencil_actions(zpass) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::StencilOpSeparate(face, fail, zfail, zpass))) - .unwrap() + if self.validate_stencil_actions(fail) && + self.validate_stencil_actions(zfail) && + self.validate_stencil_actions(zpass) + { + self.send_command(WebGLCommand::StencilOpSeparate(face, fail, zfail, zpass)) } else { self.webgl_error(InvalidEnum) } } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn LinkProgram(&self, program: Option<&WebGLProgram>) { - if let Some(program) = program { - if let Err(e) = program.link() { - self.webgl_error(e); - } + fn LinkProgram(&self, program: &WebGLProgram) { + handle_potential_webgl_error!(self, self.validate_ownership(program), return); + if program.is_deleted() { + return self.webgl_error(InvalidValue); } + handle_potential_webgl_error!(self, program.link()); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn ShaderSource(&self, shader: Option<&WebGLShader>, source: DOMString) { - if let Some(shader) = shader { - shader.set_source(source) - } + fn ShaderSource(&self, shader: &WebGLShader, source: DOMString) { + handle_potential_webgl_error!(self, self.validate_ownership(shader), return); + shader.set_source(source) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn GetShaderSource(&self, shader: Option<&WebGLShader>) -> Option<DOMString> { - shader.and_then(|s| s.source()) + fn GetShaderSource(&self, shader: &WebGLShader) -> Option<DOMString> { + handle_potential_webgl_error!(self, self.validate_ownership(shader), return None); + Some(shader.source()) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn Uniform1f(&self, - uniform: Option<&WebGLUniformLocation>, - val: f32) { - if self.validate_uniform_parameters(uniform, UniformSetterType::Float, &[val]) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform1f(uniform.unwrap().id(), val))) - .unwrap() - } + fn Uniform1f(&self, location: Option<&WebGLUniformLocation>, val: f32) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL | constants::FLOAT => {}, + _ => return Err(InvalidOperation), + } + self.send_command(WebGLCommand::Uniform1f(location.id(), val)); + Ok(()) + }); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn Uniform1i(&self, - uniform: Option<&WebGLUniformLocation>, - val: i32) { - if self.validate_uniform_parameters(uniform, UniformSetterType::Int, &[val]) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform1i(uniform.unwrap().id(), val))) - .unwrap() - } + fn Uniform1i(&self, location: Option<&WebGLUniformLocation>, val: i32) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL | constants::INT => {}, + constants::SAMPLER_2D | constants::SAMPLER_CUBE => { + if val < 0 || val as u32 >= self.limits.max_combined_texture_image_units { + return Err(InvalidValue); + } + }, + _ => return Err(InvalidOperation), + } + self.send_command(WebGLCommand::Uniform1i(location.id(), val)); + Ok(()) + }); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn Uniform1iv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Int32>(cx, data, ConversionBehavior::Default)); - - if self.validate_uniform_parameters(uniform, UniformSetterType::Int, &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform1iv(uniform.unwrap().id(), data_vec))) - .unwrap() - } - - Ok(()) + fn Uniform1iv(&self, location: Option<&WebGLUniformLocation>, val: Int32ArrayOrLongSequence) { + self.uniform1iv(location, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn Uniform1fv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - - if self.validate_uniform_parameters(uniform, UniformSetterType::Float, &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform1fv(uniform.unwrap().id(), data_vec))) - .unwrap() - } - - Ok(()) + fn Uniform1fv( + &self, + location: Option<&WebGLUniformLocation>, + val: Float32ArrayOrUnrestrictedFloatSequence, + ) { + self.uniform1fv(location, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn Uniform2f(&self, - uniform: Option<&WebGLUniformLocation>, - x: f32, y: f32) { - if self.validate_uniform_parameters(uniform, UniformSetterType::FloatVec2, &[x, y]) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform2f(uniform.unwrap().id(), x, y))) - .unwrap() - } + fn Uniform2f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC2 | constants::FLOAT_VEC2 => {}, + _ => return Err(InvalidOperation), + } + self.send_command(WebGLCommand::Uniform2f(location.id(), x, y)); + Ok(()) + }); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn Uniform2fv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - - if self.validate_uniform_parameters(uniform, - UniformSetterType::FloatVec2, - &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform2fv(uniform.unwrap().id(), data_vec))) - .unwrap() - } - - Ok(()) + fn Uniform2fv( + &self, + location: Option<&WebGLUniformLocation>, + val: Float32ArrayOrUnrestrictedFloatSequence, + ) { + self.uniform2fv(location, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn Uniform2i(&self, - uniform: Option<&WebGLUniformLocation>, - x: i32, y: i32) { - if self.validate_uniform_parameters(uniform, - UniformSetterType::IntVec2, - &[x, y]) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform2i(uniform.unwrap().id(), x, y))) - .unwrap() - } + fn Uniform2i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC2 | constants::INT_VEC2 => {}, + _ => return Err(InvalidOperation), + } + self.send_command(WebGLCommand::Uniform2i(location.id(), x, y)); + Ok(()) + }); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn Uniform2iv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Int32>(cx, data, ConversionBehavior::Default)); - - if self.validate_uniform_parameters(uniform, - UniformSetterType::IntVec2, - &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform2iv(uniform.unwrap().id(), data_vec))) - .unwrap() - } - - Ok(()) + fn Uniform2iv(&self, location: Option<&WebGLUniformLocation>, val: Int32ArrayOrLongSequence) { + self.uniform2iv(location, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn Uniform3f(&self, - uniform: Option<&WebGLUniformLocation>, - x: f32, y: f32, z: f32) { - if self.validate_uniform_parameters(uniform, - UniformSetterType::FloatVec3, - &[x, y, z]) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform3f(uniform.unwrap().id(), x, y, z))) - .unwrap() - } + fn Uniform3f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32, z: f32) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC3 | constants::FLOAT_VEC3 => {}, + _ => return Err(InvalidOperation), + } + self.send_command(WebGLCommand::Uniform3f(location.id(), x, y, z)); + Ok(()) + }); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn Uniform3fv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - - if self.validate_uniform_parameters(uniform, - UniformSetterType::FloatVec3, - &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform3fv(uniform.unwrap().id(), data_vec))) - .unwrap() - } - - Ok(()) + fn Uniform3fv( + &self, + location: Option<&WebGLUniformLocation>, + val: Float32ArrayOrUnrestrictedFloatSequence, + ) { + self.uniform3fv(location, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn Uniform3i(&self, - uniform: Option<&WebGLUniformLocation>, - x: i32, y: i32, z: i32) { - if self.validate_uniform_parameters(uniform, - UniformSetterType::IntVec3, - &[x, y, z]) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform3i(uniform.unwrap().id(), x, y, z))) - .unwrap() - } + fn Uniform3i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32, z: i32) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC3 | constants::INT_VEC3 => {}, + _ => return Err(InvalidOperation), + } + self.send_command(WebGLCommand::Uniform3i(location.id(), x, y, z)); + Ok(()) + }); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn Uniform3iv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Int32>(cx, data, ConversionBehavior::Default)); - - if self.validate_uniform_parameters(uniform, - UniformSetterType::IntVec3, - &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform3iv(uniform.unwrap().id(), data_vec))) - .unwrap() - } - - Ok(()) + fn Uniform3iv(&self, location: Option<&WebGLUniformLocation>, val: Int32ArrayOrLongSequence) { + self.uniform3iv(location, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn Uniform4i(&self, - uniform: Option<&WebGLUniformLocation>, - x: i32, y: i32, z: i32, w: i32) { - if self.validate_uniform_parameters(uniform, - UniformSetterType::IntVec4, - &[x, y, z, w]) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform4i(uniform.unwrap().id(), x, y, z, w))) - .unwrap() - } + fn Uniform4i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32, z: i32, w: i32) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC4 | constants::INT_VEC4 => {}, + _ => return Err(InvalidOperation), + } + self.send_command(WebGLCommand::Uniform4i(location.id(), x, y, z, w)); + Ok(()) + }); } - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn Uniform4iv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Int32>(cx, data, ConversionBehavior::Default)); - - if self.validate_uniform_parameters(uniform, - UniformSetterType::IntVec4, - &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform4iv(uniform.unwrap().id(), data_vec))) - .unwrap() - } - - Ok(()) + fn Uniform4iv(&self, location: Option<&WebGLUniformLocation>, val: Int32ArrayOrLongSequence) { + self.uniform4iv(location, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn Uniform4f(&self, - uniform: Option<&WebGLUniformLocation>, - x: f32, y: f32, z: f32, w: f32) { - if self.validate_uniform_parameters(uniform, - UniformSetterType::FloatVec4, - &[x, y, z, w]) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform4f(uniform.unwrap().id(), x, y, z, w))) - .unwrap() - } + fn Uniform4f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32, z: f32, w: f32) { + self.with_location(location, |location| { + match location.type_() { + constants::BOOL_VEC4 | constants::FLOAT_VEC4 => {}, + _ => return Err(InvalidOperation), + } + self.send_command(WebGLCommand::Uniform4f(location.id(), x, y, z, w)); + Ok(()) + }); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn Uniform4fv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - - if self.validate_uniform_parameters(uniform, - UniformSetterType::FloatVec4, - &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Uniform4fv(uniform.unwrap().id(), data_vec))) - .unwrap() - } - - Ok(()) + fn Uniform4fv( + &self, + location: Option<&WebGLUniformLocation>, + val: Float32ArrayOrUnrestrictedFloatSequence, + ) { + self.uniform4fv(location, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn UniformMatrix2fv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - transpose: bool, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - if self.validate_uniform_parameters(uniform, - UniformSetterType::FloatMat2, - &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::UniformMatrix2fv(uniform.unwrap().id(), transpose, data_vec))) - .unwrap() - } - - Ok(()) + fn UniformMatrix2fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + ) { + self.uniform_matrix_2fv(location, transpose, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn UniformMatrix3fv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - transpose: bool, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - if self.validate_uniform_parameters(uniform, - UniformSetterType::FloatMat3, - &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::UniformMatrix3fv(uniform.unwrap().id(), transpose, data_vec))) - .unwrap() - } + fn UniformMatrix3fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + ) { + self.uniform_matrix_3fv(location, transpose, val, 0, 0) + } - Ok(()) + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 + fn UniformMatrix4fv( + &self, + location: Option<&WebGLUniformLocation>, + transpose: bool, + val: Float32ArrayOrUnrestrictedFloatSequence, + ) { + self.uniform_matrix_4fv(location, transpose, val, 0, 0) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 #[allow(unsafe_code)] - unsafe fn UniformMatrix4fv(&self, - cx: *mut JSContext, - uniform: Option<&WebGLUniformLocation>, - transpose: bool, - data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - if self.validate_uniform_parameters(uniform, - UniformSetterType::FloatMat4, - &data_vec) { - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::UniformMatrix4fv(uniform.unwrap().id(), transpose, data_vec))) - .unwrap() + fn GetUniform( + &self, + cx: SafeJSContext, + program: &WebGLProgram, + location: &WebGLUniformLocation, + ) -> JSVal { + handle_potential_webgl_error!( + self, + self.uniform_check_program(program, location), + return NullValue() + ); + + let triple = (self, program.id(), location.id()); + + match location.type_() { + constants::BOOL => BooleanValue(uniform_get(triple, WebGLCommand::GetUniformBool)), + constants::BOOL_VEC2 => unsafe { + rooted!(in(*cx) let mut rval = NullValue()); + uniform_get(triple, WebGLCommand::GetUniformBool2).to_jsval(*cx, rval.handle_mut()); + rval.get() + }, + constants::BOOL_VEC3 => unsafe { + rooted!(in(*cx) let mut rval = NullValue()); + uniform_get(triple, WebGLCommand::GetUniformBool3).to_jsval(*cx, rval.handle_mut()); + rval.get() + }, + constants::BOOL_VEC4 => unsafe { + rooted!(in(*cx) let mut rval = NullValue()); + uniform_get(triple, WebGLCommand::GetUniformBool4).to_jsval(*cx, rval.handle_mut()); + rval.get() + }, + constants::INT | constants::SAMPLER_2D | constants::SAMPLER_CUBE => { + Int32Value(uniform_get(triple, WebGLCommand::GetUniformInt)) + }, + constants::INT_VEC2 => unsafe { + uniform_typed::<Int32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformInt2)) + }, + constants::INT_VEC3 => unsafe { + uniform_typed::<Int32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformInt3)) + }, + constants::INT_VEC4 => unsafe { + uniform_typed::<Int32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformInt4)) + }, + constants::FLOAT => { + DoubleValue(uniform_get(triple, WebGLCommand::GetUniformFloat) as f64) + }, + constants::FLOAT_VEC2 => unsafe { + uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat2)) + }, + constants::FLOAT_VEC3 => unsafe { + uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat3)) + }, + constants::FLOAT_VEC4 | constants::FLOAT_MAT2 => unsafe { + uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat4)) + }, + constants::FLOAT_MAT3 => unsafe { + uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat9)) + }, + constants::FLOAT_MAT4 => unsafe { + uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat16)) + }, + _ => panic!("wrong uniform type"), } - - Ok(()) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 fn UseProgram(&self, program: Option<&WebGLProgram>) { if let Some(program) = program { - match program.use_program() { - Ok(()) => self.current_program.set(Some(program)), - Err(e) => self.webgl_error(e), + handle_potential_webgl_error!(self, self.validate_ownership(program), return); + if program.is_deleted() || !program.is_linked() { + return self.webgl_error(InvalidOperation); + } + if program.is_in_use() { + return; } + program.in_use(true); } + match self.current_program.get() { + Some(ref current) if program != Some(&**current) => current.in_use(false), + _ => {}, + } + self.send_command(WebGLCommand::UseProgram(program.map(|p| p.id()))); + self.current_program.set(program); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 - fn ValidateProgram(&self, program: Option<&WebGLProgram>) { - if let Some(program) = program { - if let Err(e) = program.validate() { - self.webgl_error(e); - } + fn ValidateProgram(&self, program: &WebGLProgram) { + handle_potential_webgl_error!(self, self.validate_ownership(program), return); + if let Err(e) = program.validate() { + self.webgl_error(e); } } @@ -2662,15 +4140,16 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn VertexAttrib1fv(&self, cx: *mut JSContext, indx: u32, data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - if data_vec.len() < 1 { - return Ok(self.webgl_error(InvalidOperation)); + fn VertexAttrib1fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) { + let values = match v { + Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => v.to_vec(), + Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, + }; + if values.len() < 1 { + // https://github.com/KhronosGroup/WebGL/issues/2700 + return self.webgl_error(InvalidValue); } - self.vertex_attrib(indx, data_vec[0], 0f32, 0f32, 1f32); - Ok(()) + self.vertex_attrib(indx, values[0], 0f32, 0f32, 1f32); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 @@ -2679,15 +4158,16 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn VertexAttrib2fv(&self, cx: *mut JSContext, indx: u32, data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - if data_vec.len() < 2 { - return Ok(self.webgl_error(InvalidOperation)); + fn VertexAttrib2fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) { + let values = match v { + Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => v.to_vec(), + Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, + }; + if values.len() < 2 { + // https://github.com/KhronosGroup/WebGL/issues/2700 + return self.webgl_error(InvalidValue); } - self.vertex_attrib(indx, data_vec[0], data_vec[1], 0f32, 1f32); - Ok(()) + self.vertex_attrib(indx, values[0], values[1], 0f32, 1f32); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 @@ -2696,15 +4176,16 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn VertexAttrib3fv(&self, cx: *mut JSContext, indx: u32, data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - if data_vec.len() < 3 { - return Ok(self.webgl_error(InvalidOperation)); + fn VertexAttrib3fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) { + let values = match v { + Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => v.to_vec(), + Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, + }; + if values.len() < 3 { + // https://github.com/KhronosGroup/WebGL/issues/2700 + return self.webgl_error(InvalidValue); } - self.vertex_attrib(indx, data_vec[0], data_vec[1], data_vec[2], 1f32); - Ok(()) + self.vertex_attrib(indx, values[0], values[1], values[2], 1f32); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 @@ -2713,92 +4194,77 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - #[allow(unsafe_code)] - unsafe fn VertexAttrib4fv(&self, cx: *mut JSContext, indx: u32, data: *mut JSObject) -> Fallible<()> { - assert!(!data.is_null()); - let data_vec = try!(typed_array_or_sequence_to_vec::<Float32>(cx, data, ())); - if data_vec.len() < 4 { - return Ok(self.webgl_error(InvalidOperation)); + fn VertexAttrib4fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) { + let values = match v { + Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => v.to_vec(), + Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v, + }; + if values.len() < 4 { + // https://github.com/KhronosGroup/WebGL/issues/2700 + return self.webgl_error(InvalidValue); } - - self.vertex_attrib(indx, data_vec[0], data_vec[1], data_vec[2], data_vec[3]); - Ok(()) + self.vertex_attrib(indx, values[0], values[1], values[2], values[3]); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 - fn VertexAttribPointer(&self, attrib_id: u32, size: i32, data_type: u32, - normalized: bool, stride: i32, offset: i64) { - if attrib_id > self.limits.max_vertex_attribs { - return self.webgl_error(InvalidValue); - } - - // GLES spec: If offset or stride is negative, an INVALID_VALUE error will be generated - // WebGL spec: the maximum supported stride is 255 - if stride < 0 || stride > 255 || offset < 0 { - return self.webgl_error(InvalidValue); - } - if size < 1 || size > 4 { - return self.webgl_error(InvalidValue); - } - if self.bound_buffer_array.get().is_none() { - return self.webgl_error(InvalidOperation); - } - - // stride and offset must be multiple of data_type - match data_type { - constants::BYTE | constants::UNSIGNED_BYTE => {}, - constants::SHORT | constants::UNSIGNED_SHORT => { - if offset % 2 > 0 || stride % 2 > 0 { - return self.webgl_error(InvalidOperation); - } - }, - constants::FLOAT => { - if offset % 4 > 0 || stride % 4 > 0 { - return self.webgl_error(InvalidOperation); - } - }, - _ => return self.webgl_error(InvalidEnum), - - } - - let msg = CanvasMsg::WebGL( - WebGLCommand::VertexAttribPointer(attrib_id, size, data_type, normalized, stride, offset as u32)); - self.ipc_renderer.send(msg).unwrap() + fn VertexAttribPointer( + &self, + index: u32, + size: i32, + type_: u32, + normalized: bool, + stride: i32, + offset: i64, + ) { + let res = match self.webgl_version() { + WebGLVersion::WebGL1 => self + .current_vao() + .vertex_attrib_pointer(index, size, type_, normalized, stride, offset), + WebGLVersion::WebGL2 => self + .current_vao_webgl2() + .vertex_attrib_pointer(index, size, type_, normalized, stride, offset), + }; + handle_potential_webgl_error!(self, res); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4 fn Viewport(&self, x: i32, y: i32, width: i32, height: i32) { if width < 0 || height < 0 { - return self.webgl_error(InvalidValue) + return self.webgl_error(InvalidValue); } - self.ipc_renderer - .send(CanvasMsg::WebGL(WebGLCommand::Viewport(x, y, width, height))) - .unwrap() + self.send_command(WebGLCommand::SetViewport(x, y, width, height)) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 #[allow(unsafe_code)] - unsafe fn TexImage2D(&self, - cx: *mut JSContext, - target: u32, - level: i32, - internal_format: u32, - width: i32, - height: i32, - border: i32, - format: u32, - data_type: u32, - data_ptr: *mut JSObject) -> Fallible<()> { - let data = if data_ptr.is_null() { - None - } else { - Some(try!(fallible_array_buffer_view_to_vec(cx, data_ptr))) - }; - - let validator = TexImage2DValidator::new(self, target, level, - internal_format, width, height, - border, format, data_type); + fn TexImage2D( + &self, + target: u32, + level: i32, + internal_format: i32, + width: i32, + height: i32, + border: i32, + format: u32, + data_type: u32, + pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>, + ) -> ErrorResult { + if !self.extension_manager.is_tex_type_enabled(data_type) { + return Ok(self.webgl_error(InvalidEnum)); + } + + let validator = TexImage2DValidator::new( + self, + target, + level, + internal_format as u32, + width, + height, + border, + format, + data_type, + ); let TexImage2DValidatorResult { texture, @@ -2807,6 +4273,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { height, level, border, + internal_format, format, data_type, } = match validator.validate() { @@ -2814,21 +4281,34 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Err(_) => return Ok(()), // NB: The validator sets the correct error for us. }; + if !internal_format.compatible_data_types().contains(&data_type) { + return Ok(self.webgl_error(InvalidOperation)); + } + if texture.is_immutable() { + return Ok(self.webgl_error(InvalidOperation)); + } + let unpacking_alignment = self.texture_unpacking_alignment.get(); - let expected_byte_length = - match { self.validate_tex_image_2d_data(width, height, - format, data_type, - unpacking_alignment, data_ptr, cx) } { - Ok(byte_length) => byte_length, - Err(()) => return Ok(()), - }; + let expected_byte_length = match { + self.validate_tex_image_2d_data( + width, + height, + format, + data_type, + unpacking_alignment, + pixels.as_ref(), + ) + } { + Ok(byte_length) => byte_length, + Err(()) => return Ok(()), + }; // If data is null, a buffer of sufficient size // initialized to 0 is passed. - let buff = match data { - None => vec![0u8; expected_byte_length as usize], - Some(data) => data, + let buff = match *pixels { + None => IpcSharedMemory::from_bytes(&vec![0u8; expected_byte_length as usize]), + Some(ref data) => IpcSharedMemory::from_bytes(unsafe { data.as_slice() }), }; // From the WebGL spec: @@ -2841,75 +4321,190 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return Ok(self.webgl_error(InvalidOperation)); } - self.tex_image_2d(texture, target, data_type, format, - level, width, height, border, unpacking_alignment, buff); + let size = Size2D::new(width, height); + + if !self.validate_filterable_texture( + &texture, + target, + level, + internal_format, + size, + data_type, + ) { + // FIXME(nox): What is the spec for this? No error is emitted ever + // by validate_filterable_texture. + return Ok(()); + } + + let size = Size2D::new(width, height); + + self.tex_image_2d( + &texture, + target, + data_type, + internal_format, + format, + level, + border, + unpacking_alignment, + size, + TexSource::Pixels(TexPixels::from_array(buff, size)), + ); Ok(()) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 - fn TexImage2D_(&self, - target: u32, - level: i32, - internal_format: u32, - format: u32, - data_type: u32, - source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) -> Fallible<()> { - // Get pixels from image source - let (pixels, size) = match self.get_image_pixels(source) { - Ok((pixels, size)) => (pixels, size), - Err(_) => return Ok(()), + fn TexImage2D_( + &self, + target: u32, + level: i32, + internal_format: i32, + format: u32, + data_type: u32, + source: TexImageSource, + ) -> ErrorResult { + if !self.extension_manager.is_tex_type_enabled(data_type) { + return Ok(self.webgl_error(InvalidEnum)); + } + + let pixels = match self.get_image_pixels(source)? { + Some(pixels) => pixels, + None => return Ok(()), }; - let validator = TexImage2DValidator::new(self, - target, level, internal_format, - size.width, size.height, - 0, format, data_type); + let validator = TexImage2DValidator::new( + self, + target, + level, + internal_format as u32, + pixels.size().width as i32, + pixels.size().height as i32, + 0, + format, + data_type, + ); let TexImage2DValidatorResult { texture, target, - width, - height, level, border, + internal_format, format, data_type, + .. } = match validator.validate() { Ok(result) => result, Err(_) => return Ok(()), // NB: The validator sets the correct error for us. }; - let pixels = self.rgba8_image_to_tex_image_data(format, data_type, pixels); + if !internal_format.compatible_data_types().contains(&data_type) { + return Ok(self.webgl_error(InvalidOperation)); + } + if texture.is_immutable() { + return Ok(self.webgl_error(InvalidOperation)); + } - self.tex_image_2d(texture, target, data_type, format, - level, width, height, border, 1, pixels); + if !self.validate_filterable_texture( + &texture, + target, + level, + internal_format, + pixels.size(), + data_type, + ) { + // FIXME(nox): What is the spec for this? No error is emitted ever + // by validate_filterable_texture. + return Ok(()); + } + + self.tex_image_2d( + &texture, + target, + data_type, + internal_format, + format, + level, + border, + 1, + pixels.size(), + TexSource::Pixels(pixels), + ); Ok(()) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 - #[allow(unsafe_code)] - unsafe fn TexSubImage2D(&self, - cx: *mut JSContext, - target: u32, - level: i32, - xoffset: i32, - yoffset: i32, - width: i32, - height: i32, - format: u32, - data_type: u32, - data_ptr: *mut JSObject) -> Fallible<()> { - let data = if data_ptr.is_null() { - None - } else { - Some(try!(fallible_array_buffer_view_to_vec(cx, data_ptr))) - }; + fn TexImageDOM( + &self, + target: u32, + level: i32, + internal_format: u32, + width: i32, + height: i32, + format: u32, + data_type: u32, + source: &HTMLIFrameElement, + ) -> ErrorResult { + // Currently DOMToTexture only supports TEXTURE_2D, RGBA, UNSIGNED_BYTE and no levels. + if target != constants::TEXTURE_2D || + level != 0 || + internal_format != constants::RGBA || + format != constants::RGBA || + data_type != constants::UNSIGNED_BYTE + { + return Ok(self.webgl_error(InvalidValue)); + } + // Get bound texture + let texture = handle_potential_webgl_error!( + self, + self.textures + .active_texture_slot(constants::TEXTURE_2D, self.webgl_version()) + .unwrap() + .get() + .ok_or(InvalidOperation), + return Ok(()) + ); + + let pipeline_id = source.pipeline_id().ok_or(Error::InvalidState)?; + let document_id = self + .global() + .downcast::<Window>() + .ok_or(Error::InvalidState)? + .webrender_document(); + + texture.set_attached_to_dom(); + + let command = DOMToTextureCommand::Attach( + self.webgl_sender.context_id(), + texture.id(), + document_id, + pipeline_id.to_webrender(), + Size2D::new(width, height), + ); + self.webgl_sender.send_dom_to_texture(command).unwrap(); - let validator = TexImage2DValidator::new(self, target, level, - format, width, height, - 0, format, data_type); + Ok(()) + } + + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 + #[allow(unsafe_code)] + fn TexSubImage2D( + &self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + width: i32, + height: i32, + format: u32, + data_type: u32, + pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>, + ) -> ErrorResult { + let validator = TexImage2DValidator::new( + self, target, level, format, width, height, 0, format, data_type, + ); let TexImage2DValidatorResult { texture, target, @@ -2926,21 +4521,29 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { let unpacking_alignment = self.texture_unpacking_alignment.get(); - let expected_byte_length = - match { self.validate_tex_image_2d_data(width, height, - format, data_type, - unpacking_alignment, data_ptr, cx) } { - Ok(byte_length) => byte_length, - Err(()) => return Ok(()), - }; - - // If data is null, a buffer of sufficient size - // initialized to 0 is passed. - let buff = match data { - None => vec![0u8; expected_byte_length as usize], - Some(data) => data, + let expected_byte_length = match { + self.validate_tex_image_2d_data( + width, + height, + format, + data_type, + unpacking_alignment, + pixels.as_ref(), + ) + } { + Ok(byte_length) => byte_length, + Err(()) => return Ok(()), }; + let buff = handle_potential_webgl_error!( + self, + pixels + .as_ref() + .map(|p| IpcSharedMemory::from_bytes(unsafe { p.as_slice() })) + .ok_or(InvalidValue), + return Ok(()) + ); + // From the WebGL spec: // // "If pixels is non-null but its size is less than what @@ -2951,34 +4554,50 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return Ok(self.webgl_error(InvalidOperation)); } - self.tex_sub_image_2d(texture, target, level, xoffset, yoffset, - width, height, format, data_type, unpacking_alignment, buff); + self.tex_sub_image_2d( + texture, + target, + level, + xoffset, + yoffset, + format, + data_type, + unpacking_alignment, + TexPixels::from_array(buff, Size2D::new(width, height)), + ); Ok(()) } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 - fn TexSubImage2D_(&self, - target: u32, - level: i32, - xoffset: i32, - yoffset: i32, - format: u32, - data_type: u32, - source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) - -> Fallible<()> { - let (pixels, size) = match self.get_image_pixels(source) { - Ok((pixels, size)) => (pixels, size), - Err(_) => return Ok(()), + fn TexSubImage2D_( + &self, + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + format: u32, + data_type: u32, + source: TexImageSource, + ) -> ErrorResult { + let pixels = match self.get_image_pixels(source)? { + Some(pixels) => pixels, + None => return Ok(()), }; - let validator = TexImage2DValidator::new(self, target, level, format, - size.width, size.height, - 0, format, data_type); + let validator = TexImage2DValidator::new( + self, + target, + level, + format, + pixels.size().width as i32, + pixels.size().height as i32, + 0, + format, + data_type, + ); let TexImage2DValidatorResult { texture, target, - width, - height, level, format, data_type, @@ -2988,10 +4607,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { Err(_) => return Ok(()), // NB: The validator sets the correct error for us. }; - let pixels = self.rgba8_image_to_tex_image_data(format, data_type, pixels); - - self.tex_sub_image_2d(texture, target, level, xoffset, yoffset, - width, height, format, data_type, 1, pixels); + self.tex_sub_image_2d( + texture, target, level, xoffset, yoffset, format, data_type, 1, pixels, + ); Ok(()) } @@ -3005,6 +4623,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { self.tex_parameter(target, name, TexParameterValue::Int(value)) } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 fn CheckFramebufferStatus(&self, target: u32) -> u32 { // From the GLES 2.0.25 spec, 4.4 ("Framebuffer Objects"): // @@ -3016,118 +4635,414 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { return 0; } - match self.bound_framebuffer.get() { + match self.bound_draw_framebuffer.get() { Some(fb) => return fb.check_status(), None => return constants::FRAMEBUFFER_COMPLETE, } } - fn RenderbufferStorage(&self, target: u32, internal_format: u32, - width: i32, height: i32) { - // From the GLES 2.0.25 spec: - // - // "target must be RENDERBUFFER." - if target != constants::RENDERBUFFER { - return self.webgl_error(InvalidOperation) - } + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7 + fn RenderbufferStorage(&self, target: u32, internal_format: u32, width: i32, height: i32) { + self.renderbuffer_storage(target, 0, internal_format, width, height) + } - // From the GLES 2.0.25 spec: - // - // "If either width or height is greater than the value of - // MAX_RENDERBUFFER_SIZE , the error INVALID_VALUE is - // generated." - // - // and we have to throw out negative-size values as well just - // like for TexImage. - // - // FIXME: Handle max_renderbuffer_size, which doesn't seem to - // be in limits. - if width < 0 || height < 0 { - return self.webgl_error(InvalidValue); + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn FramebufferRenderbuffer( + &self, + target: u32, + attachment: u32, + renderbuffertarget: u32, + rb: Option<&WebGLRenderbuffer>, + ) { + if let Some(rb) = rb { + handle_potential_webgl_error!(self, self.validate_ownership(rb), return); } - match self.bound_renderbuffer.get() { - Some(rb) => { - handle_potential_webgl_error!(self, rb.storage(internal_format, width, height)); - if let Some(fb) = self.bound_framebuffer.get() { - fb.invalidate_renderbuffer(&*rb); - } - } - None => self.webgl_error(InvalidOperation), - }; - - // FIXME: We need to clear the renderbuffer before it can be - // accessed. See https://github.com/servo/servo/issues/13710 - } - - fn FramebufferRenderbuffer(&self, target: u32, attachment: u32, - renderbuffertarget: u32, - rb: Option<&WebGLRenderbuffer>) { if target != constants::FRAMEBUFFER || renderbuffertarget != constants::RENDERBUFFER { return self.webgl_error(InvalidEnum); } - match self.bound_framebuffer.get() { + match self.bound_draw_framebuffer.get() { Some(fb) => handle_potential_webgl_error!(self, fb.renderbuffer(attachment, rb)), None => self.webgl_error(InvalidOperation), }; } - fn FramebufferTexture2D(&self, target: u32, attachment: u32, - textarget: u32, texture: Option<&WebGLTexture>, - level: i32) { + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 + fn FramebufferTexture2D( + &self, + target: u32, + attachment: u32, + textarget: u32, + texture: Option<&WebGLTexture>, + level: i32, + ) { + if let Some(texture) = texture { + handle_potential_webgl_error!(self, self.validate_ownership(texture), return); + } + if target != constants::FRAMEBUFFER { return self.webgl_error(InvalidEnum); } - match self.bound_framebuffer.get() { - Some(fb) => handle_potential_webgl_error!(self, fb.texture2d(attachment, textarget, texture, level)), + // From the GLES 2.0.25 spec, page 113: + // + // "level specifies the mipmap level of the texture image + // to be attached to the framebuffer and must be + // 0. Otherwise, INVALID_VALUE is generated." + if level != 0 { + return self.webgl_error(InvalidValue); + } + + match self.bound_draw_framebuffer.get() { + Some(fb) => handle_potential_webgl_error!( + self, + fb.texture2d(attachment, textarget, texture, level) + ), None => self.webgl_error(InvalidOperation), }; } + + /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9 + fn GetAttachedShaders(&self, program: &WebGLProgram) -> Option<Vec<DomRoot<WebGLShader>>> { + handle_potential_webgl_error!(self, self.validate_ownership(program), return None); + handle_potential_webgl_error!(self, program.attached_shaders().map(Some), None) + } + + /// https://immersive-web.github.io/webxr/#dom-webglrenderingcontextbase-makexrcompatible + fn MakeXRCompatible(&self) -> Rc<Promise> { + // XXXManishearth Fill in with compatibility checks when rust-webxr supports this + let p = Promise::new(&self.global()); + p.resolve_native(&()); + p + } } -pub trait LayoutCanvasWebGLRenderingContextHelpers { +impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, WebGLRenderingContext> { #[allow(unsafe_code)] - unsafe fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg>; + unsafe fn canvas_data_source(self) -> HTMLCanvasDataSource { + (*self.unsafe_get()).layout_handle() + } } -impl LayoutCanvasWebGLRenderingContextHelpers for LayoutJS<WebGLRenderingContext> { - #[allow(unsafe_code)] - unsafe fn get_ipc_renderer(&self) -> IpcSender<CanvasMsg> { - (*self.unsafe_get()).ipc_renderer.clone() +#[derive(Default, JSTraceable, MallocSizeOf)] +struct Capabilities { + value: Cell<CapFlags>, +} + +impl Capabilities { + fn set(&self, cap: u32, set: bool) -> WebGLResult<bool> { + let cap = CapFlags::from_enum(cap)?; + let mut value = self.value.get(); + if value.contains(cap) == set { + return Ok(false); + } + value.set(cap, set); + self.value.set(value); + Ok(true) + } + + fn is_enabled(&self, cap: u32) -> WebGLResult<bool> { + Ok(self.value.get().contains(CapFlags::from_enum(cap)?)) + } +} + +impl Default for CapFlags { + fn default() -> Self { + CapFlags::DITHER + } +} + +macro_rules! capabilities { + ($name:ident, $next:ident, $($rest:ident,)*) => { + capabilities!($name, $next, $($rest,)* [$name = 1;]); + }; + ($prev:ident, $name:ident, $($rest:ident,)* [$($tt:tt)*]) => { + capabilities!($name, $($rest,)* [$($tt)* $name = Self::$prev.bits << 1;]); + }; + ($prev:ident, [$($name:ident = $value:expr;)*]) => { + bitflags! { + #[derive(JSTraceable, MallocSizeOf)] + struct CapFlags: u16 { + $(const $name = $value;)* + } + } + + impl CapFlags { + fn from_enum(cap: u32) -> WebGLResult<Self> { + match cap { + $(constants::$name => Ok(Self::$name),)* + _ => Err(InvalidEnum), + } + } + } + }; +} + +capabilities! { + BLEND, + CULL_FACE, + DEPTH_TEST, + DITHER, + POLYGON_OFFSET_FILL, + SAMPLE_ALPHA_TO_COVERAGE, + SAMPLE_COVERAGE, + SCISSOR_TEST, + STENCIL_TEST, +} + +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +pub struct Textures { + active_unit: Cell<u32>, + units: Box<[TextureUnit]>, +} + +impl Textures { + fn new(max_combined_textures: u32) -> Self { + Self { + active_unit: Default::default(), + units: (0..max_combined_textures) + .map(|_| Default::default()) + .collect::<Vec<_>>() + .into(), + } + } + + pub fn active_unit_enum(&self) -> u32 { + self.active_unit.get() + constants::TEXTURE0 + } + + fn set_active_unit_enum(&self, index: u32) -> WebGLResult<()> { + if index < constants::TEXTURE0 || (index - constants::TEXTURE0) as usize > self.units.len() + { + return Err(InvalidEnum); + } + self.active_unit.set(index - constants::TEXTURE0); + Ok(()) + } + + pub fn active_texture_slot( + &self, + target: u32, + webgl_version: WebGLVersion, + ) -> WebGLResult<&MutNullableDom<WebGLTexture>> { + let active_unit = self.active_unit(); + let is_webgl2 = webgl_version == WebGLVersion::WebGL2; + match target { + constants::TEXTURE_2D => Ok(&active_unit.tex_2d), + constants::TEXTURE_CUBE_MAP => Ok(&active_unit.tex_cube_map), + WebGL2RenderingContextConstants::TEXTURE_2D_ARRAY if is_webgl2 => { + Ok(&active_unit.tex_2d_array) + }, + WebGL2RenderingContextConstants::TEXTURE_3D if is_webgl2 => Ok(&active_unit.tex_3d), + _ => Err(InvalidEnum), + } + } + + pub fn active_texture_for_image_target( + &self, + target: TexImageTarget, + ) -> Option<DomRoot<WebGLTexture>> { + let active_unit = self.active_unit(); + match target { + TexImageTarget::Texture2D => active_unit.tex_2d.get(), + TexImageTarget::Texture2DArray => active_unit.tex_2d_array.get(), + TexImageTarget::Texture3D => active_unit.tex_3d.get(), + TexImageTarget::CubeMap | + TexImageTarget::CubeMapPositiveX | + TexImageTarget::CubeMapNegativeX | + TexImageTarget::CubeMapPositiveY | + TexImageTarget::CubeMapNegativeY | + TexImageTarget::CubeMapPositiveZ | + TexImageTarget::CubeMapNegativeZ => active_unit.tex_cube_map.get(), + } + } + + fn active_unit(&self) -> &TextureUnit { + &self.units[self.active_unit.get() as usize] + } + + fn iter(&self) -> impl Iterator<Item = (u32, &TextureUnit)> { + self.units + .iter() + .enumerate() + .map(|(index, unit)| (index as u32 + constants::TEXTURE0, unit)) } } -#[derive(Debug, PartialEq)] -pub enum UniformSetterType { - Int, - IntVec2, - IntVec3, - IntVec4, - Float, - FloatVec2, - FloatVec3, - FloatVec4, - FloatMat2, - FloatMat3, - FloatMat4, +#[unrooted_must_root_lint::must_root] +#[derive(Default, JSTraceable, MallocSizeOf)] +struct TextureUnit { + tex_2d: MutNullableDom<WebGLTexture>, + tex_cube_map: MutNullableDom<WebGLTexture>, + tex_2d_array: MutNullableDom<WebGLTexture>, + tex_3d: MutNullableDom<WebGLTexture>, } -impl UniformSetterType { - pub fn element_count(&self) -> usize { - match *self { - UniformSetterType::Int => 1, - UniformSetterType::IntVec2 => 2, - UniformSetterType::IntVec3 => 3, - UniformSetterType::IntVec4 => 4, - UniformSetterType::Float => 1, - UniformSetterType::FloatVec2 => 2, - UniformSetterType::FloatVec3 => 3, - UniformSetterType::FloatVec4 => 4, - UniformSetterType::FloatMat2 => 4, - UniformSetterType::FloatMat3 => 9, - UniformSetterType::FloatMat4 => 16, +impl TextureUnit { + fn unbind(&self, texture: &WebGLTexture) -> Option<u32> { + let fields = [ + (&self.tex_2d, constants::TEXTURE_2D), + (&self.tex_cube_map, constants::TEXTURE_CUBE_MAP), + ( + &self.tex_2d_array, + WebGL2RenderingContextConstants::TEXTURE_2D_ARRAY, + ), + (&self.tex_3d, WebGL2RenderingContextConstants::TEXTURE_3D), + ]; + for &(slot, target) in &fields { + if slot.get().map_or(false, |t| texture == &*t) { + slot.set(None); + return Some(target); + } } + None + } +} + +pub struct TexPixels { + data: IpcSharedMemory, + size: Size2D<u32>, + pixel_format: Option<PixelFormat>, + premultiplied: bool, +} + +impl TexPixels { + fn new( + data: IpcSharedMemory, + size: Size2D<u32>, + pixel_format: PixelFormat, + premultiplied: bool, + ) -> Self { + Self { + data, + size, + pixel_format: Some(pixel_format), + premultiplied, + } + } + + pub fn from_array(data: IpcSharedMemory, size: Size2D<u32>) -> Self { + Self { + data, + size, + pixel_format: None, + premultiplied: false, + } + } + + pub fn size(&self) -> Size2D<u32> { + self.size + } +} + +pub enum TexSource { + Pixels(TexPixels), + BufferOffset(i64), +} + +#[derive(JSTraceable)] +pub struct WebGLCommandSender { + sender: WebGLChan, + waker: Option<Box<dyn EventLoopWaker>>, +} + +impl WebGLCommandSender { + pub fn new(sender: WebGLChan, waker: Option<Box<dyn EventLoopWaker>>) -> WebGLCommandSender { + WebGLCommandSender { sender, waker } + } + + pub fn send(&self, msg: WebGLMsg) -> WebGLSendResult { + let result = self.sender.send(msg); + if let Some(ref waker) = self.waker { + waker.wake(); + } + result + } +} + +#[derive(JSTraceable, MallocSizeOf)] +pub(crate) struct WebGLMessageSender { + sender: WebGLMsgSender, + #[ignore_malloc_size_of = "traits are cumbersome"] + waker: Option<Box<dyn EventLoopWaker>>, +} + +impl Clone for WebGLMessageSender { + fn clone(&self) -> WebGLMessageSender { + WebGLMessageSender { + sender: self.sender.clone(), + waker: self.waker.as_ref().map(|w| (*w).clone_box()), + } + } +} + +impl WebGLMessageSender { + fn wake_after_send<F: FnOnce() -> WebGLSendResult>(&self, f: F) -> WebGLSendResult { + let result = f(); + if let Some(ref waker) = self.waker { + waker.wake(); + } + result + } + + pub fn new( + sender: WebGLMsgSender, + waker: Option<Box<dyn EventLoopWaker>>, + ) -> WebGLMessageSender { + WebGLMessageSender { sender, waker } + } + + pub fn context_id(&self) -> WebGLContextId { + self.sender.context_id() + } + + pub fn send(&self, msg: WebGLCommand, backtrace: WebGLCommandBacktrace) -> WebGLSendResult { + self.wake_after_send(|| self.sender.send(msg, backtrace)) + } + + pub fn send_resize( + &self, + size: Size2D<u32>, + sender: WebGLSender<Result<(), String>>, + ) -> WebGLSendResult { + self.wake_after_send(|| self.sender.send_resize(size, sender)) + } + + pub fn send_remove(&self) -> WebGLSendResult { + self.wake_after_send(|| self.sender.send_remove()) + } + + pub fn send_dom_to_texture(&self, command: DOMToTextureCommand) -> WebGLSendResult { + self.wake_after_send(|| self.sender.send_dom_to_texture(command)) + } +} + +pub trait Size2DExt { + fn to_u64(&self) -> Size2D<u64>; +} + +impl Size2DExt for Size2D<u32> { + fn to_u64(&self) -> Size2D<u64> { + return Size2D::new(self.width as u64, self.height as u64); + } +} + +fn array_buffer_type_to_sized_type(type_: Type) -> Option<SizedDataType> { + match type_ { + Type::Uint8 | Type::Uint8Clamped => Some(SizedDataType::Uint8), + Type::Uint16 => Some(SizedDataType::Uint16), + Type::Uint32 => Some(SizedDataType::Uint32), + Type::Int8 => Some(SizedDataType::Int8), + Type::Int16 => Some(SizedDataType::Int16), + Type::Int32 => Some(SizedDataType::Int32), + Type::Float32 => Some(SizedDataType::Float32), + Type::Float64 | + Type::BigInt64 | + Type::BigUint64 | + Type::MaxTypedArrayViewType | + Type::Int64 | + Type::Simd128 => None, } } diff --git a/components/script/dom/webglsampler.rs b/components/script/dom/webglsampler.rs new file mode 100644 index 00000000000..063decffe8f --- /dev/null +++ b/components/script/dom/webglsampler.rs @@ -0,0 +1,184 @@ +/* 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 crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use canvas_traits::webgl::WebGLError::*; +use canvas_traits::webgl::{webgl_channel, WebGLCommand, WebGLSamplerId}; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct WebGLSampler { + webgl_object: WebGLObject, + gl_id: WebGLSamplerId, + marked_for_deletion: Cell<bool>, +} + +#[derive(Clone, Copy)] +pub enum WebGLSamplerValue { + Float(f32), + GLenum(u32), +} + +fn validate_params(pname: u32, value: WebGLSamplerValue) -> bool { + match value { + WebGLSamplerValue::GLenum(value) => { + let allowed_values = match pname { + constants::TEXTURE_MIN_FILTER => &[ + constants::NEAREST, + constants::LINEAR, + constants::NEAREST_MIPMAP_NEAREST, + constants::LINEAR_MIPMAP_NEAREST, + constants::NEAREST_MIPMAP_LINEAR, + constants::LINEAR_MIPMAP_LINEAR, + ][..], + constants::TEXTURE_MAG_FILTER => &[constants::NEAREST, constants::LINEAR][..], + constants::TEXTURE_WRAP_R | + constants::TEXTURE_WRAP_S | + constants::TEXTURE_WRAP_T => &[ + constants::CLAMP_TO_EDGE, + constants::MIRRORED_REPEAT, + constants::REPEAT, + ][..], + constants::TEXTURE_COMPARE_MODE => { + &[constants::NONE, constants::COMPARE_REF_TO_TEXTURE][..] + }, + constants::TEXTURE_COMPARE_FUNC => &[ + constants::LEQUAL, + constants::GEQUAL, + constants::LESS, + constants::GREATER, + constants::EQUAL, + constants::NOTEQUAL, + constants::ALWAYS, + constants::NEVER, + ][..], + _ => &[][..], + }; + allowed_values.contains(&value) + }, + WebGLSamplerValue::Float(_) => match pname { + constants::TEXTURE_MIN_LOD | constants::TEXTURE_MAX_LOD => true, + _ => false, + }, + } +} + +impl WebGLSampler { + fn new_inherited(context: &WebGLRenderingContext, id: WebGLSamplerId) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), + gl_id: id, + marked_for_deletion: Cell::new(false), + } + } + + pub fn new(context: &WebGLRenderingContext) -> DomRoot<Self> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::GenerateSampler(sender)); + let id = receiver.recv().unwrap(); + + reflect_dom_object( + Box::new(Self::new_inherited(context, id)), + &*context.global(), + ) + } + + pub fn delete(&self, operation_fallibility: Operation) { + if !self.marked_for_deletion.get() { + self.marked_for_deletion.set(true); + + let command = WebGLCommand::DeleteSampler(self.gl_id); + let context = self.upcast::<WebGLObject>().context(); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(command), + Operation::Infallible => context.send_command(command), + } + } + } + + pub fn is_valid(&self) -> bool { + !self.marked_for_deletion.get() + } + + pub fn bind( + &self, + context: &WebGLRenderingContext, + unit: u32, + ) -> Result<(), canvas_traits::webgl::WebGLError> { + if !self.is_valid() { + return Err(InvalidOperation); + } + context.send_command(WebGLCommand::BindSampler(unit, self.gl_id)); + Ok(()) + } + + pub fn set_parameter( + &self, + context: &WebGLRenderingContext, + pname: u32, + value: WebGLSamplerValue, + ) -> Result<(), canvas_traits::webgl::WebGLError> { + if !self.is_valid() { + return Err(InvalidOperation); + } + if !validate_params(pname, value) { + return Err(InvalidEnum); + } + let command = match value { + WebGLSamplerValue::GLenum(value) => { + WebGLCommand::SetSamplerParameterInt(self.gl_id, pname, value as i32) + }, + WebGLSamplerValue::Float(value) => { + WebGLCommand::SetSamplerParameterFloat(self.gl_id, pname, value) + }, + }; + context.send_command(command); + Ok(()) + } + + pub fn get_parameter( + &self, + context: &WebGLRenderingContext, + pname: u32, + ) -> Result<WebGLSamplerValue, canvas_traits::webgl::WebGLError> { + if !self.is_valid() { + return Err(InvalidOperation); + } + match pname { + constants::TEXTURE_MIN_FILTER | + constants::TEXTURE_MAG_FILTER | + constants::TEXTURE_WRAP_R | + constants::TEXTURE_WRAP_S | + constants::TEXTURE_WRAP_T | + constants::TEXTURE_COMPARE_FUNC | + constants::TEXTURE_COMPARE_MODE => { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::GetSamplerParameterInt( + self.gl_id, pname, sender, + )); + Ok(WebGLSamplerValue::GLenum(receiver.recv().unwrap() as u32)) + }, + constants::TEXTURE_MIN_LOD | constants::TEXTURE_MAX_LOD => { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::GetSamplerParameterFloat( + self.gl_id, pname, sender, + )); + Ok(WebGLSamplerValue::Float(receiver.recv().unwrap())) + }, + _ => Err(InvalidEnum), + } + } +} + +impl Drop for WebGLSampler { + fn drop(&mut self) { + self.delete(Operation::Fallible); + } +} diff --git a/components/script/dom/webglshader.rs b/components/script/dom/webglshader.rs index b66151bf353..3f5f6c1cf43 100644 --- a/components/script/dom/webglshader.rs +++ b/components/script/dom/webglshader.rs @@ -1,25 +1,29 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use angle::hl::{BuiltInResources, Output, ShaderValidator}; -use canvas_traits::CanvasMsg; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::WebGLShaderBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::webglobject::WebGLObject; -use dom::window::Window; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::webgl_extensions::ext::extfragdepth::EXTFragDepth; +use crate::dom::webgl_extensions::ext::extshadertexturelod::EXTShaderTextureLod; +use crate::dom::webgl_extensions::ext::oesstandardderivatives::OESStandardDerivatives; +use crate::dom::webgl_extensions::WebGLExtensions; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use canvas_traits::webgl::{webgl_channel, GlType, WebGLVersion}; +use canvas_traits::webgl::{GLLimits, WebGLCommand, WebGLError}; +use canvas_traits::webgl::{WebGLResult, WebGLSLVersion, WebGLShaderId}; use dom_struct::dom_struct; -use ipc_channel::ipc::IpcSender; +use mozangle::shaders::{ffi, BuiltInResources, Output, ShaderValidator}; use std::cell::Cell; -use std::sync::{ONCE_INIT, Once}; -use webrender_traits; -use webrender_traits::{WebGLCommand, WebGLParameter, WebGLResult, WebGLShaderId}; +use std::os::raw::c_int; +use std::sync::Once; -#[derive(Clone, Copy, PartialEq, Debug, JSTraceable, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub enum ShaderCompilationStatus { NotCompiled, Succeeded, @@ -31,64 +35,165 @@ pub struct WebGLShader { webgl_object: WebGLObject, id: WebGLShaderId, gl_type: u32, - source: DOMRefCell<Option<DOMString>>, - info_log: DOMRefCell<Option<String>>, - is_deleted: Cell<bool>, + source: DomRefCell<DOMString>, + info_log: DomRefCell<DOMString>, + marked_for_deletion: Cell<bool>, attached_counter: Cell<u32>, compilation_status: Cell<ShaderCompilationStatus>, - #[ignore_heap_size_of = "Defined in ipc-channel"] - renderer: IpcSender<CanvasMsg>, } -#[cfg(not(target_os = "android"))] -const SHADER_OUTPUT_FORMAT: Output = Output::Glsl; - -#[cfg(target_os = "android")] -const SHADER_OUTPUT_FORMAT: Output = Output::Essl; - -static GLSLANG_INITIALIZATION: Once = ONCE_INIT; +static GLSLANG_INITIALIZATION: Once = Once::new(); impl WebGLShader { - fn new_inherited(renderer: IpcSender<CanvasMsg>, - id: WebGLShaderId, - shader_type: u32) - -> WebGLShader { - GLSLANG_INITIALIZATION.call_once(|| ::angle::hl::initialize().unwrap()); - WebGLShader { - webgl_object: WebGLObject::new_inherited(), + fn new_inherited(context: &WebGLRenderingContext, id: WebGLShaderId, shader_type: u32) -> Self { + GLSLANG_INITIALIZATION.call_once(|| ::mozangle::shaders::initialize().unwrap()); + Self { + webgl_object: WebGLObject::new_inherited(context), id: id, gl_type: shader_type, - source: DOMRefCell::new(None), - info_log: DOMRefCell::new(None), - is_deleted: Cell::new(false), + source: Default::default(), + info_log: Default::default(), + marked_for_deletion: Cell::new(false), attached_counter: Cell::new(0), compilation_status: Cell::new(ShaderCompilationStatus::NotCompiled), - renderer: renderer, } } - pub fn maybe_new(window: &Window, - renderer: IpcSender<CanvasMsg>, - shader_type: u32) - -> Option<Root<WebGLShader>> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateShader(shader_type, sender))).unwrap(); - - let result = receiver.recv().unwrap(); - result.map(|shader_id| WebGLShader::new(window, renderer, shader_id, shader_type)) + pub fn maybe_new(context: &WebGLRenderingContext, shader_type: u32) -> Option<DomRoot<Self>> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::CreateShader(shader_type, sender)); + receiver + .recv() + .unwrap() + .map(|id| WebGLShader::new(context, id, shader_type)) } - pub fn new(window: &Window, - renderer: IpcSender<CanvasMsg>, - id: WebGLShaderId, - shader_type: u32) - -> Root<WebGLShader> { - reflect_dom_object(box WebGLShader::new_inherited(renderer, id, shader_type), - window, - WebGLShaderBinding::Wrap) + pub fn new( + context: &WebGLRenderingContext, + id: WebGLShaderId, + shader_type: u32, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLShader::new_inherited(context, id, shader_type)), + &*context.global(), + ) } } +// Based on https://searchfox.org/mozilla-central/rev/efdf9bb55789ea782ae3a431bda6be74a87b041e/gfx/angle/checkout/src/compiler/translator/ShaderLang.cpp#173 +fn default_validator() -> BuiltInResources { + BuiltInResources { + // Constants. + MaxVertexAttribs: 8, + MaxVertexUniformVectors: 128, + MaxVaryingVectors: 8, + MaxVertexTextureImageUnits: 0, + MaxCombinedTextureImageUnits: 8, + MaxTextureImageUnits: 8, + MaxFragmentUniformVectors: 16, + MaxDrawBuffers: 1, + + // Extensions. + OES_standard_derivatives: 0, + OES_EGL_image_external: 0, + OES_EGL_image_external_essl3: 0, + NV_EGL_stream_consumer_external: 0, + ARB_texture_rectangle: 0, + EXT_blend_func_extended: 0, + EXT_draw_buffers: 0, + EXT_frag_depth: 0, + EXT_shader_texture_lod: 0, + WEBGL_debug_shader_precision: 0, + EXT_shader_framebuffer_fetch: 0, + NV_shader_framebuffer_fetch: 0, + NV_draw_buffers: 0, + ARM_shader_framebuffer_fetch: 0, + //OVR_multiview: 0, + OVR_multiview2: 0, + EXT_YUV_target: 0, + EXT_geometry_shader: 0, + OES_texture_storage_multisample_2d_array: 0, + //OES_texture_3d: 0, + ANGLE_texture_multisample: 0, + ANGLE_multi_draw: 0, + + // Disable highp precision in fragment shader by default. + FragmentPrecisionHigh: 0, + + // GLSL ES 3.0 constants. + MaxVertexOutputVectors: 16, + MaxFragmentInputVectors: 15, + MinProgramTexelOffset: -8, + MaxProgramTexelOffset: 7, + + // Extension constants. + MaxDualSourceDrawBuffers: 0, + MaxViewsOVR: 4, + + // Disable name hashing by default. + HashFunction: None, + ArrayIndexClampingStrategy: + ffi::ShArrayIndexClampingStrategy::SH_CLAMP_WITH_CLAMP_INTRINSIC, + + MaxExpressionComplexity: 256, + MaxCallStackDepth: 256, + MaxFunctionParameters: 1024, + + // ES 3.1 Revision 4, 7.2 Built-in Constants + + // ES 3.1, Revision 4, 8.13 Texture minification + // "The value of MIN_PROGRAM_TEXTURE_GATHER_OFFSET must be less than or equal to the value of + // MIN_PROGRAM_TEXEL_OFFSET. The value of MAX_PROGRAM_TEXTURE_GATHER_OFFSET must be greater than + // or equal to the value of MAX_PROGRAM_TEXEL_OFFSET" + MinProgramTextureGatherOffset: -8, + MaxProgramTextureGatherOffset: 7, + + MaxImageUnits: 4, + MaxVertexImageUniforms: 0, + MaxFragmentImageUniforms: 0, + MaxComputeImageUniforms: 0, + MaxCombinedImageUniforms: 0, + + MaxUniformLocations: 1024, + + MaxCombinedShaderOutputResources: 4, + + MaxComputeWorkGroupCount: [65535, 65535, 65535], + MaxComputeWorkGroupSize: [128, 128, 64], + MaxComputeUniformComponents: 512, + MaxComputeTextureImageUnits: 16, + + MaxComputeAtomicCounters: 8, + MaxComputeAtomicCounterBuffers: 1, + + MaxVertexAtomicCounters: 0, + MaxFragmentAtomicCounters: 0, + MaxCombinedAtomicCounters: 8, + MaxAtomicCounterBindings: 1, + + MaxVertexAtomicCounterBuffers: 0, + MaxFragmentAtomicCounterBuffers: 0, + MaxCombinedAtomicCounterBuffers: 1, + MaxAtomicCounterBufferSize: 32, + + MaxUniformBufferBindings: 32, + MaxShaderStorageBufferBindings: 4, + MaxPointSize: 0.0, + + MaxGeometryUniformComponents: 1024, + MaxGeometryUniformBlocks: 12, + MaxGeometryInputComponents: 64, + MaxGeometryOutputComponents: 64, + MaxGeometryOutputVertices: 256, + MaxGeometryTotalOutputComponents: 1024, + MaxGeometryTextureImageUnits: 16, + MaxGeometryAtomicCounterBuffers: 0, + MaxGeometryAtomicCounters: 0, + MaxGeometryShaderStorageBlocks: 0, + MaxGeometryShaderInvocations: 32, + MaxGeometryImageUniforms: 0, + } +} impl WebGLShader { pub fn id(&self) -> WebGLShaderId { @@ -100,54 +205,165 @@ impl WebGLShader { } /// glCompileShader - pub fn compile(&self) { + pub fn compile( + &self, + api_type: GlType, + webgl_version: WebGLVersion, + glsl_version: WebGLSLVersion, + limits: &GLLimits, + ext: &WebGLExtensions, + ) -> WebGLResult<()> { + if self.marked_for_deletion.get() && !self.is_attached() { + return Err(WebGLError::InvalidValue); + } if self.compilation_status.get() != ShaderCompilationStatus::NotCompiled { debug!("Compiling already compiled shader {}", self.id); } - if let Some(ref source) = *self.source.borrow() { - let mut params = BuiltInResources::default(); - params.FragmentPrecisionHigh = 1; - let validator = ShaderValidator::for_webgl(self.gl_type, - SHADER_OUTPUT_FORMAT, - ¶ms).unwrap(); - match validator.compile_and_translate(&[source]) { - Ok(translated_source) => { - debug!("Shader translated: {}", translated_source); - // NOTE: At this point we should be pretty sure that the compilation in the paint thread - // will succeed. - // It could be interesting to retrieve the info log from the paint thread though - let msg = WebGLCommand::CompileShader(self.id, translated_source); - self.renderer.send(CanvasMsg::WebGL(msg)).unwrap(); - self.compilation_status.set(ShaderCompilationStatus::Succeeded); - }, - Err(error) => { - self.compilation_status.set(ShaderCompilationStatus::Failed); - debug!("Shader {} compilation failed: {}", self.id, error); - }, - } + let source = self.source.borrow(); + + let mut params = BuiltInResources { + MaxVertexAttribs: limits.max_vertex_attribs as c_int, + MaxVertexUniformVectors: limits.max_vertex_uniform_vectors as c_int, + MaxVertexTextureImageUnits: limits.max_vertex_texture_image_units as c_int, + MaxCombinedTextureImageUnits: limits.max_combined_texture_image_units as c_int, + MaxTextureImageUnits: limits.max_texture_image_units as c_int, + MaxFragmentUniformVectors: limits.max_fragment_uniform_vectors as c_int, + + MaxVertexOutputVectors: limits.max_vertex_output_vectors as c_int, + MaxFragmentInputVectors: limits.max_fragment_input_vectors as c_int, + MaxVaryingVectors: limits.max_varying_vectors as c_int, + + OES_standard_derivatives: ext.is_enabled::<OESStandardDerivatives>() as c_int, + EXT_shader_texture_lod: ext.is_enabled::<EXTShaderTextureLod>() as c_int, + EXT_frag_depth: ext.is_enabled::<EXTFragDepth>() as c_int, + + FragmentPrecisionHigh: 1, + ..default_validator() + }; + + if webgl_version == WebGLVersion::WebGL2 { + params.MinProgramTexelOffset = limits.min_program_texel_offset as c_int; + params.MaxProgramTexelOffset = limits.max_program_texel_offset as c_int; + params.MaxDrawBuffers = limits.max_draw_buffers as c_int; + } - *self.info_log.borrow_mut() = Some(validator.info_log()); - // TODO(emilio): More data (like uniform data) should be collected - // here to properly validate uniforms. - // - // This requires a more complex interface with ANGLE, using C++ - // bindings and being extremely cautious about destructing things. + let validator = match webgl_version { + WebGLVersion::WebGL1 => { + let output_format = if api_type == GlType::Gles { + Output::Essl + } else { + Output::Glsl + }; + ShaderValidator::for_webgl(self.gl_type, output_format, ¶ms).unwrap() + }, + WebGLVersion::WebGL2 => { + let output_format = if api_type == GlType::Gles { + Output::Essl + } else { + match (glsl_version.major, glsl_version.minor) { + (1, 30) => Output::Glsl130, + (1, 40) => Output::Glsl140, + (1, 50) => Output::Glsl150Core, + (3, 30) => Output::Glsl330Core, + (4, 0) => Output::Glsl400Core, + (4, 10) => Output::Glsl410Core, + (4, 20) => Output::Glsl420Core, + (4, 30) => Output::Glsl430Core, + (4, 40) => Output::Glsl440Core, + (4, _) => Output::Glsl450Core, + _ => Output::Glsl140, + } + }; + ShaderValidator::for_webgl2(self.gl_type, output_format, ¶ms).unwrap() + }, + }; + + // Replicating + // https://searchfox.org/mozilla-central/rev/c621276fbdd9591f52009042d959b9e19b66d49f/dom/canvas/WebGLShaderValidator.cpp#32 + let options = mozangle::shaders::ffi::SH_VARIABLES | + mozangle::shaders::ffi::SH_ENFORCE_PACKING_RESTRICTIONS | + mozangle::shaders::ffi::SH_OBJECT_CODE | + mozangle::shaders::ffi::SH_INIT_GL_POSITION | + mozangle::shaders::ffi::SH_INITIALIZE_UNINITIALIZED_LOCALS | + mozangle::shaders::ffi::SH_INIT_OUTPUT_VARIABLES | + mozangle::shaders::ffi::SH_LIMIT_EXPRESSION_COMPLEXITY | + mozangle::shaders::ffi::SH_LIMIT_CALL_STACK_DEPTH | + if cfg!(target_os = "macos") { + // Work around https://bugs.webkit.org/show_bug.cgi?id=124684, + // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb + mozangle::shaders::ffi::SH_UNFOLD_SHORT_CIRCUIT | + // Work around that Mac drivers handle struct scopes incorrectly. + mozangle::shaders::ffi::SH_REGENERATE_STRUCT_NAMES | + // Work around that Intel drivers on Mac OSX handle for-loop incorrectly. + mozangle::shaders::ffi::SH_ADD_AND_TRUE_TO_LOOP_CONDITION + } else { + // We want to do this everywhere, but to do this on Mac, we need + // to do it only on Mac OSX > 10.6 as this causes the shader + // compiler in 10.6 to crash + mozangle::shaders::ffi::SH_CLAMP_INDIRECT_ARRAY_BOUNDS + }; + + // Replicating + // https://github.com/servo/mozangle/blob/706a9baaf8026c1a3cb6c67ba63aa5f4734264d0/src/shaders/mod.rs#L226 + let options = options | + mozangle::shaders::ffi::SH_VALIDATE | + mozangle::shaders::ffi::SH_OBJECT_CODE | + mozangle::shaders::ffi::SH_VARIABLES | // For uniform_name_map() + mozangle::shaders::ffi::SH_EMULATE_ABS_INT_FUNCTION | // To workaround drivers + mozangle::shaders::ffi::SH_EMULATE_ISNAN_FLOAT_FUNCTION | // To workaround drivers + mozangle::shaders::ffi::SH_EMULATE_ATAN2_FLOAT_FUNCTION | // To workaround drivers + mozangle::shaders::ffi::SH_CLAMP_INDIRECT_ARRAY_BOUNDS | + mozangle::shaders::ffi::SH_INIT_GL_POSITION | + mozangle::shaders::ffi::SH_ENFORCE_PACKING_RESTRICTIONS | + mozangle::shaders::ffi::SH_LIMIT_EXPRESSION_COMPLEXITY | + mozangle::shaders::ffi::SH_LIMIT_CALL_STACK_DEPTH; + + match validator.compile(&[&source], options) { + Ok(()) => { + let translated_source = validator.object_code(); + debug!("Shader translated: {}", translated_source); + // NOTE: At this point we should be pretty sure that the compilation in the paint thread + // will succeed. + // It could be interesting to retrieve the info log from the paint thread though + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::CompileShader(self.id, translated_source)); + self.compilation_status + .set(ShaderCompilationStatus::Succeeded); + }, + Err(error) => { + self.compilation_status.set(ShaderCompilationStatus::Failed); + debug!("Shader {} compilation failed: {}", self.id, error); + }, } + + *self.info_log.borrow_mut() = validator.info_log().into(); + + Ok(()) } /// Mark this shader as deleted (if it wasn't previously) /// and delete it as if calling glDeleteShader. /// Currently does not check if shader is attached - pub fn delete(&self) { - if !self.is_deleted.get() { - self.is_deleted.set(true); - let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteShader(self.id))); + pub fn mark_for_deletion(&self, operation_fallibility: Operation) { + if !self.marked_for_deletion.get() { + self.marked_for_deletion.set(true); + let context = self.upcast::<WebGLObject>().context(); + let cmd = WebGLCommand::DeleteShader(self.id); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(cmd), + Operation::Infallible => context.send_command(cmd), + } } } + pub fn is_marked_for_deletion(&self) -> bool { + self.marked_for_deletion.get() + } + pub fn is_deleted(&self) -> bool { - self.is_deleted.get() + self.marked_for_deletion.get() && !self.is_attached() } pub fn is_attached(&self) -> bool { @@ -164,25 +380,18 @@ impl WebGLShader { } /// glGetShaderInfoLog - pub fn info_log(&self) -> Option<String> { + pub fn info_log(&self) -> DOMString { self.info_log.borrow().clone() } - /// glGetParameter - pub fn parameter(&self, param_id: u32) -> WebGLResult<WebGLParameter> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::GetShaderParameter(self.id, param_id, sender))).unwrap(); - receiver.recv().unwrap() - } - /// Get the shader source - pub fn source(&self) -> Option<DOMString> { + pub fn source(&self) -> DOMString { self.source.borrow().clone() } /// glShaderSource pub fn set_source(&self, source: DOMString) { - *self.source.borrow_mut() = Some(source); + *self.source.borrow_mut() = source; } pub fn successfully_compiled(&self) -> bool { @@ -192,7 +401,6 @@ impl WebGLShader { impl Drop for WebGLShader { fn drop(&mut self) { - assert!(self.attached_counter.get() == 0); - self.delete(); + self.mark_for_deletion(Operation::Fallible); } } diff --git a/components/script/dom/webglshaderprecisionformat.rs b/components/script/dom/webglshaderprecisionformat.rs index 10a76b73fe1..e1a7202ff8d 100644 --- a/components/script/dom/webglshaderprecisionformat.rs +++ b/components/script/dom/webglshaderprecisionformat.rs @@ -1,15 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #![allow(dead_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use dom::bindings::codegen::Bindings::WebGLShaderPrecisionFormatBinding; -use dom::bindings::codegen::Bindings::WebGLShaderPrecisionFormatBinding::WebGLShaderPrecisionFormatMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::window::Window; +use crate::dom::bindings::codegen::Bindings::WebGLShaderPrecisionFormatBinding::WebGLShaderPrecisionFormatMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::window::Window; use dom_struct::dom_struct; #[dom_struct] @@ -30,14 +29,18 @@ impl WebGLShaderPrecisionFormat { } } - pub fn new(window: &Window, - range_min: i32, - range_max: i32, - precision: i32) -> Root<WebGLShaderPrecisionFormat> { + pub fn new( + window: &Window, + range_min: i32, + range_max: i32, + precision: i32, + ) -> DomRoot<WebGLShaderPrecisionFormat> { reflect_dom_object( - box WebGLShaderPrecisionFormat::new_inherited(range_min, range_max, precision), + Box::new(WebGLShaderPrecisionFormat::new_inherited( + range_min, range_max, precision, + )), window, - WebGLShaderPrecisionFormatBinding::Wrap) + ) } } diff --git a/components/script/dom/webglsync.rs b/components/script/dom/webglsync.rs new file mode 100644 index 00000000000..a6288f69c1e --- /dev/null +++ b/components/script/dom/webglsync.rs @@ -0,0 +1,135 @@ +/* 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 crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use crate::task_source::TaskSource; +use canvas_traits::webgl::{webgl_channel, WebGLCommand, WebGLSyncId}; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct WebGLSync { + webgl_object: WebGLObject, + sync_id: WebGLSyncId, + marked_for_deletion: Cell<bool>, + client_wait_status: Cell<Option<u32>>, + sync_status: Cell<Option<u32>>, +} + +impl WebGLSync { + fn new_inherited(context: &WebGLRenderingContext, sync_id: WebGLSyncId) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), + sync_id, + marked_for_deletion: Cell::new(false), + client_wait_status: Cell::new(None), + sync_status: Cell::new(None), + } + } + + pub fn new(context: &WebGLRenderingContext) -> DomRoot<Self> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::FenceSync(sender)); + let sync_id = receiver.recv().unwrap(); + + reflect_dom_object( + Box::new(WebGLSync::new_inherited(context, sync_id)), + &*context.global(), + ) + } +} + +impl WebGLSync { + pub fn client_wait_sync( + &self, + context: &WebGLRenderingContext, + flags: u32, + timeout: u64, + ) -> Option<u32> { + match self.client_wait_status.get() { + Some(constants::TIMEOUT_EXPIRED) | Some(constants::WAIT_FAILED) | None => { + let global = self.global(); + let this = Trusted::new(self); + let context = Trusted::new(context); + let task = task!(request_client_wait_status: move || { + let this = this.root(); + let context = context.root(); + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::ClientWaitSync( + this.sync_id, + flags, + timeout, + sender, + )); + this.client_wait_status.set(Some(receiver.recv().unwrap())); + }); + global + .as_window() + .task_manager() + .dom_manipulation_task_source() + .queue(task, global.upcast()) + .unwrap(); + }, + _ => {}, + } + self.client_wait_status.get() + } + + pub fn delete(&self, operation_fallibility: Operation) { + if self.is_valid() { + self.marked_for_deletion.set(true); + let context = self.upcast::<WebGLObject>().context(); + let cmd = WebGLCommand::DeleteSync(self.sync_id); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(cmd), + Operation::Infallible => context.send_command(cmd), + } + } + } + + pub fn get_sync_status(&self, pname: u32, context: &WebGLRenderingContext) -> Option<u32> { + match self.sync_status.get() { + Some(constants::UNSIGNALED) | None => { + let global = self.global(); + let this = Trusted::new(self); + let context = Trusted::new(context); + let task = task!(request_sync_status: move || { + let this = this.root(); + let context = context.root(); + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::GetSyncParameter(this.sync_id, pname, sender)); + this.sync_status.set(Some(receiver.recv().unwrap())); + }); + global + .as_window() + .task_manager() + .dom_manipulation_task_source() + .queue(task, global.upcast()) + .unwrap(); + }, + _ => {}, + } + self.sync_status.get() + } + + pub fn is_valid(&self) -> bool { + !self.marked_for_deletion.get() + } + + pub fn id(&self) -> WebGLSyncId { + self.sync_id + } +} + +impl Drop for WebGLSync { + fn drop(&mut self) { + self.delete(Operation::Fallible); + } +} diff --git a/components/script/dom/webgltexture.rs b/components/script/dom/webgltexture.rs index d88f955a862..7456f772dec 100644 --- a/components/script/dom/webgltexture.rs +++ b/components/script/dom/webgltexture.rs @@ -1,27 +1,43 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use canvas_traits::CanvasMsg; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; -use dom::bindings::codegen::Bindings::WebGLTextureBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::webgl_validations::types::{TexImageTarget, TexFormat, TexDataType}; -use dom::webglobject::WebGLObject; -use dom::window::Window; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EXTTextureFilterAnisotropicBinding::EXTTextureFilterAnisotropicConstants; +use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::webgl_validations::types::TexImageTarget; +use crate::dom::webglframebuffer::WebGLFramebuffer; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use crate::dom::xrsession::XRSession; +use canvas_traits::webgl::{ + webgl_channel, TexDataType, TexFormat, TexParameter, TexParameterBool, TexParameterInt, + WebGLResult, WebGLTextureId, +}; +use canvas_traits::webgl::{DOMToTextureCommand, WebGLCommand, WebGLError}; use dom_struct::dom_struct; -use ipc_channel::ipc::IpcSender; use std::cell::Cell; use std::cmp; -use webrender_traits; -use webrender_traits::{WebGLCommand, WebGLError, WebGLResult, WebGLTextureId}; pub enum TexParameterValue { Float(f32), Int(i32), + Bool(bool), +} + +// Textures generated for WebXR are owned by the WebXR device, not by the WebGL thread +// so the GL texture should not be deleted when the texture is garbage collected. +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +enum WebGLTextureOwner { + WebGL, + WebXR(Dom<XRSession>), } const MAX_LEVEL_COUNT: usize = 31; @@ -36,52 +52,77 @@ pub struct WebGLTexture { /// The target to which this texture was bound the first time target: Cell<Option<u32>>, is_deleted: Cell<bool>, + owner: WebGLTextureOwner, /// Stores information about mipmap levels and cubemap faces. - #[ignore_heap_size_of = "Arrays are cumbersome"] - image_info_array: DOMRefCell<[ImageInfo; MAX_LEVEL_COUNT * MAX_FACE_COUNT]>, + #[ignore_malloc_size_of = "Arrays are cumbersome"] + image_info_array: DomRefCell<[Option<ImageInfo>; MAX_LEVEL_COUNT * MAX_FACE_COUNT]>, /// Face count can only be 1 or 6 face_count: Cell<u8>, base_mipmap_level: u32, - #[ignore_heap_size_of = "Defined in ipc-channel"] - renderer: IpcSender<CanvasMsg>, + // Store information for min and mag filters + min_filter: Cell<u32>, + mag_filter: Cell<u32>, + /// True if this texture is used for the DOMToTexture feature. + attached_to_dom: Cell<bool>, + /// Framebuffer that this texture is attached to. + attached_framebuffer: MutNullableDom<WebGLFramebuffer>, + /// Number of immutable levels. + immutable_levels: Cell<Option<u32>>, } impl WebGLTexture { - fn new_inherited(renderer: IpcSender<CanvasMsg>, - id: WebGLTextureId) - -> WebGLTexture { - WebGLTexture { - webgl_object: WebGLObject::new_inherited(), + fn new_inherited( + context: &WebGLRenderingContext, + id: WebGLTextureId, + owner: Option<&XRSession>, + ) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), id: id, target: Cell::new(None), is_deleted: Cell::new(false), + owner: owner + .map(|session| WebGLTextureOwner::WebXR(Dom::from_ref(session))) + .unwrap_or(WebGLTextureOwner::WebGL), + immutable_levels: Cell::new(None), face_count: Cell::new(0), base_mipmap_level: 0, - image_info_array: DOMRefCell::new([ImageInfo::new(); MAX_LEVEL_COUNT * MAX_FACE_COUNT]), - renderer: renderer, + min_filter: Cell::new(constants::NEAREST_MIPMAP_LINEAR), + mag_filter: Cell::new(constants::LINEAR), + image_info_array: DomRefCell::new([None; MAX_LEVEL_COUNT * MAX_FACE_COUNT]), + attached_to_dom: Cell::new(false), + attached_framebuffer: Default::default(), } } - pub fn maybe_new(window: &Window, renderer: IpcSender<CanvasMsg>) - -> Option<Root<WebGLTexture>> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateTexture(sender))).unwrap(); + pub fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::CreateTexture(sender)); + receiver + .recv() + .unwrap() + .map(|id| WebGLTexture::new(context, id)) + } - let result = receiver.recv().unwrap(); - result.map(|texture_id| WebGLTexture::new(window, renderer, texture_id)) + pub fn new(context: &WebGLRenderingContext, id: WebGLTextureId) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLTexture::new_inherited(context, id, None)), + &*context.global(), + ) } - pub fn new(window: &Window, - renderer: IpcSender<CanvasMsg>, - id: WebGLTextureId) - -> Root<WebGLTexture> { - reflect_dom_object(box WebGLTexture::new_inherited(renderer, id), - window, - WebGLTextureBinding::Wrap) + pub fn new_webxr( + context: &WebGLRenderingContext, + id: WebGLTextureId, + session: &XRSession, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLTexture::new_inherited(context, id, Some(session))), + &*context.global(), + ) } } - impl WebGLTexture { pub fn id(&self) -> WebGLTextureId { self.id @@ -89,7 +130,7 @@ impl WebGLTexture { // NB: Only valid texture targets come here pub fn bind(&self, target: u32) -> WebGLResult<()> { - if self.is_deleted.get() { + if self.is_invalid() { return Err(WebGLError::InvalidOperation); } @@ -102,37 +143,44 @@ impl WebGLTexture { let face_count = match target { constants::TEXTURE_2D => 1, constants::TEXTURE_CUBE_MAP => 6, - _ => return Err(WebGLError::InvalidOperation) + _ => return Err(WebGLError::InvalidEnum), }; self.face_count.set(face_count); self.target.set(Some(target)); } - let msg = CanvasMsg::WebGL(WebGLCommand::BindTexture(target, Some(self.id))); - self.renderer.send(msg).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::BindTexture(target, Some(self.id))); Ok(()) } - pub fn initialize(&self, - target: TexImageTarget, - width: u32, - height: u32, - depth: u32, - internal_format: TexFormat, - level: u32, - data_type: Option<TexDataType>) -> WebGLResult<()> { + pub fn initialize( + &self, + target: TexImageTarget, + width: u32, + height: u32, + depth: u32, + internal_format: TexFormat, + level: u32, + data_type: Option<TexDataType>, + ) -> WebGLResult<()> { let image_info = ImageInfo { width: width, height: height, depth: depth, - internal_format: Some(internal_format), - is_initialized: true, + internal_format: internal_format, data_type: data_type, }; let face_index = self.face_index_for_target(&target); self.set_image_infos_at_level_and_face(level, face_index, image_info); + + if let Some(fb) = self.attached_framebuffer.get() { + fb.update_status(); + } + Ok(()) } @@ -142,13 +190,10 @@ impl WebGLTexture { None => { error!("Cannot generate mipmap on texture that has no target!"); return Err(WebGLError::InvalidOperation); - } + }, }; - let base_image_info = self.base_image_info().unwrap(); - if !base_image_info.is_initialized() { - return Err(WebGLError::InvalidOperation); - } + let base_image_info = self.base_image_info().ok_or(WebGLError::InvalidOperation)?; let is_cubic = target == constants::TEXTURE_CUBE_MAP; if is_cubic && !self.is_cube_complete() { @@ -163,7 +208,9 @@ impl WebGLTexture { return Err(WebGLError::InvalidOperation); } - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::GenerateMipmap(target))).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::GenerateMipmap(target)); if self.base_mipmap_level + base_image_info.get_max_mimap_levels() == 0 { return Err(WebGLError::InvalidOperation); @@ -173,89 +220,157 @@ impl WebGLTexture { self.populate_mip_chain(self.base_mipmap_level, last_level) } - pub fn delete(&self) { + pub fn delete(&self, operation_fallibility: Operation) { if !self.is_deleted.get() { self.is_deleted.set(true); - let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteTexture(self.id))); + let context = self.upcast::<WebGLObject>().context(); + // Notify WR to release the frame output when using DOMToTexture feature + if self.attached_to_dom.get() { + let _ = context + .webgl_sender() + .send_dom_to_texture(DOMToTextureCommand::Detach(self.id)); + } + + /* + If a texture object is deleted while its image is attached to one or more attachment + points in a currently bound framebuffer, then it is as if FramebufferTexture had been + called, with a texture of zero, for each attachment point to which this im-age was + attached in that framebuffer. In other words, this texture image is firstdetached from + all attachment points in a currently bound framebuffer. + - GLES 3.0, 4.4.2.3, "Attaching Texture Images to a Framebuffer" + */ + if let Some(fb) = context.get_draw_framebuffer_slot().get() { + let _ = fb.detach_texture(self); + } + if let Some(fb) = context.get_read_framebuffer_slot().get() { + let _ = fb.detach_texture(self); + } + + // We don't delete textures owned by WebXR + if let WebGLTextureOwner::WebXR(_) = self.owner { + return; + } + + let cmd = WebGLCommand::DeleteTexture(self.id); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(cmd), + Operation::Infallible => context.send_command(cmd), + } } } - pub fn is_deleted(&self) -> bool { + pub fn is_invalid(&self) -> bool { + // https://immersive-web.github.io/layers/#xrwebglsubimagetype + if let WebGLTextureOwner::WebXR(ref session) = self.owner { + if session.is_outside_raf() { + return true; + } + } self.is_deleted.get() } + pub fn is_immutable(&self) -> bool { + self.immutable_levels.get().is_some() + } + pub fn target(&self) -> Option<u32> { self.target.get() } + pub fn maybe_get_tex_parameter(&self, param: TexParameter) -> Option<TexParameterValue> { + match param { + TexParameter::Int(TexParameterInt::TextureImmutableLevels) => Some( + TexParameterValue::Int(self.immutable_levels.get().unwrap_or(0) as i32), + ), + TexParameter::Bool(TexParameterBool::TextureImmutableFormat) => { + Some(TexParameterValue::Bool(self.is_immutable())) + }, + _ => None, + } + } + /// We have to follow the conversion rules for GLES 2.0. See: /// https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html /// - pub fn tex_parameter(&self, - target: u32, - name: u32, - value: TexParameterValue) -> WebGLResult<()> { - let (int_value, _float_value) = match value { + pub fn tex_parameter(&self, param: u32, value: TexParameterValue) -> WebGLResult<()> { + let target = self.target().unwrap(); + + let (int_value, float_value) = match value { TexParameterValue::Int(int_value) => (int_value, int_value as f32), TexParameterValue::Float(float_value) => (float_value as i32, float_value), + TexParameterValue::Bool(_) => unreachable!("no settable tex params should be booleans"), }; - match name { - constants::TEXTURE_MIN_FILTER => { - match int_value as u32 { - constants::NEAREST | - constants::LINEAR | - constants::NEAREST_MIPMAP_NEAREST | - constants::LINEAR_MIPMAP_NEAREST | - constants::NEAREST_MIPMAP_LINEAR | - constants::LINEAR_MIPMAP_LINEAR => { - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::TexParameteri(target, name, int_value))) - .unwrap(); - Ok(()) - }, - - _ => Err(WebGLError::InvalidEnum), - } + let update_filter = |filter: &Cell<u32>| { + if filter.get() == int_value as u32 { + return Ok(()); + } + filter.set(int_value as u32); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::TexParameteri(target, param, int_value)); + Ok(()) + }; + match param { + constants::TEXTURE_MIN_FILTER => match int_value as u32 { + constants::NEAREST | + constants::LINEAR | + constants::NEAREST_MIPMAP_NEAREST | + constants::LINEAR_MIPMAP_NEAREST | + constants::NEAREST_MIPMAP_LINEAR | + constants::LINEAR_MIPMAP_LINEAR => update_filter(&self.min_filter), + _ => Err(WebGLError::InvalidEnum), }, - constants::TEXTURE_MAG_FILTER => { - match int_value as u32 { - constants::NEAREST | - constants::LINEAR => { - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::TexParameteri(target, name, int_value))) - .unwrap(); - Ok(()) - }, - - _ => Err(WebGLError::InvalidEnum), - } + constants::TEXTURE_MAG_FILTER => match int_value as u32 { + constants::NEAREST | constants::LINEAR => update_filter(&self.mag_filter), + _ => return Err(WebGLError::InvalidEnum), + }, + constants::TEXTURE_WRAP_S | constants::TEXTURE_WRAP_T => match int_value as u32 { + constants::CLAMP_TO_EDGE | constants::MIRRORED_REPEAT | constants::REPEAT => { + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::TexParameteri(target, param, int_value)); + Ok(()) + }, + _ => Err(WebGLError::InvalidEnum), }, - constants::TEXTURE_WRAP_S | - constants::TEXTURE_WRAP_T => { - match int_value as u32 { - constants::CLAMP_TO_EDGE | - constants::MIRRORED_REPEAT | - constants::REPEAT => { - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::TexParameteri(target, name, int_value))) - .unwrap(); - Ok(()) - }, - - _ => Err(WebGLError::InvalidEnum), + EXTTextureFilterAnisotropicConstants::TEXTURE_MAX_ANISOTROPY_EXT => { + // NaN is not less than 1., what a time to be alive. + if !(float_value >= 1.) { + return Err(WebGLError::InvalidValue); } + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::TexParameterf(target, param, float_value)); + Ok(()) }, - _ => Err(WebGLError::InvalidEnum), } } + pub fn min_filter(&self) -> u32 { + self.min_filter.get() + } + + pub fn mag_filter(&self) -> u32 { + self.mag_filter.get() + } + + pub fn is_using_linear_filtering(&self) -> bool { + let filters = [self.min_filter.get(), self.mag_filter.get()]; + filters.iter().any(|filter| match *filter { + constants::LINEAR | + constants::NEAREST_MIPMAP_LINEAR | + constants::LINEAR_MIPMAP_NEAREST | + constants::LINEAR_MIPMAP_LINEAR => true, + _ => false, + }) + } + pub fn populate_mip_chain(&self, first_level: u32, last_level: u32) -> WebGLResult<()> { - let base_image_info = self.image_info_at_face(0, first_level); - if !base_image_info.is_initialized() { - return Err(WebGLError::InvalidOperation); - } + let base_image_info = self + .image_info_at_face(0, first_level) + .ok_or(WebGLError::InvalidOperation)?; let mut ref_width = base_image_info.width; let mut ref_height = base_image_info.height; @@ -277,7 +392,6 @@ impl WebGLTexture { height: ref_height, depth: 0, internal_format: base_image_info.internal_format, - is_initialized: base_image_info.is_initialized(), data_type: base_image_info.data_type, }; @@ -287,26 +401,27 @@ impl WebGLTexture { } fn is_cube_complete(&self) -> bool { - debug_assert!(self.face_count.get() == 6); + debug_assert_eq!(self.face_count.get(), 6); - let image_info = self.base_image_info().unwrap(); - if !image_info.is_defined() { - return false; - } + let image_info = match self.base_image_info() { + Some(info) => info, + None => return false, + }; let ref_width = image_info.width; let ref_format = image_info.internal_format; for face in 0..self.face_count.get() { - let current_image_info = self.image_info_at_face(face, self.base_mipmap_level); - if !current_image_info.is_defined() { - return false; - } + let current_image_info = match self.image_info_at_face(face, self.base_mipmap_level) { + Some(info) => info, + None => return false, + }; // Compares height with width to enforce square dimensions if current_image_info.internal_format != ref_format || - current_image_info.width != ref_width || - current_image_info.height != ref_width { + current_image_info.width != ref_width || + current_image_info.height != ref_width + { return false; } } @@ -314,27 +429,24 @@ impl WebGLTexture { true } - fn face_index_for_target(&self, - target: &TexImageTarget) -> u8 { + fn face_index_for_target(&self, target: &TexImageTarget) -> u8 { match *target { - TexImageTarget::Texture2D => 0, TexImageTarget::CubeMapPositiveX => 0, TexImageTarget::CubeMapNegativeX => 1, TexImageTarget::CubeMapPositiveY => 2, TexImageTarget::CubeMapNegativeY => 3, TexImageTarget::CubeMapPositiveZ => 4, TexImageTarget::CubeMapNegativeZ => 5, + _ => 0, } } - pub fn image_info_for_target(&self, - target: &TexImageTarget, - level: u32) -> ImageInfo { + pub fn image_info_for_target(&self, target: &TexImageTarget, level: u32) -> Option<ImageInfo> { let face_index = self.face_index_for_target(&target); self.image_info_at_face(face_index, level) } - pub fn image_info_at_face(&self, face: u8, level: u32) -> ImageInfo { + pub fn image_info_at_face(&self, face: u8, level: u32) -> Option<ImageInfo> { let pos = (level * self.face_count.get() as u32) + face as u32; self.image_info_array.borrow()[pos as usize] } @@ -348,44 +460,96 @@ impl WebGLTexture { fn set_image_infos_at_level_and_face(&self, level: u32, face: u8, image_info: ImageInfo) { debug_assert!(face < self.face_count.get()); let pos = (level * self.face_count.get() as u32) + face as u32; - self.image_info_array.borrow_mut()[pos as usize] = image_info; + self.image_info_array.borrow_mut()[pos as usize] = Some(image_info); } fn base_image_info(&self) -> Option<ImageInfo> { assert!((self.base_mipmap_level as usize) < MAX_LEVEL_COUNT); - Some(self.image_info_at_face(0, self.base_mipmap_level)) + self.image_info_at_face(0, self.base_mipmap_level) + } + + pub fn set_attached_to_dom(&self) { + self.attached_to_dom.set(true); + } + + pub fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) { + self.attached_framebuffer.set(Some(fb)); + } + + pub fn detach_from_framebuffer(&self) { + self.attached_framebuffer.set(None); + } + + pub fn storage( + &self, + target: TexImageTarget, + levels: u32, + internal_format: TexFormat, + width: u32, + height: u32, + depth: u32, + ) -> WebGLResult<()> { + // Handled by the caller + assert!(!self.is_immutable()); + assert!(self.target().is_some()); + + let target_id = target.as_gl_constant(); + let command = match target { + TexImageTarget::Texture2D | TexImageTarget::CubeMap => { + WebGLCommand::TexStorage2D(target_id, levels, internal_format, width, height) + }, + TexImageTarget::Texture3D | TexImageTarget::Texture2DArray => { + WebGLCommand::TexStorage3D(target_id, levels, internal_format, width, height, depth) + }, + _ => unreachable!(), // handled by the caller + }; + self.upcast::<WebGLObject>().context().send_command(command); + + let mut width = width; + let mut height = height; + let mut depth = depth; + for level in 0..levels { + let image_info = ImageInfo { + width, + height, + depth, + internal_format, + data_type: None, + }; + self.set_image_infos_at_level(level, image_info); + + width = cmp::max(1, width / 2); + height = cmp::max(1, height / 2); + depth = cmp::max(1, depth / 2); + } + + self.immutable_levels.set(Some(levels)); + + if let Some(fb) = self.attached_framebuffer.get() { + fb.update_status(); + } + + Ok(()) } } impl Drop for WebGLTexture { fn drop(&mut self) { - self.delete(); + self.delete(Operation::Fallible); } } -#[derive(Clone, Copy, PartialEq, Debug, JSTraceable, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub struct ImageInfo { width: u32, height: u32, depth: u32, - internal_format: Option<TexFormat>, - is_initialized: bool, + internal_format: TexFormat, data_type: Option<TexDataType>, } impl ImageInfo { - fn new() -> ImageInfo { - ImageInfo { - width: 0, - height: 0, - depth: 0, - internal_format: None, - is_initialized: false, - data_type: None, - } - } - pub fn width(&self) -> u32 { self.width } @@ -394,7 +558,7 @@ impl ImageInfo { self.height } - pub fn internal_format(&self) -> Option<TexFormat> { + pub fn internal_format(&self) -> TexFormat { self.internal_format } @@ -404,16 +568,8 @@ impl ImageInfo { fn is_power_of_two(&self) -> bool { self.width.is_power_of_two() && - self.height.is_power_of_two() && - self.depth.is_power_of_two() - } - - fn is_initialized(&self) -> bool { - self.is_initialized - } - - fn is_defined(&self) -> bool { - self.internal_format.is_some() + self.height.is_power_of_two() && + self.depth.is_power_of_two() } fn get_max_mimap_levels(&self) -> u32 { @@ -426,7 +582,21 @@ impl ImageInfo { } fn is_compressed_format(&self) -> bool { - // TODO: Once Servo supports compressed formats, check for them here - false + self.internal_format.is_compressed() } } + +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)] +pub enum TexCompressionValidation { + None, + S3TC, +} + +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)] +pub struct TexCompression { + pub format: TexFormat, + pub bytes_per_block: u8, + pub block_width: u8, + pub block_height: u8, + pub validation: TexCompressionValidation, +} diff --git a/components/script/dom/webgltransformfeedback.rs b/components/script/dom/webgltransformfeedback.rs new file mode 100644 index 00000000000..3b0b2abf0a2 --- /dev/null +++ b/components/script/dom/webgltransformfeedback.rs @@ -0,0 +1,130 @@ +/* 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 crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use canvas_traits::webgl::{webgl_channel, WebGLCommand}; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct WebGLTransformFeedback { + webgl_object: WebGLObject, + id: u32, + marked_for_deletion: Cell<bool>, + has_been_bound: Cell<bool>, + is_active: Cell<bool>, + is_paused: Cell<bool>, +} + +impl WebGLTransformFeedback { + fn new_inherited(context: &WebGLRenderingContext, id: u32) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), + id, + marked_for_deletion: Cell::new(false), + has_been_bound: Cell::new(false), + is_active: Cell::new(false), + is_paused: Cell::new(false), + } + } + + pub fn new(context: &WebGLRenderingContext) -> DomRoot<Self> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::CreateTransformFeedback(sender)); + let id = receiver.recv().unwrap(); + + reflect_dom_object( + Box::new(WebGLTransformFeedback::new_inherited(context, id)), + &*context.global(), + ) + } +} + +impl WebGLTransformFeedback { + pub fn bind(&self, context: &WebGLRenderingContext, target: u32) { + context.send_command(WebGLCommand::BindTransformFeedback(target, self.id())); + self.has_been_bound.set(true); + } + + pub fn begin(&self, context: &WebGLRenderingContext, primitive_mode: u32) { + if self.has_been_bound.get() && !self.is_active() { + context.send_command(WebGLCommand::BeginTransformFeedback(primitive_mode)); + self.set_active(true); + } + } + + pub fn end(&self, context: &WebGLRenderingContext) { + if self.has_been_bound.get() && self.is_active() { + if self.is_paused() { + context.send_command(WebGLCommand::ResumeTransformFeedback()); + } + context.send_command(WebGLCommand::EndTransformFeedback()); + self.set_active(false); + } + } + + pub fn resume(&self, context: &WebGLRenderingContext) { + if self.is_active() && self.is_paused() { + context.send_command(WebGLCommand::ResumeTransformFeedback()); + self.set_pause(false); + } + } + + pub fn pause(&self, context: &WebGLRenderingContext) { + if self.is_active() && !self.is_paused() { + context.send_command(WebGLCommand::PauseTransformFeedback()); + self.set_pause(true); + } + } + + pub fn id(&self) -> u32 { + self.id + } + + pub fn is_valid(&self) -> bool { + !self.marked_for_deletion.get() + } + + pub fn is_active(&self) -> bool { + self.is_active.get() + } + + pub fn is_paused(&self) -> bool { + self.is_paused.get() + } + + pub fn delete(&self, operation_fallibility: Operation) { + if self.is_valid() && self.id() != 0 { + self.marked_for_deletion.set(true); + let context = self.upcast::<WebGLObject>().context(); + let cmd = WebGLCommand::DeleteTransformFeedback(self.id); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(cmd), + Operation::Infallible => context.send_command(cmd), + } + } + } + + pub fn set_active(&self, value: bool) { + if self.is_valid() && self.has_been_bound.get() { + self.is_active.set(value); + } + } + + pub fn set_pause(&self, value: bool) { + if self.is_valid() && self.is_active() { + self.is_active.set(value); + } + } +} + +impl Drop for WebGLTransformFeedback { + fn drop(&mut self) { + self.delete(Operation::Fallible); + } +} diff --git a/components/script/dom/webgluniformlocation.rs b/components/script/dom/webgluniformlocation.rs index d76ab91ee5d..153b2e651ba 100644 --- a/components/script/dom/webgluniformlocation.rs +++ b/components/script/dom/webgluniformlocation.rs @@ -1,40 +1,66 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl -use dom::bindings::codegen::Bindings::WebGLUniformLocationBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::window::Window; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::window::Window; +use canvas_traits::webgl::WebGLContextId; +use canvas_traits::webgl::WebGLProgramId; use dom_struct::dom_struct; -use webrender_traits::WebGLProgramId; #[dom_struct] pub struct WebGLUniformLocation { reflector_: Reflector, id: i32, + context_id: WebGLContextId, program_id: WebGLProgramId, + link_generation: u64, + size: Option<i32>, + type_: u32, } impl WebGLUniformLocation { - fn new_inherited(id: i32, - program_id: WebGLProgramId) - -> WebGLUniformLocation { - WebGLUniformLocation { + fn new_inherited( + id: i32, + context_id: WebGLContextId, + program_id: WebGLProgramId, + link_generation: u64, + size: Option<i32>, + type_: u32, + ) -> Self { + Self { reflector_: Reflector::new(), - id: id, - program_id: program_id, + id, + context_id, + program_id, + link_generation, + size, + type_, } } - pub fn new(window: &Window, - id: i32, - program_id: WebGLProgramId) - -> Root<WebGLUniformLocation> { - reflect_dom_object(box WebGLUniformLocation::new_inherited(id, program_id), - window, - WebGLUniformLocationBinding::Wrap) + pub fn new( + window: &Window, + id: i32, + context_id: WebGLContextId, + program_id: WebGLProgramId, + link_generation: u64, + size: Option<i32>, + type_: u32, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(Self::new_inherited( + id, + context_id, + program_id, + link_generation, + size, + type_, + )), + window, + ) } pub fn id(&self) -> i32 { @@ -44,4 +70,20 @@ impl WebGLUniformLocation { pub fn program_id(&self) -> WebGLProgramId { self.program_id } + + pub fn context_id(&self) -> WebGLContextId { + self.context_id + } + + pub fn link_generation(&self) -> u64 { + self.link_generation + } + + pub fn size(&self) -> Option<i32> { + self.size + } + + pub fn type_(&self) -> u32 { + self.type_ + } } diff --git a/components/script/dom/webglvertexarrayobject.rs b/components/script/dom/webglvertexarrayobject.rs new file mode 100644 index 00000000000..07f53af0316 --- /dev/null +++ b/components/script/dom/webglvertexarrayobject.rs @@ -0,0 +1,102 @@ +/* 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 crate::dom::bindings::cell::Ref; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::vertexarrayobject::{VertexArrayObject, VertexAttribData}; +use crate::dom::webglbuffer::WebGLBuffer; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use canvas_traits::webgl::{ActiveAttribInfo, WebGLResult, WebGLVertexArrayId}; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct WebGLVertexArrayObject { + webgl_object_: WebGLObject, + array_object: VertexArrayObject, +} + +impl WebGLVertexArrayObject { + fn new_inherited(context: &WebGLRenderingContext, id: Option<WebGLVertexArrayId>) -> Self { + Self { + webgl_object_: WebGLObject::new_inherited(context), + array_object: VertexArrayObject::new(context, id), + } + } + + pub fn new(context: &WebGLRenderingContext, id: Option<WebGLVertexArrayId>) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLVertexArrayObject::new_inherited(context, id)), + &*context.global(), + ) + } + + pub fn id(&self) -> Option<WebGLVertexArrayId> { + self.array_object.id() + } + + pub fn is_deleted(&self) -> bool { + self.array_object.is_deleted() + } + + pub fn delete(&self, operation_fallibility: Operation) { + self.array_object.delete(operation_fallibility); + } + + pub fn ever_bound(&self) -> bool { + self.array_object.ever_bound() + } + + pub fn set_ever_bound(&self) { + self.array_object.set_ever_bound(); + } + + pub fn element_array_buffer(&self) -> &MutNullableDom<WebGLBuffer> { + self.array_object.element_array_buffer() + } + + pub fn get_vertex_attrib(&self, index: u32) -> Option<Ref<VertexAttribData>> { + self.array_object.get_vertex_attrib(index) + } + + pub fn set_vertex_attrib_type(&self, index: u32, type_: u32) { + self.array_object.set_vertex_attrib_type(index, type_); + } + + pub fn vertex_attrib_pointer( + &self, + index: u32, + size: i32, + type_: u32, + normalized: bool, + stride: i32, + offset: i64, + ) -> WebGLResult<()> { + self.array_object + .vertex_attrib_pointer(index, size, type_, normalized, stride, offset) + } + + pub fn vertex_attrib_divisor(&self, index: u32, value: u32) { + self.array_object.vertex_attrib_divisor(index, value); + } + + pub fn enabled_vertex_attrib_array(&self, index: u32, value: bool) { + self.array_object.enabled_vertex_attrib_array(index, value); + } + + pub fn unbind_buffer(&self, buffer: &WebGLBuffer) { + self.array_object.unbind_buffer(buffer); + } + + pub fn validate_for_draw( + &self, + required_len: u32, + instance_count: u32, + active_attribs: &[ActiveAttribInfo], + ) -> WebGLResult<()> { + self.array_object + .validate_for_draw(required_len, instance_count, active_attribs) + } +} diff --git a/components/script/dom/webglvertexarrayobjectoes.rs b/components/script/dom/webglvertexarrayobjectoes.rs new file mode 100644 index 00000000000..215d39c1a5a --- /dev/null +++ b/components/script/dom/webglvertexarrayobjectoes.rs @@ -0,0 +1,102 @@ +/* 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 crate::dom::bindings::cell::Ref; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::vertexarrayobject::{VertexArrayObject, VertexAttribData}; +use crate::dom::webglbuffer::WebGLBuffer; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use canvas_traits::webgl::{ActiveAttribInfo, WebGLResult, WebGLVertexArrayId}; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct WebGLVertexArrayObjectOES { + webgl_object_: WebGLObject, + array_object: VertexArrayObject, +} + +impl WebGLVertexArrayObjectOES { + fn new_inherited(context: &WebGLRenderingContext, id: Option<WebGLVertexArrayId>) -> Self { + Self { + webgl_object_: WebGLObject::new_inherited(context), + array_object: VertexArrayObject::new(context, id), + } + } + + pub fn new(context: &WebGLRenderingContext, id: Option<WebGLVertexArrayId>) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLVertexArrayObjectOES::new_inherited(context, id)), + &*context.global(), + ) + } + + pub fn id(&self) -> Option<WebGLVertexArrayId> { + self.array_object.id() + } + + pub fn is_deleted(&self) -> bool { + self.array_object.is_deleted() + } + + pub fn delete(&self, operation_fallibility: Operation) { + self.array_object.delete(operation_fallibility); + } + + pub fn ever_bound(&self) -> bool { + self.array_object.ever_bound() + } + + pub fn set_ever_bound(&self) { + self.array_object.set_ever_bound(); + } + + pub fn element_array_buffer(&self) -> &MutNullableDom<WebGLBuffer> { + self.array_object.element_array_buffer() + } + + pub fn get_vertex_attrib(&self, index: u32) -> Option<Ref<VertexAttribData>> { + self.array_object.get_vertex_attrib(index) + } + + pub fn set_vertex_attrib_type(&self, index: u32, type_: u32) { + self.array_object.set_vertex_attrib_type(index, type_); + } + + pub fn vertex_attrib_pointer( + &self, + index: u32, + size: i32, + type_: u32, + normalized: bool, + stride: i32, + offset: i64, + ) -> WebGLResult<()> { + self.array_object + .vertex_attrib_pointer(index, size, type_, normalized, stride, offset) + } + + pub fn vertex_attrib_divisor(&self, index: u32, value: u32) { + self.array_object.vertex_attrib_divisor(index, value); + } + + pub fn enabled_vertex_attrib_array(&self, index: u32, value: bool) { + self.array_object.enabled_vertex_attrib_array(index, value); + } + + pub fn unbind_buffer(&self, buffer: &WebGLBuffer) { + self.array_object.unbind_buffer(buffer); + } + + pub fn validate_for_draw( + &self, + required_len: u32, + instance_count: u32, + active_attribs: &[ActiveAttribInfo], + ) -> WebGLResult<()> { + self.array_object + .validate_for_draw(required_len, instance_count, active_attribs) + } +} diff --git a/components/script/dom/webidls/ANGLEInstancedArrays.webidl b/components/script/dom/webidls/ANGLEInstancedArrays.webidl new file mode 100644 index 00000000000..fc7e5d3efab --- /dev/null +++ b/components/script/dom/webidls/ANGLEInstancedArrays.webidl @@ -0,0 +1,15 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/ + */ + +[NoInterfaceObject, Exposed=Window] +interface ANGLEInstancedArrays { + const GLenum VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE = 0x88FE; + void drawArraysInstancedANGLE(GLenum mode, GLint first, GLsizei count, GLsizei primcount); + void drawElementsInstancedANGLE(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei primcount); + void vertexAttribDivisorANGLE(GLuint index, GLuint divisor); +}; diff --git a/components/script/dom/webidls/ActivatableElement.webidl b/components/script/dom/webidls/ActivatableElement.webidl index 2b69baff303..bce7730a833 100644 --- a/components/script/dom/webidls/ActivatableElement.webidl +++ b/components/script/dom/webidls/ActivatableElement.webidl @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // Interface for testing element activation // This interface is entirely internal to Servo, and should not be accessible to // web pages. -[Exposed=(Window,Worker), NoInterfaceObject] -interface ActivatableElement { +[Exposed=(Window,Worker)] +interface mixin ActivatableElement { [Throws, Pref="dom.testing.element.activation.enabled"] void enterFormalActivationState(); diff --git a/components/script/dom/webidls/AnalyserNode.webidl b/components/script/dom/webidls/AnalyserNode.webidl new file mode 100644 index 00000000000..c1398d56378 --- /dev/null +++ b/components/script/dom/webidls/AnalyserNode.webidl @@ -0,0 +1,28 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#analysernode + */ + +dictionary AnalyserOptions : AudioNodeOptions { + unsigned long fftSize = 2048; + double maxDecibels = -30; + double minDecibels = -100; + double smoothingTimeConstant = 0.8; +}; + +[Exposed=Window] +interface AnalyserNode : AudioNode { + [Throws] constructor(BaseAudioContext context, optional AnalyserOptions options = {}); + void getFloatFrequencyData (Float32Array array); + void getByteFrequencyData (Uint8Array array); + void getFloatTimeDomainData (Float32Array array); + void getByteTimeDomainData (Uint8Array array); + [SetterThrows] attribute unsigned long fftSize; + readonly attribute unsigned long frequencyBinCount; + [SetterThrows] attribute double minDecibels; + [SetterThrows] attribute double maxDecibels; + [SetterThrows] attribute double smoothingTimeConstant; +}; diff --git a/components/script/dom/webidls/AnimationEvent.webidl b/components/script/dom/webidls/AnimationEvent.webidl new file mode 100644 index 00000000000..fd9d6c47f7e --- /dev/null +++ b/components/script/dom/webidls/AnimationEvent.webidl @@ -0,0 +1,26 @@ +/* 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 http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * http://www.w3.org/TR/css3-animations/#animation-events- + * http://dev.w3.org/csswg/css3-animations/#animation-events- + * + * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C + * liability, trademark and document use rules apply. + */ + +[Exposed=Window] +interface AnimationEvent : Event { + constructor(DOMString type, optional AnimationEventInit eventInitDict = {}); + + readonly attribute DOMString animationName; + readonly attribute float elapsedTime; + readonly attribute DOMString pseudoElement; +}; + +dictionary AnimationEventInit : EventInit { + DOMString animationName = ""; + float elapsedTime = 0; + DOMString pseudoElement = ""; +}; diff --git a/components/script/dom/webidls/Attr.webidl b/components/script/dom/webidls/Attr.webidl index 79449804081..f56f9104e65 100644 --- a/components/script/dom/webidls/Attr.webidl +++ b/components/script/dom/webidls/Attr.webidl @@ -1,13 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#interface-attr * */ -interface Attr { +[Exposed=Window] +interface Attr : Node { [Constant] readonly attribute DOMString? namespaceURI; [Constant] @@ -16,14 +17,8 @@ interface Attr { readonly attribute DOMString localName; [Constant] readonly attribute DOMString name; - [Constant] - readonly attribute DOMString nodeName; // historical alias of .name - [Pure] + [CEReactions, Pure] attribute DOMString value; - [Pure] - attribute DOMString textContent; // historical alias of .value - [Pure] - attribute DOMString nodeValue; // historical alias of .value [Pure] readonly attribute Element? ownerElement; diff --git a/components/script/dom/webidls/AudioBuffer.webidl b/components/script/dom/webidls/AudioBuffer.webidl new file mode 100644 index 00000000000..75b3b470838 --- /dev/null +++ b/components/script/dom/webidls/AudioBuffer.webidl @@ -0,0 +1,29 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#audiobuffer + */ + +dictionary AudioBufferOptions { + unsigned long numberOfChannels = 1; + required unsigned long length; + required float sampleRate; +}; + +[Exposed=Window] +interface AudioBuffer { + [Throws] constructor(AudioBufferOptions options); + readonly attribute float sampleRate; + readonly attribute unsigned long length; + readonly attribute double duration; + readonly attribute unsigned long numberOfChannels; + [Throws] Float32Array getChannelData(unsigned long channel); + [Throws] void copyFromChannel(Float32Array destination, + unsigned long channelNumber, + optional unsigned long startInChannel = 0); + [Throws] void copyToChannel(Float32Array source, + unsigned long channelNumber, + optional unsigned long startInChannel = 0); +}; diff --git a/components/script/dom/webidls/AudioBufferSourceNode.webidl b/components/script/dom/webidls/AudioBufferSourceNode.webidl new file mode 100644 index 00000000000..8744a521ddb --- /dev/null +++ b/components/script/dom/webidls/AudioBufferSourceNode.webidl @@ -0,0 +1,30 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#AudioBufferSourceNode + */ + +dictionary AudioBufferSourceOptions { + AudioBuffer? buffer; + float detune = 0; + boolean loop = false; + double loopEnd = 0; + double loopStart = 0; + float playbackRate = 1; +}; + +[Exposed=Window] +interface AudioBufferSourceNode : AudioScheduledSourceNode { + [Throws] constructor(BaseAudioContext context, optional AudioBufferSourceOptions options = {}); + [Throws] attribute AudioBuffer? buffer; + readonly attribute AudioParam playbackRate; + readonly attribute AudioParam detune; + attribute boolean loop; + attribute double loopStart; + attribute double loopEnd; + [Throws] void start(optional double when = 0, + optional double offset, + optional double duration); +}; diff --git a/components/script/dom/webidls/AudioContext.webidl b/components/script/dom/webidls/AudioContext.webidl new file mode 100644 index 00000000000..09d6693b690 --- /dev/null +++ b/components/script/dom/webidls/AudioContext.webidl @@ -0,0 +1,40 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#dom-audiocontext + */ + +enum AudioContextLatencyCategory { + "balanced", + "interactive", + "playback" +}; + +dictionary AudioContextOptions { + (AudioContextLatencyCategory or double) latencyHint = "interactive"; + float sampleRate; +}; + +dictionary AudioTimestamp { + double contextTime; + DOMHighResTimeStamp performanceTime; +}; + +[Exposed=Window] +interface AudioContext : BaseAudioContext { + [Throws] constructor(optional AudioContextOptions contextOptions = {}); + readonly attribute double baseLatency; + readonly attribute double outputLatency; + + AudioTimestamp getOutputTimestamp(); + + Promise<void> suspend(); + Promise<void> close(); + + [Throws] MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement); + [Throws] MediaStreamAudioSourceNode createMediaStreamSource(MediaStream mediaStream); + [Throws] MediaStreamTrackAudioSourceNode createMediaStreamTrackSource(MediaStreamTrack mediaStreamTrack); + [Throws] MediaStreamAudioDestinationNode createMediaStreamDestination(); +}; diff --git a/components/script/dom/webidls/AudioDestinationNode.webidl b/components/script/dom/webidls/AudioDestinationNode.webidl new file mode 100644 index 00000000000..572c7d954f0 --- /dev/null +++ b/components/script/dom/webidls/AudioDestinationNode.webidl @@ -0,0 +1,12 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#dom-audiodestinationnode + */ + +[Exposed=Window] +interface AudioDestinationNode : AudioNode { + readonly attribute unsigned long maxChannelCount; +}; diff --git a/components/script/dom/webidls/AudioListener.webidl b/components/script/dom/webidls/AudioListener.webidl new file mode 100644 index 00000000000..d625740802f --- /dev/null +++ b/components/script/dom/webidls/AudioListener.webidl @@ -0,0 +1,22 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#audiolistener + */ + +[Exposed=Window] +interface AudioListener { + readonly attribute AudioParam positionX; + readonly attribute AudioParam positionY; + readonly attribute AudioParam positionZ; + readonly attribute AudioParam forwardX; + readonly attribute AudioParam forwardY; + readonly attribute AudioParam forwardZ; + readonly attribute AudioParam upX; + readonly attribute AudioParam upY; + readonly attribute AudioParam upZ; + [Throws] AudioListener setPosition (float x, float y, float z); + [Throws] AudioListener setOrientation (float x, float y, float z, float xUp, float yUp, float zUp); +}; diff --git a/components/script/dom/webidls/AudioNode.webidl b/components/script/dom/webidls/AudioNode.webidl new file mode 100644 index 00000000000..bf4f88e02b6 --- /dev/null +++ b/components/script/dom/webidls/AudioNode.webidl @@ -0,0 +1,62 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#dom-audionode + */ + +enum ChannelCountMode { + "max", + "clamped-max", + "explicit" +}; + +enum ChannelInterpretation { + "speakers", + "discrete" +}; + +dictionary AudioNodeOptions { + unsigned long channelCount; + ChannelCountMode channelCountMode; + ChannelInterpretation channelInterpretation; +}; + +[Exposed=Window] +interface AudioNode : EventTarget { + [Throws] + AudioNode connect(AudioNode destinationNode, + optional unsigned long output = 0, + optional unsigned long input = 0); + [Throws] + void connect(AudioParam destinationParam, + optional unsigned long output = 0); + [Throws] + void disconnect(); + [Throws] + void disconnect(unsigned long output); + [Throws] + void disconnect(AudioNode destination); + [Throws] + void disconnect(AudioNode destination, unsigned long output); + [Throws] + void disconnect(AudioNode destination, + unsigned long output, + unsigned long input); + [Throws] + void disconnect(AudioParam destination); + [Throws] + void disconnect(AudioParam destination, unsigned long output); + + readonly attribute BaseAudioContext context; + readonly attribute unsigned long numberOfInputs; + readonly attribute unsigned long numberOfOutputs; + + [SetterThrows] + attribute unsigned long channelCount; + [SetterThrows] + attribute ChannelCountMode channelCountMode; + [SetterThrows] + attribute ChannelInterpretation channelInterpretation; +}; diff --git a/components/script/dom/webidls/AudioParam.webidl b/components/script/dom/webidls/AudioParam.webidl new file mode 100644 index 00000000000..42f1012539c --- /dev/null +++ b/components/script/dom/webidls/AudioParam.webidl @@ -0,0 +1,32 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#dom-audioparam + */ + +enum AutomationRate { + "a-rate", + "k-rate" +}; + +[Exposed=Window] +interface AudioParam { + attribute float value; + attribute AutomationRate automationRate; + readonly attribute float defaultValue; + readonly attribute float minValue; + readonly attribute float maxValue; + [Throws] AudioParam setValueAtTime(float value, double startTime); + [Throws] AudioParam linearRampToValueAtTime(float value, double endTime); + [Throws] AudioParam exponentialRampToValueAtTime(float value, double endTime); + [Throws] AudioParam setTargetAtTime(float target, + double startTime, + float timeConstant); + [Throws] AudioParam setValueCurveAtTime(sequence<float> values, + double startTime, + double duration); + [Throws] AudioParam cancelScheduledValues(double cancelTime); + [Throws] AudioParam cancelAndHoldAtTime(double cancelTime); +}; diff --git a/components/script/dom/webidls/AudioScheduledSourceNode.webidl b/components/script/dom/webidls/AudioScheduledSourceNode.webidl new file mode 100644 index 00000000000..6be6373a7ec --- /dev/null +++ b/components/script/dom/webidls/AudioScheduledSourceNode.webidl @@ -0,0 +1,14 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#AudioScheduledSourceNode + */ + +[Exposed=Window] +interface AudioScheduledSourceNode : AudioNode { + attribute EventHandler onended; + [Throws] void start(optional double when = 0); + [Throws] void stop(optional double when = 0); +}; diff --git a/components/script/dom/webidls/AudioTrack.webidl b/components/script/dom/webidls/AudioTrack.webidl new file mode 100644 index 00000000000..2fa2ec9a5fa --- /dev/null +++ b/components/script/dom/webidls/AudioTrack.webidl @@ -0,0 +1,14 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#audiotrack + +[Exposed=Window] +interface AudioTrack { + readonly attribute DOMString id; + readonly attribute DOMString kind; + readonly attribute DOMString label; + readonly attribute DOMString language; + attribute boolean enabled; +}; diff --git a/components/script/dom/webidls/AudioTrackList.webidl b/components/script/dom/webidls/AudioTrackList.webidl new file mode 100644 index 00000000000..4428776972c --- /dev/null +++ b/components/script/dom/webidls/AudioTrackList.webidl @@ -0,0 +1,16 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#audiotracklist + +[Exposed=Window] +interface AudioTrackList : EventTarget { + readonly attribute unsigned long length; + getter AudioTrack (unsigned long index); + AudioTrack? getTrackById(DOMString id); + + attribute EventHandler onchange; + attribute EventHandler onaddtrack; + attribute EventHandler onremovetrack; +}; diff --git a/components/script/dom/webidls/BaseAudioContext.webidl b/components/script/dom/webidls/BaseAudioContext.webidl new file mode 100644 index 00000000000..57fb677defd --- /dev/null +++ b/components/script/dom/webidls/BaseAudioContext.webidl @@ -0,0 +1,55 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#BaseAudioContext + */ + +enum AudioContextState { + "suspended", + "running", + "closed" +}; + +callback DecodeErrorCallback = void (DOMException error); +callback DecodeSuccessCallback = void (AudioBuffer decodedData); + +[Exposed=Window] +interface BaseAudioContext : EventTarget { + readonly attribute AudioDestinationNode destination; + readonly attribute float sampleRate; + readonly attribute double currentTime; + readonly attribute AudioListener listener; + readonly attribute AudioContextState state; + Promise<void> resume(); + attribute EventHandler onstatechange; + [Throws] AudioBuffer createBuffer(unsigned long numberOfChannels, + unsigned long length, + float sampleRate); + Promise<AudioBuffer> decodeAudioData(ArrayBuffer audioData, + optional DecodeSuccessCallback successCallback, + optional DecodeErrorCallback errorCallback); + [Throws] AudioBufferSourceNode createBufferSource(); + [Throws] ConstantSourceNode createConstantSource(); + // ScriptProcessorNode createScriptProcessor(optional unsigned long bufferSize = 0, + // optional unsigned long numberOfInputChannels = 2, + // optional unsigned long numberOfOutputChannels = 2); + [Throws] AnalyserNode createAnalyser(); + [Throws] GainNode createGain(); + // DelayNode createDelay(optional double maxDelayTime = 1); + [Throws] BiquadFilterNode createBiquadFilter(); + // IIRFilterNode createIIRFilter(sequence<double> feedforward, + // sequence<double> feedback); + // WaveShaperNode createWaveShaper(); + [Throws] PannerNode createPanner(); + [Throws] StereoPannerNode createStereoPanner(); + // ConvolverNode createConvolver(); + [Throws] ChannelSplitterNode createChannelSplitter(optional unsigned long numberOfOutputs = 6); + [Throws] ChannelMergerNode createChannelMerger(optional unsigned long numberOfInputs = 6); + // DynamicsCompressorNode createDynamicsCompressor(); + [Throws] OscillatorNode createOscillator(); + // PeriodicWave createPeriodicWave(sequence<float> real, + // sequence<float> imag, + // optional PeriodicWaveConstraints constraints); +}; diff --git a/components/script/dom/webidls/BeforeUnloadEvent.webidl b/components/script/dom/webidls/BeforeUnloadEvent.webidl index 71fe9396d48..d5aee92901c 100644 --- a/components/script/dom/webidls/BeforeUnloadEvent.webidl +++ b/components/script/dom/webidls/BeforeUnloadEvent.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * For more information on this interface please see * https://html.spec.whatwg.org/multipage/#beforeunloadevent diff --git a/components/script/dom/webidls/BiquadFilterNode.webidl b/components/script/dom/webidls/BiquadFilterNode.webidl new file mode 100644 index 00000000000..d1b5450338c --- /dev/null +++ b/components/script/dom/webidls/BiquadFilterNode.webidl @@ -0,0 +1,39 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#biquadfilternode + */ + +enum BiquadFilterType { + "lowpass", + "highpass", + "bandpass", + "lowshelf", + "highshelf", + "peaking", + "notch", + "allpass" +}; + +dictionary BiquadFilterOptions : AudioNodeOptions { + BiquadFilterType type = "lowpass"; + float Q = 1; + float detune = 0; + float frequency = 350; + float gain = 0; +}; + +[Exposed=Window] +interface BiquadFilterNode : AudioNode { + [Throws] constructor(BaseAudioContext context, optional BiquadFilterOptions options = {}); + attribute BiquadFilterType type; + readonly attribute AudioParam frequency; + readonly attribute AudioParam detune; + readonly attribute AudioParam Q; + readonly attribute AudioParam gain; + // the AudioParam model of https://github.com/servo/servo/issues/21659 needs to + // be implemented before we implement this + // void getFrequencyResponse (Float32Array frequencyHz, Float32Array magResponse, Float32Array phaseResponse); +}; diff --git a/components/script/dom/webidls/Blob.webidl b/components/script/dom/webidls/Blob.webidl index 18a009d39a9..572879ec621 100644 --- a/components/script/dom/webidls/Blob.webidl +++ b/components/script/dom/webidls/Blob.webidl @@ -1,25 +1,29 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/FileAPI/#blob -[Constructor(optional sequence<BlobPart> blobParts, - optional BlobPropertyBag options), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface Blob { + [Throws] constructor(optional sequence<BlobPart> blobParts, + optional BlobPropertyBag options = {}); readonly attribute unsigned long long size; readonly attribute DOMString type; // slice Blob into byte-ranged chunks - Blob slice([Clamp] optional long long start, - [Clamp] optional long long end, + Blob slice(optional [Clamp] long long start, + optional [Clamp] long long end, optional DOMString contentType); + + [NewObject] object stream(); + [NewObject] Promise<DOMString> text(); + [NewObject] Promise<ArrayBuffer> arrayBuffer(); }; dictionary BlobPropertyBag { DOMString type = ""; }; -typedef (/*ArrayBuffer or ArrayBufferView or */Blob or DOMString) BlobPart; +typedef (ArrayBuffer or ArrayBufferView or Blob or DOMString) BlobPart; diff --git a/components/script/dom/webidls/Bluetooth.webidl b/components/script/dom/webidls/Bluetooth.webidl index 6299e661474..e5e135c0577 100644 --- a/components/script/dom/webidls/Bluetooth.webidl +++ b/components/script/dom/webidls/Bluetooth.webidl @@ -1,14 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://webbluetoothcg.github.io/web-bluetooth/#bluetooth dictionary BluetoothDataFilterInit { - // BufferSource dataPrefix; - sequence<octet> dataPrefix; - // BufferSource mask; - sequence<octet> mask; + BufferSource dataPrefix; + BufferSource mask; }; dictionary BluetoothLEScanFilterInit { @@ -16,18 +14,18 @@ dictionary BluetoothLEScanFilterInit { DOMString name; DOMString namePrefix; // Maps unsigned shorts to BluetoothDataFilters. - MozMap<BluetoothDataFilterInit> manufacturerData; + record<DOMString, BluetoothDataFilterInit> manufacturerData; // Maps BluetoothServiceUUIDs to BluetoothDataFilters. - MozMap<BluetoothDataFilterInit> serviceData; + record<DOMString, BluetoothDataFilterInit> serviceData; }; dictionary RequestDeviceOptions { sequence<BluetoothLEScanFilterInit> filters; - sequence<BluetoothServiceUUID> optionalServices /*= []*/; + sequence<BluetoothServiceUUID> optionalServices = []; boolean acceptAllDevices = false; }; -[Pref="dom.bluetooth.enabled"] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface Bluetooth : EventTarget { [SecureContext] Promise<boolean> getAvailability(); @@ -36,9 +34,9 @@ interface Bluetooth : EventTarget { // [SecureContext, SameObject] // readonly attribute BluetoothDevice? referringDevice; [SecureContext] - Promise<BluetoothDevice> requestDevice(optional RequestDeviceOptions options); + Promise<BluetoothDevice> requestDevice(optional RequestDeviceOptions options = {}); }; -// Bluetooth implements BluetoothDeviceEventHandlers; -// Bluetooth implements CharacteristicEventHandlers; -// Bluetooth implements ServiceEventHandlers; +// Bluetooth includes BluetoothDeviceEventHandlers; +// Bluetooth includes CharacteristicEventHandlers; +// Bluetooth includes ServiceEventHandlers; diff --git a/components/script/dom/webidls/BluetoothAdvertisingEvent.webidl b/components/script/dom/webidls/BluetoothAdvertisingEvent.webidl index a6a86a393a9..a7dd3fe947f 100644 --- a/components/script/dom/webidls/BluetoothAdvertisingEvent.webidl +++ b/components/script/dom/webidls/BluetoothAdvertisingEvent.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://webbluetoothcg.github.io/web-bluetooth/#advertising-events @@ -10,8 +10,9 @@ interface BluetoothServiceDataMap { readonly maplike<UUID, DataView>; };*/ -[Pref="dom.bluetooth.enabled", Constructor(DOMString type, BluetoothAdvertisingEventInit init)] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface BluetoothAdvertisingEvent : Event { + [Throws] constructor(DOMString type, BluetoothAdvertisingEventInit init); [SameObject] readonly attribute BluetoothDevice device; // readonly attribute FrozenArray<UUID> uuids; diff --git a/components/script/dom/webidls/BluetoothCharacteristicProperties.webidl b/components/script/dom/webidls/BluetoothCharacteristicProperties.webidl index 01079511d84..124f881a2e0 100644 --- a/components/script/dom/webidls/BluetoothCharacteristicProperties.webidl +++ b/components/script/dom/webidls/BluetoothCharacteristicProperties.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://webbluetoothcg.github.io/web-bluetooth/#characteristicproperties -[Pref="dom.bluetooth.enabled"] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface BluetoothCharacteristicProperties { readonly attribute boolean broadcast; readonly attribute boolean read; diff --git a/components/script/dom/webidls/BluetoothDevice.webidl b/components/script/dom/webidls/BluetoothDevice.webidl index 1eb9f495ec0..8ead2168146 100644 --- a/components/script/dom/webidls/BluetoothDevice.webidl +++ b/components/script/dom/webidls/BluetoothDevice.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdevice -[Pref="dom.bluetooth.enabled"] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface BluetoothDevice : EventTarget { readonly attribute DOMString id; readonly attribute DOMString? name; @@ -15,12 +15,11 @@ interface BluetoothDevice : EventTarget { readonly attribute boolean watchingAdvertisements; }; -[NoInterfaceObject] -interface BluetoothDeviceEventHandlers { +interface mixin BluetoothDeviceEventHandlers { attribute EventHandler ongattserverdisconnected; }; -// BluetoothDevice implements EventTarget; -BluetoothDevice implements BluetoothDeviceEventHandlers; -// BluetoothDevice implements CharacteristicEventHandlers; -// BluetoothDevice implements ServiceEventHandlers; +// BluetoothDevice includes EventTarget; +BluetoothDevice includes BluetoothDeviceEventHandlers; +// BluetoothDevice includes CharacteristicEventHandlers; +// BluetoothDevice includes ServiceEventHandlers; diff --git a/components/script/dom/webidls/BluetoothPermissionResult.webidl b/components/script/dom/webidls/BluetoothPermissionResult.webidl index 3ac0685b3d0..95c06797aef 100644 --- a/components/script/dom/webidls/BluetoothPermissionResult.webidl +++ b/components/script/dom/webidls/BluetoothPermissionResult.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothpermissionresult @@ -8,11 +8,11 @@ dictionary BluetoothPermissionDescriptor : PermissionDescriptor { DOMString deviceId; // These match RequestDeviceOptions. sequence<BluetoothLEScanFilterInit> filters; - sequence<BluetoothServiceUUID> optionalServices/* = []*/; + sequence<BluetoothServiceUUID> optionalServices = []; boolean acceptAllDevices = false; }; -[Pref="dom.bluetooth.enabled"] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface BluetoothPermissionResult : PermissionStatus { // attribute FrozenArray<BluetoothDevice> devices; // Workaround until FrozenArray get implemented. diff --git a/components/script/dom/webidls/BluetoothRemoteGATTCharacteristic.webidl b/components/script/dom/webidls/BluetoothRemoteGATTCharacteristic.webidl index 3e086fc21ca..af85d1c1860 100644 --- a/components/script/dom/webidls/BluetoothRemoteGATTCharacteristic.webidl +++ b/components/script/dom/webidls/BluetoothRemoteGATTCharacteristic.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattcharacteristic -[Pref="dom.bluetooth.enabled"] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface BluetoothRemoteGATTCharacteristic : EventTarget { [SameObject] readonly attribute BluetoothRemoteGATTService service; @@ -16,16 +16,14 @@ interface BluetoothRemoteGATTCharacteristic : EventTarget { getDescriptors(optional BluetoothDescriptorUUID descriptor); Promise<ByteString> readValue(); //Promise<DataView> readValue(); - Promise<void> writeValue(sequence<octet> value); - //Promise<void> writeValue(BufferSource value); + Promise<void> writeValue(BufferSource value); Promise<BluetoothRemoteGATTCharacteristic> startNotifications(); Promise<BluetoothRemoteGATTCharacteristic> stopNotifications(); }; -[NoInterfaceObject] -interface CharacteristicEventHandlers { +interface mixin CharacteristicEventHandlers { attribute EventHandler oncharacteristicvaluechanged; }; -// BluetoothRemoteGATTCharacteristic implements EventTarget; -BluetoothRemoteGATTCharacteristic implements CharacteristicEventHandlers; +// BluetoothRemoteGATTCharacteristic includes EventTarget; +BluetoothRemoteGATTCharacteristic includes CharacteristicEventHandlers; diff --git a/components/script/dom/webidls/BluetoothRemoteGATTDescriptor.webidl b/components/script/dom/webidls/BluetoothRemoteGATTDescriptor.webidl index a202975013c..37c8722e224 100644 --- a/components/script/dom/webidls/BluetoothRemoteGATTDescriptor.webidl +++ b/components/script/dom/webidls/BluetoothRemoteGATTDescriptor.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // http://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattdescriptor -[Pref="dom.bluetooth.enabled"] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface BluetoothRemoteGATTDescriptor { [SameObject] readonly attribute BluetoothRemoteGATTCharacteristic characteristic; @@ -12,6 +12,5 @@ interface BluetoothRemoteGATTDescriptor { readonly attribute ByteString? value; Promise<ByteString> readValue(); //Promise<DataView> readValue(); - Promise<void> writeValue(sequence<octet> value); - //Promise<void> writeValue(BufferSource value); + Promise<void> writeValue(BufferSource value); }; diff --git a/components/script/dom/webidls/BluetoothRemoteGATTServer.webidl b/components/script/dom/webidls/BluetoothRemoteGATTServer.webidl index cb735cdc66d..324750dc39b 100644 --- a/components/script/dom/webidls/BluetoothRemoteGATTServer.webidl +++ b/components/script/dom/webidls/BluetoothRemoteGATTServer.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattserver -[Pref="dom.bluetooth.enabled"] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface BluetoothRemoteGATTServer { [SameObject] readonly attribute BluetoothDevice device; diff --git a/components/script/dom/webidls/BluetoothRemoteGATTService.webidl b/components/script/dom/webidls/BluetoothRemoteGATTService.webidl index 41a6fdfef3a..7e9f624dd1c 100644 --- a/components/script/dom/webidls/BluetoothRemoteGATTService.webidl +++ b/components/script/dom/webidls/BluetoothRemoteGATTService.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothremotegattservice -[Pref="dom.bluetooth.enabled"] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface BluetoothRemoteGATTService : EventTarget { [SameObject] readonly attribute BluetoothDevice device; @@ -17,13 +17,12 @@ interface BluetoothRemoteGATTService : EventTarget { Promise<sequence<BluetoothRemoteGATTService>> getIncludedServices(optional BluetoothServiceUUID service); }; -[NoInterfaceObject] -interface ServiceEventHandlers { +interface mixin ServiceEventHandlers { attribute EventHandler onserviceadded; attribute EventHandler onservicechanged; attribute EventHandler onserviceremoved; }; -// BluetoothRemoteGATTService implements EventTarget; -// BluetoothRemoteGATTService implements CharacteristicEventHandlers; -BluetoothRemoteGATTService implements ServiceEventHandlers; +// BluetoothRemoteGATTService includes EventTarget; +// BluetoothRemoteGATTService includes CharacteristicEventHandlers; +BluetoothRemoteGATTService includes ServiceEventHandlers; diff --git a/components/script/dom/webidls/BluetoothUUID.webidl b/components/script/dom/webidls/BluetoothUUID.webidl index eca9710eeb6..dde82d3acb7 100644 --- a/components/script/dom/webidls/BluetoothUUID.webidl +++ b/components/script/dom/webidls/BluetoothUUID.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://webbluetoothcg.github.io/web-bluetooth/#bluetoothuuid -[Pref="dom.bluetooth.enabled"] +[Exposed=Window, Pref="dom.bluetooth.enabled"] interface BluetoothUUID { [Throws] static UUID getService(BluetoothServiceUUID name); diff --git a/components/script/dom/webidls/Body.webidl b/components/script/dom/webidls/Body.webidl index bb7aa5c6859..5de4aa36813 100644 --- a/components/script/dom/webidls/Body.webidl +++ b/components/script/dom/webidls/Body.webidl @@ -1,16 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://fetch.spec.whatwg.org/#body -[NoInterfaceObject, - Exposed=(Window,Worker)] - -interface Body { +[Exposed=(Window,Worker)] +interface mixin Body { readonly attribute boolean bodyUsed; + readonly attribute object? body; - // [NewObject] Promise<ArrayBuffer> arrayBuffer(); + [NewObject] Promise<ArrayBuffer> arrayBuffer(); [NewObject] Promise<Blob> blob(); [NewObject] Promise<FormData> formData(); [NewObject] Promise<any> json(); diff --git a/components/script/dom/webidls/BroadcastChannel.webidl b/components/script/dom/webidls/BroadcastChannel.webidl new file mode 100644 index 00000000000..6d72f3997cf --- /dev/null +++ b/components/script/dom/webidls/BroadcastChannel.webidl @@ -0,0 +1,18 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is: + * https://html.spec.whatwg.org/multipage/#broadcastchannel + */ + +[Exposed=(Window,Worker)] +interface BroadcastChannel : EventTarget { + constructor(DOMString name); + + readonly attribute DOMString name; + [Throws] void postMessage(any message); + void close(); + attribute EventHandler onmessage; + attribute EventHandler onmessageerror; +}; diff --git a/components/script/dom/webidls/BrowserElement.webidl b/components/script/dom/webidls/BrowserElement.webidl deleted file mode 100644 index 4c6273a1cef..00000000000 --- a/components/script/dom/webidls/BrowserElement.webidl +++ /dev/null @@ -1,227 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API - -callback BrowserElementNextPaintEventCallback = void (); - -//enum BrowserFindCaseSensitivity { "case-sensitive", "case-insensitive" }; -//enum BrowserFindDirection { "forward", "backward" }; - -//dictionary BrowserElementDownloadOptions { -// DOMString? filename; -// DOMString? referrer; -//}; - -//dictionary BrowserElementExecuteScriptOptions { -// DOMString? url; -// DOMString? origin; -//}; - -[NoInterfaceObject, Exposed=(Window,Worker)] -interface BrowserElement { -}; - -dictionary BrowserElementSecurityChangeDetail { - - // state: - // "insecure" indicates that the data corresponding to - // the request was received over an insecure channel. - // - // "broken" indicates an unknown security state. This - // may mean that the request is being loaded as part - // of a page in which some content was received over - // an insecure channel. - // - // "secure" indicates that the data corresponding to the - // request was received over a secure channel. - DOMString state; - - // trackingState: - // "loaded_tracking_content": tracking content has been loaded. - // "blocked_tracking_content": tracking content has been blocked from loading. - DOMString trackingState; - - // mixedState: - // "blocked_mixed_active_content": Mixed active content has been blocked from loading. - // "loaded_mixed_active_content": Mixed active content has been loaded. - DOMString mixedState; - - boolean extendedValidation; - boolean trackingContent; - boolean mixedContent; -}; - -dictionary BrowserElementErrorEventDetail { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsererror - // just requires a "type" field, but we also provide - // an optional human-readable description, and - // an optional machine-readable report (e.g. a backtrace for panics) - DOMString type; - DOMString description; - DOMString report; - DOMString version; -}; - -dictionary BrowserElementLocationChangeEventDetail { - DOMString url; - boolean canGoBack; - boolean canGoForward; -}; - -dictionary BrowserElementIconChangeEventDetail { - DOMString rel; - DOMString href; - DOMString sizes; -}; - -dictionary BrowserShowModalPromptEventDetail { - DOMString promptType; - DOMString title; - DOMString message; - DOMString returnValue; - // TODO(simartin) unblock() callback -}; - -dictionary BrowserElementOpenTabEventDetail { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowseropentab - DOMString url; -}; - -dictionary BrowserElementOpenWindowEventDetail { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowseropenwindow - DOMString url; - DOMString target; - DOMString features; - // Element frameElement; -}; - -dictionary BrowserElementVisibilityChangeEventDetail { - boolean visible; -}; - -BrowserElement implements BrowserElementCommon; -BrowserElement implements BrowserElementPrivileged; - -[NoInterfaceObject, Exposed=(Window,Worker)] -interface BrowserElementCommon { - [Throws, - Pref="dom.mozbrowser.enabled"] - void setVisible(boolean visible); - - [Throws, - Pref="dom.mozbrowser.enabled"] - boolean getVisible(); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //void setActive(boolean active); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //boolean getActive(); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //void addNextPaintListener(BrowserElementNextPaintEventCallback listener); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //void removeNextPaintListener(BrowserElementNextPaintEventCallback listener); -}; - -[NoInterfaceObject, Exposed=(Window,Worker)] -interface BrowserElementPrivileged { - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //void sendMouseEvent(DOMString type, - // unsigned long x, - // unsigned long y, - // unsigned long button, - // unsigned long clickCount, - // unsigned long modifiers); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled", - // Func="TouchEvent::PrefEnabled"] - //void sendTouchEvent(DOMString type, - // sequence<unsigned long> identifiers, - // sequence<long> x, - // sequence<long> y, - // sequence<unsigned long> rx, - // sequence<unsigned long> ry, - // sequence<float> rotationAngles, - // sequence<float> forces, - // unsigned long count, - // unsigned long modifiers); - - [Func="::dom::window::Window::global_is_mozbrowser", Throws] - void goBack(); - - [Func="::dom::window::Window::global_is_mozbrowser", Throws] - void goForward(); - - [Func="::dom::window::Window::global_is_mozbrowser", Throws] - void reload(optional boolean hardReload = false); - - [Func="::dom::window::Window::global_is_mozbrowser", Throws] - void stop(); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //DOMRequest download(DOMString url, - // optional BrowserElementDownloadOptions options); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //DOMRequest purgeHistory(); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //DOMRequest getScreenshot([EnforceRange] unsigned long width, - // [EnforceRange] unsigned long height, - // optional DOMString mimeType=""); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //void zoom(float zoom); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //DOMRequest getCanGoBack(); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //DOMRequest getCanGoForward(); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //DOMRequest getContentDimensions(); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //DOMRequest setInputMethodActive(boolean isActive); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //void setNFCFocus(boolean isFocus); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //void findAll(DOMString searchString, BrowserFindCaseSensitivity caseSensitivity); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //void findNext(BrowserFindDirection direction); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //void clearMatch(); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled"] - //DOMRequest executeScript(DOMString script, - // optional BrowserElementExecuteScriptOptions options); - -}; diff --git a/components/script/dom/webidls/CDATASection.webidl b/components/script/dom/webidls/CDATASection.webidl new file mode 100644 index 00000000000..28cb4a85003 --- /dev/null +++ b/components/script/dom/webidls/CDATASection.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://dom.spec.whatwg.org/#interface-cdatasection + */ + +[Exposed=Window] +interface CDATASection : Text { +}; diff --git a/components/script/dom/webidls/CSS.webidl b/components/script/dom/webidls/CSS.webidl index 5f4aa4f0bef..15049ae7539 100644 --- a/components/script/dom/webidls/CSS.webidl +++ b/components/script/dom/webidls/CSS.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * http://dev.w3.org/csswg/cssom/#the-css-interface @@ -17,3 +17,8 @@ partial interface CSS { static boolean supports(DOMString property, DOMString value); static boolean supports(DOMString conditionText); }; + +// https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet +partial interface CSS { + [SameObject, Pref="dom.worklet.enabled"] static readonly attribute Worklet paintWorklet; +}; diff --git a/components/script/dom/webidls/CSSConditionRule.webidl b/components/script/dom/webidls/CSSConditionRule.webidl index 889153a085e..daf4258bc88 100644 --- a/components/script/dom/webidls/CSSConditionRule.webidl +++ b/components/script/dom/webidls/CSSConditionRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/css-conditional/#cssconditionrule [Abstract, Exposed=Window] diff --git a/components/script/dom/webidls/CSSFontFaceRule.webidl b/components/script/dom/webidls/CSSFontFaceRule.webidl index a0a1b8f950b..0e159a4b4ec 100644 --- a/components/script/dom/webidls/CSSFontFaceRule.webidl +++ b/components/script/dom/webidls/CSSFontFaceRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/css-fonts/#cssfontfacerule is unfortunately not web-compatible: // https://github.com/w3c/csswg-drafts/issues/825 diff --git a/components/script/dom/webidls/CSSGroupingRule.webidl b/components/script/dom/webidls/CSSGroupingRule.webidl index 41ac5e8dd57..3e21e85ecc4 100644 --- a/components/script/dom/webidls/CSSGroupingRule.webidl +++ b/components/script/dom/webidls/CSSGroupingRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#the-cssgroupingrule-interface [Abstract, Exposed=Window] diff --git a/components/script/dom/webidls/CSSImportRule.webidl b/components/script/dom/webidls/CSSImportRule.webidl index b8131a7bb87..1340d90c736 100644 --- a/components/script/dom/webidls/CSSImportRule.webidl +++ b/components/script/dom/webidls/CSSImportRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#cssimportrule [Exposed=Window] diff --git a/components/script/dom/webidls/CSSKeyframeRule.webidl b/components/script/dom/webidls/CSSKeyframeRule.webidl index 079b88ef231..92195320767 100644 --- a/components/script/dom/webidls/CSSKeyframeRule.webidl +++ b/components/script/dom/webidls/CSSKeyframeRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/css-animations/#interface-csskeyframerule [Exposed=Window] diff --git a/components/script/dom/webidls/CSSKeyframesRule.webidl b/components/script/dom/webidls/CSSKeyframesRule.webidl index fc31fc62406..e1f30b19015 100644 --- a/components/script/dom/webidls/CSSKeyframesRule.webidl +++ b/components/script/dom/webidls/CSSKeyframesRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/css-animations/#interface-csskeyframesrule [Exposed=Window] diff --git a/components/script/dom/webidls/CSSMediaRule.webidl b/components/script/dom/webidls/CSSMediaRule.webidl index 0f44c382620..13b0cbafe76 100644 --- a/components/script/dom/webidls/CSSMediaRule.webidl +++ b/components/script/dom/webidls/CSSMediaRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#the-cssmediarule-interface // https://drafts.csswg.org/css-conditional/#cssmediarule diff --git a/components/script/dom/webidls/CSSNamespaceRule.webidl b/components/script/dom/webidls/CSSNamespaceRule.webidl index aa1ce87e289..082d53833e9 100644 --- a/components/script/dom/webidls/CSSNamespaceRule.webidl +++ b/components/script/dom/webidls/CSSNamespaceRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#the-cssnamespacerule-interface [Exposed=Window] diff --git a/components/script/dom/webidls/CSSRule.webidl b/components/script/dom/webidls/CSSRule.webidl index d36111b0cc0..215f3aaf445 100644 --- a/components/script/dom/webidls/CSSRule.webidl +++ b/components/script/dom/webidls/CSSRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#the-cssrule-interface [Abstract, Exposed=Window] diff --git a/components/script/dom/webidls/CSSRuleList.webidl b/components/script/dom/webidls/CSSRuleList.webidl index 8ee12004a26..e411930589b 100644 --- a/components/script/dom/webidls/CSSRuleList.webidl +++ b/components/script/dom/webidls/CSSRuleList.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#cssrulelist // [LegacyArrayClass] diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index efe1d57b038..7865195360f 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface @@ -10,434 +10,20 @@ [Exposed=Window] interface CSSStyleDeclaration { - [SetterThrows] + [CEReactions, SetterThrows] attribute DOMString cssText; readonly attribute unsigned long length; getter DOMString item(unsigned long index); DOMString getPropertyValue(DOMString property); DOMString getPropertyPriority(DOMString property); - [Throws] + [CEReactions, Throws] void setProperty(DOMString property, [TreatNullAs=EmptyString] DOMString value, - [TreatNullAs=EmptyString] optional DOMString priority = ""); - [Throws] - void setPropertyValue(DOMString property, [TreatNullAs=EmptyString] DOMString value); - - [Throws] - void setPropertyPriority(DOMString property, [TreatNullAs=EmptyString] DOMString priority); - - [Throws] + optional [TreatNullAs=EmptyString] DOMString priority = ""); + [CEReactions, Throws] DOMString removeProperty(DOMString property); - //readonly attribute CSSRule? parentRule; - [SetterThrows] + // readonly attribute CSSRule? parentRule; + [CEReactions, SetterThrows] attribute DOMString cssFloat; }; -partial interface CSSStyleDeclaration { - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString all; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundPosition; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-position; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundPositionX; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-position-x; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundPositionY; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-position-y; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundRepeat; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-repeat; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundImage; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-image; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundAttachment; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-attachment; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundSize; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-size; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundOrigin; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-origin; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backgroundClip; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString background-clip; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderRadius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-radius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderSpacing; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-spacing; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBottom; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-bottom; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBottomColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-bottom-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBottomLeftRadius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-bottom-left-radius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBottomRightRadius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-bottom-right-radius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBottomStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-bottom-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBottomWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-bottom-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderLeft; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-left; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderLeftColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-left-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderLeftStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-left-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderLeftWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-left-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderRight; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-right; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderRightColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-right-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderRightStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-right-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderRightWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-right-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTop; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopLeftRadius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-left-radius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopRightRadius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-right-radius; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-source; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageSource; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-slice; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageSlice; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-repeat; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageRepeat; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-outset; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageOutset; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImage; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-start-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockStartColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-start-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockStartWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-start-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockStartStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-end-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockEndColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-end-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockEndWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-end-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockEndStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-inline-start-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderInlineStartColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-inline-start-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderInlineStartWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-inline-start-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderInlineStartStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-inline-end-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderInlineEndColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-inline-end-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderInlineEndWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-inline-end-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderInlineEndStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-start; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockStart; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-end; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockEnd; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-inline-start; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderInlineStart; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-inline-end; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderInlineEnd; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString content; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString color; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString display; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString opacity; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString visibility; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString cursor; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString boxSizing; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString box-sizing; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString boxShadow; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString box-shadow; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString textShadow; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-shadow; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString _float; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString clear; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString clip; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transform; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transformOrigin; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transform-origin; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString perspective; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString perspectiveOrigin; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString perspective-origin; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transformStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transform-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backfaceVisibility; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString backface-visibility; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString direction; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString unicodeBidi; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString unicode-bidi; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString filter; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString lineHeight; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString line-height; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString mixBlendMode; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString mix-blend-mode; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString verticalAlign; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString vertical-align; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString listStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString list-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString listStylePosition; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString list-style-position; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString listStyleType; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString list-style-type; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString listStyleImage; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString list-style-image; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString quotes; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString counterIncrement; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString counter-increment; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString counterReset; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString counter-reset; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString overflow; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString overflowX; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString overflow-x; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString overflowY; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString overflow-y; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString overflowWrap; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString overflow-wrap; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString tableLayout; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString table-layout; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderCollapse; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-collapse; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString emptyCells; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString empty-cells; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString captionSide; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString caption-side; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString whiteSpace; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString white-space; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString writingMode; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString writing-mode; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString letterSpacing; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString letter-spacing; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString wordBreak; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString word-break; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString wordSpacing; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString word-spacing; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString wordWrap; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString word-wrap; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString textOverflow; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-overflow; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString textAlign; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-align; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString textDecoration; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-decoration; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString textDecorationLine; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-decoration-line; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString textIndent; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-indent; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString textJustify; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-justify; - //[SetterThrows, TreatNullAs=EmptyString] attribute DOMString textOrientation; - //[SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-orientation; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString textRendering; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-rendering; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString textTransform; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString text-transform; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString font; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString fontFamily; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString font-family; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString fontSize; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString font-size; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString fontStretch; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString font-stretch; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString fontStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString font-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString fontVariant; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString font-variant; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString fontVariantCaps; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString font-variant-caps; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString fontWeight; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString font-weight; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString margin; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString marginBottom; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString margin-bottom; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString marginLeft; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString margin-left; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString marginRight; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString margin-right; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString marginTop; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString margin-top; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString margin-block-start; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString marginBlockStart; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString margin-block-end; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString marginBlockEnd; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString margin-inline-start; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString marginInlineStart; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString margin-inline-end; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString marginInlineEnd; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString padding; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString paddingBottom; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString padding-bottom; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString paddingLeft; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString padding-left; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString paddingRight; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString padding-right; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString paddingTop; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString padding-top; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString padding-block-start; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString paddingBlockStart; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString padding-block-end; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString paddingBlockEnd; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString padding-inline-start; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString paddingInlineStart; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString padding-inline-end; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString paddingInlineEnd; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString outline; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString outlineColor; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString outline-color; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString outlineStyle; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString outline-style; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString outlineWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString outline-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString outlineOffset; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString outline-offset; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString position; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString pointerEvents; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString pointer-events; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString top; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString right; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString left; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString bottom; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString offset-block-start; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString offsetBlockStart; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString offset-block-end; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString offsetBlockEnd; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString offset-inline-start; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString offsetInlineStart; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString offset-inline-end; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString offsetInlineEnd; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString height; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString minHeight; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString min-height; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString maxHeight; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString max-height; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString minWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString min-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString maxWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString max-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString block-size; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString blockSize; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString inline-size; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString inlineSize; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString max-block-size; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString maxBlockSize; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString max-inline-size; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString maxInlineSize; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString min-block-size; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString minBlockSize; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString min-inline-size; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString minInlineSize; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString zIndex; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString z-index; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString imageRendering; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString image-rendering; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString columnCount; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString column-count; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString columnWidth; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString column-width; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString columns; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString columnGap; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString column-gap; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transition; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transitionDuration; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transition-duration; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transitionTimingFunction; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transition-timing-function; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transitionProperty; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transition-property; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transitionDelay; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transition-delay; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexFlow; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-flow; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexDirection; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-direction; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexWrap; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-wrap; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString justifyContent; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString justify-content; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString alignItems; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString align-items; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString alignContent; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString align-content; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString order; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexBasis; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-basis; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexGrow; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-grow; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexShrink; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-shrink; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString alignSelf; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString align-self; - - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-name; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationName; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-duration; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDuration; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-timing-function; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationTimingFunction; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-iteration-count; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationIterationCount; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-direction; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDirection; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-play-state; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationPlayState; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-fill-mode; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationFillMode; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-delay; - [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDelay; -}; +// Auto-generated in GlobalGen.py: accessors for each CSS property diff --git a/components/script/dom/webidls/CSSStyleRule.webidl b/components/script/dom/webidls/CSSStyleRule.webidl index 145650a916c..48bf819bb89 100644 --- a/components/script/dom/webidls/CSSStyleRule.webidl +++ b/components/script/dom/webidls/CSSStyleRule.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#the-cssstylerule-interface [Exposed=Window] interface CSSStyleRule : CSSRule { - // attribute DOMString selectorText; + attribute DOMString selectorText; [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; }; diff --git a/components/script/dom/webidls/CSSStyleSheet.webidl b/components/script/dom/webidls/CSSStyleSheet.webidl index c616c9d2a10..c09f14a3e8a 100644 --- a/components/script/dom/webidls/CSSStyleSheet.webidl +++ b/components/script/dom/webidls/CSSStyleSheet.webidl @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#the-cssstylesheet-interface [Exposed=Window] interface CSSStyleSheet : StyleSheet { // readonly attribute CSSRule? ownerRule; [Throws, SameObject] readonly attribute CSSRuleList cssRules; - [Throws] unsigned long insertRule(DOMString rule, unsigned long index); + [Throws] unsigned long insertRule(DOMString rule, optional unsigned long index = 0); [Throws] void deleteRule(unsigned long index); }; diff --git a/components/script/dom/webidls/CSSStyleValue.webidl b/components/script/dom/webidls/CSSStyleValue.webidl new file mode 100644 index 00000000000..3cd075b67e8 --- /dev/null +++ b/components/script/dom/webidls/CSSStyleValue.webidl @@ -0,0 +1,10 @@ +/* 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/. */ + +// https://drafts.css-houdini.org/css-typed-om-1/#cssstylevalue +// NOTE: should this be exposed to Window? +[Pref="dom.worklet.enabled", Exposed=(Worklet)] +interface CSSStyleValue { + stringifier; +}; diff --git a/components/script/dom/webidls/CSSSupportsRule.webidl b/components/script/dom/webidls/CSSSupportsRule.webidl index 73f147c6181..a726f101e41 100644 --- a/components/script/dom/webidls/CSSSupportsRule.webidl +++ b/components/script/dom/webidls/CSSSupportsRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/css-conditional/#csssupportsrule [Exposed=Window] diff --git a/components/script/dom/webidls/CSSViewportRule.webidl b/components/script/dom/webidls/CSSViewportRule.webidl index b1b73eb9c50..e53b0461358 100644 --- a/components/script/dom/webidls/CSSViewportRule.webidl +++ b/components/script/dom/webidls/CSSViewportRule.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/css-device-adapt/#css-viewport-rule-interface [Exposed=Window] diff --git a/components/script/dom/webidls/CanvasGradient.webidl b/components/script/dom/webidls/CanvasGradient.webidl index 236c077551a..ec00d4a73f2 100644 --- a/components/script/dom/webidls/CanvasGradient.webidl +++ b/components/script/dom/webidls/CanvasGradient.webidl @@ -1,8 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#canvasgradient +[Exposed=(Window, PaintWorklet, Worker)] interface CanvasGradient { // opaque object [Throws] diff --git a/components/script/dom/webidls/CanvasPattern.webidl b/components/script/dom/webidls/CanvasPattern.webidl index eb382b82b1a..bfd33b082f8 100644 --- a/components/script/dom/webidls/CanvasPattern.webidl +++ b/components/script/dom/webidls/CanvasPattern.webidl @@ -1,8 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#canvaspattern +[Exposed=(Window, PaintWorklet, Worker)] interface CanvasPattern { //void setTransform(SVGMatrix matrix); }; diff --git a/components/script/dom/webidls/CanvasRenderingContext2D.webidl b/components/script/dom/webidls/CanvasRenderingContext2D.webidl index ed5f9477476..6216ec50505 100644 --- a/components/script/dom/webidls/CanvasRenderingContext2D.webidl +++ b/components/script/dom/webidls/CanvasRenderingContext2D.webidl @@ -1,55 +1,53 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -enum CanvasFillRule { "nonzero", "evenodd" }; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#2dcontext -typedef (HTMLImageElement or - /* HTMLVideoElement or */ + +// typedef (HTMLImageElement or +// SVGImageElement) HTMLOrSVGImageElement; +typedef HTMLImageElement HTMLOrSVGImageElement; + +typedef (HTMLOrSVGImageElement or + /*HTMLVideoElement or*/ HTMLCanvasElement or - CanvasRenderingContext2D /* or - ImageBitmap */) CanvasImageSource; + /*ImageBitmap or*/ + OffscreenCanvas or + /*CSSImageValue*/ CSSStyleValue) CanvasImageSource; -//[Constructor(optional unsigned long width, unsigned long height)] -interface CanvasRenderingContext2D { +enum CanvasFillRule { "nonzero", "evenodd" }; +[Exposed=Window] +interface CanvasRenderingContext2D { // back-reference to the canvas readonly attribute HTMLCanvasElement canvas; - - // canvas dimensions - // attribute unsigned long width; - // attribute unsigned long height; - - // for contexts that aren't directly fixed to a specific canvas - //void commit(); // push the image to the output bitmap }; -CanvasRenderingContext2D implements CanvasState; -CanvasRenderingContext2D implements CanvasTransform; -CanvasRenderingContext2D implements CanvasCompositing; -CanvasRenderingContext2D implements CanvasImageSmoothing; -CanvasRenderingContext2D implements CanvasFillStrokeStyles; -CanvasRenderingContext2D implements CanvasShadowStyles; -CanvasRenderingContext2D implements CanvasRect; -CanvasRenderingContext2D implements CanvasDrawPath; -CanvasRenderingContext2D implements CanvasUserInterface; -CanvasRenderingContext2D implements CanvasText; -CanvasRenderingContext2D implements CanvasDrawImage; -CanvasRenderingContext2D implements CanvasHitRegion; -CanvasRenderingContext2D implements CanvasImageData; -CanvasRenderingContext2D implements CanvasPathDrawingStyles; -CanvasRenderingContext2D implements CanvasTextDrawingStyles; -CanvasRenderingContext2D implements CanvasPath; - -[NoInterfaceObject] -interface CanvasState { +CanvasRenderingContext2D includes CanvasState; +CanvasRenderingContext2D includes CanvasTransform; +CanvasRenderingContext2D includes CanvasCompositing; +CanvasRenderingContext2D includes CanvasImageSmoothing; +CanvasRenderingContext2D includes CanvasFillStrokeStyles; +CanvasRenderingContext2D includes CanvasShadowStyles; +CanvasRenderingContext2D includes CanvasFilters; +CanvasRenderingContext2D includes CanvasRect; +CanvasRenderingContext2D includes CanvasDrawPath; +CanvasRenderingContext2D includes CanvasUserInterface; +CanvasRenderingContext2D includes CanvasText; +CanvasRenderingContext2D includes CanvasDrawImage; +CanvasRenderingContext2D includes CanvasImageData; +CanvasRenderingContext2D includes CanvasPathDrawingStyles; +CanvasRenderingContext2D includes CanvasTextDrawingStyles; +CanvasRenderingContext2D includes CanvasPath; + +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasState { // state void save(); // push state on state stack void restore(); // pop state stack and restore state }; -[NoInterfaceObject] -interface CanvasTransform { +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasTransform { // transformations (default transform is the identity matrix) void scale(unrestricted double x, unrestricted double y); void rotate(unrestricted double angle); @@ -61,7 +59,7 @@ interface CanvasTransform { unrestricted double e, unrestricted double f); - // [NewObject] DOMMatrix getTransform(); + [NewObject] DOMMatrix getTransform(); void setTransform(unrestricted double a, unrestricted double b, unrestricted double c, @@ -72,23 +70,22 @@ interface CanvasTransform { void resetTransform(); }; -[NoInterfaceObject] -interface CanvasCompositing { +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasCompositing { // compositing attribute unrestricted double globalAlpha; // (default 1.0) attribute DOMString globalCompositeOperation; // (default source-over) }; -[NoInterfaceObject] -interface CanvasImageSmoothing { +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasImageSmoothing { // image smoothing attribute boolean imageSmoothingEnabled; // (default true) // attribute ImageSmoothingQuality imageSmoothingQuality; // (default low) }; -[NoInterfaceObject] -interface CanvasFillStrokeStyles { - +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasFillStrokeStyles { // colours and styles (see also the CanvasDrawingStyles interface) attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle; // (default black) attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle; // (default black) @@ -96,11 +93,11 @@ interface CanvasFillStrokeStyles { [Throws] CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1); [Throws] - CanvasPattern createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition); + CanvasPattern? createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition); }; -[NoInterfaceObject] -interface CanvasShadowStyles { +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasShadowStyles { // shadows attribute unrestricted double shadowOffsetX; // (default 0) attribute unrestricted double shadowOffsetY; // (default 0) @@ -108,32 +105,30 @@ interface CanvasShadowStyles { attribute DOMString shadowColor; // (default transparent black) }; -[NoInterfaceObject] -interface CanvasRect { +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasFilters { + // filters + //attribute DOMString filter; // (default "none") +}; + +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasRect { // rects - //[LenientFloat] void clearRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); - //[LenientFloat] void fillRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); - //[LenientFloat] void strokeRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); }; -[NoInterfaceObject] -interface CanvasDrawPath { - // path API (see also CanvasPathMethods) +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasDrawPath { + // path API (see also CanvasPath) void beginPath(); void fill(optional CanvasFillRule fillRule = "nonzero"); //void fill(Path2D path, optional CanvasFillRule fillRule = "nonzero"); void stroke(); //void stroke(Path2D path); - //void drawFocusIfNeeded(Element element); - //void drawFocusIfNeeded(Path2D path, Element element); - //void scrollPathIntoView(); - //void scrollPathIntoView(Path2D path); void clip(optional CanvasFillRule fillRule = "nonzero"); //void clip(Path2D path, optional CanvasFillRule fillRule = "nonzero"); - //void resetClip(); boolean isPointInPath(unrestricted double x, unrestricted double y, optional CanvasFillRule fillRule = "nonzero"); //boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, @@ -142,23 +137,28 @@ interface CanvasDrawPath { //boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y); }; -[NoInterfaceObject] -interface CanvasUserInterface { - // TODO? +[Exposed=(PaintWorklet, Window)] +interface mixin CanvasUserInterface { + //void drawFocusIfNeeded(Element element); + //void drawFocusIfNeeded(Path2D path, Element element); + //void scrollPathIntoView(); + //void scrollPathIntoView(Path2D path); }; -[NoInterfaceObject] -interface CanvasText { - // text (see also the CanvasDrawingStyles interface) - //void fillText(DOMString text, unrestricted double x, unrestricted double y, - // optional unrestricted double maxWidth); +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasText { + // text (see also the CanvasPathDrawingStyles and CanvasTextDrawingStyles interfaces) + [Pref="dom.canvas_text.enabled"] + void fillText(DOMString text, unrestricted double x, unrestricted double y, + optional unrestricted double maxWidth); //void strokeText(DOMString text, unrestricted double x, unrestricted double y, // optional unrestricted double maxWidth); - //TextMetrics measureText(DOMString text); + [Pref="dom.canvas_text.enabled"] + TextMetrics measureText(DOMString text); }; -[NoInterfaceObject] -interface CanvasDrawImage { +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasDrawImage { // drawing images [Throws] void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy); @@ -172,32 +172,21 @@ interface CanvasDrawImage { unrestricted double dw, unrestricted double dh); }; -[NoInterfaceObject] -interface CanvasHitRegion { - // hit regions - //void addHitRegion(optional HitRegionOptions options); - //void removeHitRegion(DOMString id); - //void clearHitRegions(); -}; - -[NoInterfaceObject] -interface CanvasImageData { +[Exposed=(Window, Worker)] +interface mixin CanvasImageData { // pixel manipulation [Throws] - ImageData createImageData(double sw, double sh); + ImageData createImageData(long sw, long sh); [Throws] ImageData createImageData(ImageData imagedata); [Throws] - ImageData getImageData(double sx, double sy, double sw, double sh); - void putImageData(ImageData imagedata, double dx, double dy); + ImageData getImageData(long sx, long sy, long sw, long sh); + void putImageData(ImageData imagedata, long dx, long dy); void putImageData(ImageData imagedata, - double dx, double dy, - double dirtyX, double dirtyY, - double dirtyWidth, double dirtyHeight); + long dx, long dy, + long dirtyX, long dirtyY, + long dirtyWidth, long dirtyHeight); }; -CanvasRenderingContext2D implements CanvasPathDrawingStyles; -CanvasRenderingContext2D implements CanvasTextDrawingStyles; -CanvasRenderingContext2D implements CanvasPath; enum CanvasLineCap { "butt", "round", "square" }; enum CanvasLineJoin { "round", "bevel", "miter"}; @@ -205,12 +194,12 @@ enum CanvasTextAlign { "start", "end", "left", "right", "center" }; enum CanvasTextBaseline { "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" }; enum CanvasDirection { "ltr", "rtl", "inherit" }; -[NoInterfaceObject] -interface CanvasPathDrawingStyles { +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasPathDrawingStyles { // line caps/joins attribute unrestricted double lineWidth; // (default 1) - attribute CanvasLineCap lineCap; // "butt", "round", "square" (default "butt") - attribute CanvasLineJoin lineJoin; // "round", "bevel", "miter" (default "miter") + attribute CanvasLineCap lineCap; // (default "butt") + attribute CanvasLineJoin lineJoin; // (default "miter") attribute unrestricted double miterLimit; // (default 10) // dashed lines @@ -219,18 +208,18 @@ interface CanvasPathDrawingStyles { //attribute unrestricted double lineDashOffset; }; -[NoInterfaceObject] -interface CanvasTextDrawingStyles { +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasTextDrawingStyles { // text - //attribute DOMString font; // (default 10px sans-serif) - //attribute CanvasTextAlign textAlign; // "start", "end", "left", "right", "center" (default: "start") - //attribute CanvasTextBaseline textBaseline; // "top", "hanging", "middle", "alphabetic", + attribute DOMString font; // (default 10px sans-serif) + attribute CanvasTextAlign textAlign; // "start", "end", "left", "right", "center" (default: "start") + attribute CanvasTextBaseline textBaseline; // "top", "hanging", "middle", "alphabetic", // "ideographic", "bottom" (default: "alphabetic") - //attribute CanvasDirection direction; // "ltr", "rtl", "inherit" (default: "inherit") + attribute CanvasDirection direction; // "ltr", "rtl", "inherit" (default: "inherit") }; -[NoInterfaceObject, Exposed=(Window,Worker)] -interface CanvasPath { +[Exposed=(PaintWorklet, Window, Worker)] +interface mixin CanvasPath { // shared path API methods void closePath(); void moveTo(unrestricted double x, unrestricted double y); @@ -249,16 +238,15 @@ interface CanvasPath { void arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radius); - // [LenientFloat] void arcTo(double x1, double y1, double x2, double y2, - // double radiusX, double radiusY, double rotation); void rect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); [Throws] void arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean anticlockwise = false); - // [LenientFloat] void ellipse(double x, double y, double radiusX, double radiusY, - // double rotation, double startAngle, double endAngle, - // boolean anticlockwise); -}; + [Throws] + void ellipse(unrestricted double x, unrestricted double y, unrestricted double radius_x, + unrestricted double radius_y, unrestricted double rotation, unrestricted double startAngle, + unrestricted double endAngle, optional boolean anticlockwise = false); +}; diff --git a/components/script/dom/webidls/ChannelMergerNode.webidl b/components/script/dom/webidls/ChannelMergerNode.webidl new file mode 100644 index 00000000000..c6eaad8bb55 --- /dev/null +++ b/components/script/dom/webidls/ChannelMergerNode.webidl @@ -0,0 +1,16 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#channelmergernode + */ + +dictionary ChannelMergerOptions : AudioNodeOptions { + unsigned long numberOfInputs = 6; +}; + +[Exposed=Window] +interface ChannelMergerNode : AudioNode { + [Throws] constructor(BaseAudioContext context, optional ChannelMergerOptions options = {}); +}; diff --git a/components/script/dom/webidls/ChannelSplitterNode.webidl b/components/script/dom/webidls/ChannelSplitterNode.webidl new file mode 100644 index 00000000000..e867f7a9181 --- /dev/null +++ b/components/script/dom/webidls/ChannelSplitterNode.webidl @@ -0,0 +1,16 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#channelsplitternode + */ + +dictionary ChannelSplitterOptions : AudioNodeOptions { + unsigned long numberOfOutputs = 6; +}; + +[Exposed=Window] +interface ChannelSplitterNode : AudioNode { + [Throws] constructor(BaseAudioContext context, optional ChannelSplitterOptions options = {}); +}; diff --git a/components/script/dom/webidls/CharacterData.webidl b/components/script/dom/webidls/CharacterData.webidl index bd092dfec09..e69f862afc9 100644 --- a/components/script/dom/webidls/CharacterData.webidl +++ b/components/script/dom/webidls/CharacterData.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#characterdata @@ -9,9 +9,9 @@ * liability, trademark and document use rules apply. */ -[Abstract] +[Exposed=Window, Abstract] interface CharacterData : Node { - [Pure, TreatNullAs=EmptyString] attribute DOMString data; + [Pure] attribute [TreatNullAs=EmptyString] DOMString data; [Pure] readonly attribute unsigned long length; [Pure, Throws] DOMString substringData(unsigned long offset, unsigned long count); @@ -24,5 +24,5 @@ interface CharacterData : Node { void replaceData(unsigned long offset, unsigned long count, DOMString data); }; -CharacterData implements ChildNode; -CharacterData implements NonDocumentTypeChildNode; +CharacterData includes ChildNode; +CharacterData includes NonDocumentTypeChildNode; diff --git a/components/script/dom/webidls/ChildNode.webidl b/components/script/dom/webidls/ChildNode.webidl index ca642048d11..8964653fffc 100644 --- a/components/script/dom/webidls/ChildNode.webidl +++ b/components/script/dom/webidls/ChildNode.webidl @@ -1,25 +1,23 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is: * https://dom.spec.whatwg.org/#interface-childnode */ -[NoInterfaceObject] -interface ChildNode { - [Throws, Unscopable] +interface mixin ChildNode { + [Throws, CEReactions, Unscopable] void before((Node or DOMString)... nodes); - [Throws, Unscopable] + [Throws, CEReactions, Unscopable] void after((Node or DOMString)... nodes); - [Throws, Unscopable] + [Throws, CEReactions, Unscopable] void replaceWith((Node or DOMString)... nodes); - [Unscopable] + [CEReactions, Unscopable] void remove(); }; -[NoInterfaceObject] -interface NonDocumentTypeChildNode { +interface mixin NonDocumentTypeChildNode { [Pure] readonly attribute Element? previousElementSibling; [Pure] diff --git a/components/script/dom/webidls/Client.webidl b/components/script/dom/webidls/Client.webidl index 8ff045d11ab..f5a9dee934e 100644 --- a/components/script/dom/webidls/Client.webidl +++ b/components/script/dom/webidls/Client.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/ServiceWorker/#client diff --git a/components/script/dom/webidls/CloseEvent.webidl b/components/script/dom/webidls/CloseEvent.webidl index 477ce51445c..712f8848df0 100644 --- a/components/script/dom/webidls/CloseEvent.webidl +++ b/components/script/dom/webidls/CloseEvent.webidl @@ -1,10 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //https://html.spec.whatwg.org/multipage/#the-closeevent-interfaces -[Constructor(DOMString type, optional CloseEventInit eventInitDict), Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface CloseEvent : Event { + [Throws] constructor(DOMString type, optional CloseEventInit eventInitDict = {}); readonly attribute boolean wasClean; readonly attribute unsigned short code; readonly attribute DOMString reason; diff --git a/components/script/dom/webidls/Comment.webidl b/components/script/dom/webidls/Comment.webidl index d49897f8862..0ccda9ee991 100644 --- a/components/script/dom/webidls/Comment.webidl +++ b/components/script/dom/webidls/Comment.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#comment @@ -9,6 +9,7 @@ * liability, trademark and document use rules apply. */ -[Constructor(optional DOMString data = "")] +[Exposed=Window] interface Comment : CharacterData { + [Throws] constructor(optional DOMString data = ""); }; diff --git a/components/script/dom/webidls/CompositionEvent.webidl b/components/script/dom/webidls/CompositionEvent.webidl new file mode 100644 index 00000000000..1ed323d26b3 --- /dev/null +++ b/components/script/dom/webidls/CompositionEvent.webidl @@ -0,0 +1,21 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/uievents/#idl-compositionevent + * + */ + +// https://w3c.github.io/uievents/#idl-compositionevent +[Exposed=Window, Pref="dom.composition_event.enabled"] +interface CompositionEvent : UIEvent { + [Throws] constructor(DOMString type, optional CompositionEventInit eventInitDict = {}); + readonly attribute DOMString data; +}; + +// https://w3c.github.io/uievents/#idl-compositioneventinit +dictionary CompositionEventInit : UIEventInit { + DOMString data = ""; +}; + diff --git a/components/script/dom/webidls/Console.webidl b/components/script/dom/webidls/Console.webidl index 90f9bb9f58e..c41e46d8535 100644 --- a/components/script/dom/webidls/Console.webidl +++ b/components/script/dom/webidls/Console.webidl @@ -1,25 +1,28 @@ /* 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 http://mozilla.org/MPL/2.0/. */ -/* - * References: - * MDN Docs - https://developer.mozilla.org/en-US/docs/Web/API/console - * Draft Spec - https://sideshowbarker.github.io/console-spec/ - * - * © Copyright 2014 Mozilla Foundation. - */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// https://console.spec.whatwg.org/ [ClassString="Console", - Exposed=(Window,Worker), + Exposed=(Window,Worker,Worklet), ProtoObjectHack] namespace console { - // These should be DOMString message, DOMString message2, ... + // Logging void log(DOMString... messages); void debug(DOMString... messages); void info(DOMString... messages); void warn(DOMString... messages); void error(DOMString... messages); void assert(boolean condition, optional DOMString message); + void clear(); + + // Grouping + void group(DOMString... data); + void groupCollapsed(DOMString... data); + void groupEnd(); + + // Timing void time(DOMString message); void timeEnd(DOMString message); }; diff --git a/components/script/dom/webidls/ConstantSourceNode.webidl b/components/script/dom/webidls/ConstantSourceNode.webidl new file mode 100644 index 00000000000..e3a5a174250 --- /dev/null +++ b/components/script/dom/webidls/ConstantSourceNode.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#ConstantSourceNode + */ + +dictionary ConstantSourceOptions: AudioNodeOptions { + float offset = 1; +}; + +[Exposed=Window] +interface ConstantSourceNode : AudioScheduledSourceNode { + [Throws] constructor(BaseAudioContext context, optional ConstantSourceOptions options = {}); + readonly attribute AudioParam offset; +}; diff --git a/components/script/dom/webidls/Crypto.webidl b/components/script/dom/webidls/Crypto.webidl index 94611750e8f..8a92d425d89 100644 --- a/components/script/dom/webidls/Crypto.webidl +++ b/components/script/dom/webidls/Crypto.webidl @@ -1,24 +1,23 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#crypto-interface * */ -[NoInterfaceObject, Exposed=(Window,Worker)] -interface GlobalCrypto { +[Exposed=(Window,Worker)] +interface mixin GlobalCrypto { readonly attribute Crypto crypto; }; -Window implements GlobalCrypto; -WorkerGlobalScope implements GlobalCrypto; +Window includes GlobalCrypto; +WorkerGlobalScope includes GlobalCrypto; [Exposed=(Window,Worker)] interface Crypto { //readonly attribute SubtleCrypto subtle; - //ArrayBufferView getRandomValues(ArrayBufferView array); [Throws] - ArrayBufferView getRandomValues(object array); + ArrayBufferView getRandomValues(ArrayBufferView array); }; diff --git a/components/script/dom/webidls/CustomElementRegistry.webidl b/components/script/dom/webidls/CustomElementRegistry.webidl new file mode 100644 index 00000000000..1d6f638d67e --- /dev/null +++ b/components/script/dom/webidls/CustomElementRegistry.webidl @@ -0,0 +1,22 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#customelementregistry +[Exposed=Window, Pref="dom.custom_elements.enabled"] +interface CustomElementRegistry { + [Throws, CEReactions] + void define(DOMString name, CustomElementConstructor constructor_, optional ElementDefinitionOptions options = {}); + + any get(DOMString name); + + Promise<CustomElementConstructor> whenDefined(DOMString name); + + [CEReactions] void upgrade(Node root); +}; + +callback CustomElementConstructor = HTMLElement(); + +dictionary ElementDefinitionOptions { + DOMString extends; +}; diff --git a/components/script/dom/webidls/CustomEvent.webidl b/components/script/dom/webidls/CustomEvent.webidl index d9c5c189b5f..604286881df 100644 --- a/components/script/dom/webidls/CustomEvent.webidl +++ b/components/script/dom/webidls/CustomEvent.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * For more information on this interface please see * https://dom.spec.whatwg.org/#interface-customevent @@ -13,9 +13,9 @@ * http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0. */ -[Constructor(DOMString type, optional CustomEventInit eventInitDict), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface CustomEvent : Event { + [Throws] constructor(DOMString type, optional CustomEventInit eventInitDict = {}); readonly attribute any detail; void initCustomEvent(DOMString type, boolean bubbles, boolean cancelable, any detail); diff --git a/components/script/dom/webidls/DOMException.webidl b/components/script/dom/webidls/DOMException.webidl index 69866372cb6..d61d30151fa 100644 --- a/components/script/dom/webidls/DOMException.webidl +++ b/components/script/dom/webidls/DOMException.webidl @@ -1,14 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* /* https://heycam.github.io/webidl/#es-DOMException * https://heycam.github.io/webidl/#es-DOMException-constructor-object */ -[ExceptionClass, Exposed=(Window,Worker)] +[ + ExceptionClass, + Exposed=(Window,Worker,Worklet,DissimilarOriginWindow) +] interface DOMException { + [Throws] constructor(optional DOMString message="", optional DOMString name="Error"); const unsigned short INDEX_SIZE_ERR = 1; const unsigned short DOMSTRING_SIZE_ERR = 2; // historical const unsigned short HIERARCHY_REQUEST_ERR = 3; @@ -43,6 +47,4 @@ interface DOMException { // A custom message set by the thrower. readonly attribute DOMString message; - - stringifier; }; diff --git a/components/script/dom/webidls/DOMImplementation.webidl b/components/script/dom/webidls/DOMImplementation.webidl index bfee277416a..cf809b30f1a 100644 --- a/components/script/dom/webidls/DOMImplementation.webidl +++ b/components/script/dom/webidls/DOMImplementation.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#interface-domimplementation @@ -10,6 +10,7 @@ * related or neighboring rights to this work. */ +[Exposed=Window] interface DOMImplementation { [NewObject, Throws] DocumentType createDocumentType(DOMString qualifiedName, DOMString publicId, diff --git a/components/script/dom/webidls/DOMMatrix.webidl b/components/script/dom/webidls/DOMMatrix.webidl index d1dd65e8a63..eaef16c3906 100644 --- a/components/script/dom/webidls/DOMMatrix.webidl +++ b/components/script/dom/webidls/DOMMatrix.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://drafts.fxtf.org/geometry-1/#DOMMatrix @@ -10,15 +10,14 @@ * related or neighboring rights to this work. */ -[Constructor, - // Constructor(DOMString transformList), - Constructor(sequence<unrestricted double> numberSequence), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker,PaintWorklet), + LegacyWindowAlias=WebKitCSSMatrix] interface DOMMatrix : DOMMatrixReadOnly { + [Throws] constructor(optional (DOMString or sequence<unrestricted double>) init); - [NewObject, Throws] static DOMMatrix fromMatrix(optional DOMMatrixInit other); -// [NewObject] static DOMMatrix fromFloat32Array(Float32Array array32); -// [NewObject] static DOMMatrix fromFloat64Array(Float64Array array64); + [NewObject, Throws] static DOMMatrix fromMatrix(optional DOMMatrixInit other = {}); + [NewObject, Throws] static DOMMatrix fromFloat32Array(Float32Array array32); + [NewObject, Throws] static DOMMatrix fromFloat64Array(Float64Array array64); // These attributes are simple aliases for certain elements of the 4x4 matrix inherit attribute unrestricted double a; @@ -46,8 +45,8 @@ interface DOMMatrix : DOMMatrixReadOnly { inherit attribute unrestricted double m44; // Mutable transform methods - [Throws] DOMMatrix multiplySelf(optional DOMMatrixInit other); - [Throws] DOMMatrix preMultiplySelf(optional DOMMatrixInit other); + [Throws] DOMMatrix multiplySelf(optional DOMMatrixInit other = {}); + [Throws] DOMMatrix preMultiplySelf(optional DOMMatrixInit other = {}); DOMMatrix translateSelf(optional unrestricted double tx = 0, optional unrestricted double ty = 0, optional unrestricted double tz = 0); diff --git a/components/script/dom/webidls/DOMMatrixReadOnly.webidl b/components/script/dom/webidls/DOMMatrixReadOnly.webidl index 9261002e348..689b83ddc58 100644 --- a/components/script/dom/webidls/DOMMatrixReadOnly.webidl +++ b/components/script/dom/webidls/DOMMatrixReadOnly.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://drafts.fxtf.org/geometry-1/#DOMMatrix @@ -10,15 +10,13 @@ * related or neighboring rights to this work. */ -[Constructor, - // Constructor(DOMString transformList) - Constructor(sequence<unrestricted double> numberSequence), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker,PaintWorklet)] interface DOMMatrixReadOnly { + [Throws] constructor(optional (DOMString or sequence<unrestricted double>) init); - [NewObject, Throws] static DOMMatrixReadOnly fromMatrix(optional DOMMatrixInit other); -// [NewObject] static DOMMatrixReadOnly fromFloat32Array(Float32Array array32); -// [NewObject] static DOMMatrixReadOnly fromFloat64Array(Float64Array array64); + [NewObject, Throws] static DOMMatrixReadOnly fromMatrix(optional DOMMatrixInit other = {}); + [NewObject, Throws] static DOMMatrixReadOnly fromFloat32Array(Float32Array array32); + [NewObject, Throws] static DOMMatrixReadOnly fromFloat64Array(Float64Array array64); // These attributes are simple aliases for certain elements of the 4x4 matrix readonly attribute unrestricted double a; @@ -58,6 +56,8 @@ interface DOMMatrixReadOnly { optional unrestricted double originX = 0, optional unrestricted double originY = 0, optional unrestricted double originZ = 0); + [NewObject] DOMMatrix scaleNonUniform(optional unrestricted double scaleX = 1, + optional unrestricted double scaleY = 1); DOMMatrix scale3d(optional unrestricted double scale = 1, optional unrestricted double originX = 0, optional unrestricted double originY = 0, @@ -73,15 +73,14 @@ interface DOMMatrixReadOnly { optional unrestricted double angle = 0); DOMMatrix skewX(optional unrestricted double sx = 0); DOMMatrix skewY(optional unrestricted double sy = 0); - [Throws] DOMMatrix multiply(optional DOMMatrixInit other); + [Throws] DOMMatrix multiply(optional DOMMatrixInit other = {}); DOMMatrix flipX(); DOMMatrix flipY(); DOMMatrix inverse(); - DOMPoint transformPoint(optional DOMPointInit point); -// Float32Array toFloat32Array(); -// Float64Array toFloat64Array(); -// stringifier; -// serializer = { attribute }; - + DOMPoint transformPoint(optional DOMPointInit point = {}); + Float32Array toFloat32Array(); + Float64Array toFloat64Array(); +// [Exposed=Window] stringifier; + [Default] object toJSON(); }; diff --git a/components/script/dom/webidls/DOMParser.webidl b/components/script/dom/webidls/DOMParser.webidl index 6381f01e449..8af1f309c78 100644 --- a/components/script/dom/webidls/DOMParser.webidl +++ b/components/script/dom/webidls/DOMParser.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://w3c.github.io/DOM-Parsing/#the-domparser-interface @@ -14,8 +14,9 @@ enum SupportedType { "image/svg+xml"*/ }; -[Constructor] +[Exposed=Window] interface DOMParser { + [Throws] constructor(); [Throws] Document parseFromString(DOMString str, SupportedType type); }; diff --git a/components/script/dom/webidls/DOMPoint.webidl b/components/script/dom/webidls/DOMPoint.webidl index 4afc98ba3d6..765c62df573 100644 --- a/components/script/dom/webidls/DOMPoint.webidl +++ b/components/script/dom/webidls/DOMPoint.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * http://dev.w3.org/fxtf/geometry/ @@ -10,10 +10,12 @@ */ // http://dev.w3.org/fxtf/geometry/Overview.html#dompoint -[Constructor(optional unrestricted double x = 0, optional unrestricted double y = 0, - optional unrestricted double z = 0, optional unrestricted double w = 1), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker,PaintWorklet)] interface DOMPoint : DOMPointReadOnly { + [Throws] constructor(optional unrestricted double x = 0, optional unrestricted double y = 0, + optional unrestricted double z = 0, optional unrestricted double w = 1); + [NewObject] static DOMPoint fromPoint(optional DOMPointInit other = {}); + inherit attribute unrestricted double x; inherit attribute unrestricted double y; inherit attribute unrestricted double z; diff --git a/components/script/dom/webidls/DOMPointReadOnly.webidl b/components/script/dom/webidls/DOMPointReadOnly.webidl index 358a1129a82..98940ed3f08 100644 --- a/components/script/dom/webidls/DOMPointReadOnly.webidl +++ b/components/script/dom/webidls/DOMPointReadOnly.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * http://dev.w3.org/fxtf/geometry/ @@ -10,12 +10,16 @@ */ // http://dev.w3.org/fxtf/geometry/Overview.html#dompointreadonly -[Constructor(optional unrestricted double x = 0, optional unrestricted double y = 0, - optional unrestricted double z = 0, optional unrestricted double w = 1), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker,PaintWorklet)] interface DOMPointReadOnly { + [Throws] constructor(optional unrestricted double x = 0, optional unrestricted double y = 0, + optional unrestricted double z = 0, optional unrestricted double w = 1); + [NewObject] static DOMPointReadOnly fromPoint(optional DOMPointInit other = {}); + readonly attribute unrestricted double x; readonly attribute unrestricted double y; readonly attribute unrestricted double z; readonly attribute unrestricted double w; + + [Default] object toJSON(); }; diff --git a/components/script/dom/webidls/DOMQuad.webidl b/components/script/dom/webidls/DOMQuad.webidl index 51a3693093b..ba58ccffc48 100644 --- a/components/script/dom/webidls/DOMQuad.webidl +++ b/components/script/dom/webidls/DOMQuad.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://drafts.fxtf.org/geometry/#DOMQuad @@ -10,23 +10,25 @@ * related or neighboring rights to this work. */ -[Constructor(optional DOMPointInit p1, optional DOMPointInit p2, - optional DOMPointInit p3, optional DOMPointInit p4), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface DOMQuad { - [NewObject] static DOMQuad fromRect(optional DOMRectInit other); - [NewObject] static DOMQuad fromQuad(optional DOMQuadInit other); + [Throws] constructor(optional DOMPointInit p1 = {}, optional DOMPointInit p2 = {}, + optional DOMPointInit p3 = {}, optional DOMPointInit p4 = {}); + [NewObject] static DOMQuad fromRect(optional DOMRectInit other = {}); + [NewObject] static DOMQuad fromQuad(optional DOMQuadInit other = {}); [SameObject] readonly attribute DOMPoint p1; [SameObject] readonly attribute DOMPoint p2; [SameObject] readonly attribute DOMPoint p3; [SameObject] readonly attribute DOMPoint p4; [NewObject] DOMRect getBounds(); + + [Default] object toJSON(); }; dictionary DOMQuadInit { - DOMPointInit p1; - DOMPointInit p2; - DOMPointInit p3; - DOMPointInit p4; + DOMPointInit p1 = {}; + DOMPointInit p2 = {}; + DOMPointInit p3 = {}; + DOMPointInit p4 = {}; }; diff --git a/components/script/dom/webidls/DOMRect.webidl b/components/script/dom/webidls/DOMRect.webidl index 56e67ec976c..0aabed6d93f 100644 --- a/components/script/dom/webidls/DOMRect.webidl +++ b/components/script/dom/webidls/DOMRect.webidl @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -[Constructor(optional unrestricted double x = 0, optional unrestricted double y = 0, - optional unrestricted double width = 0, optional unrestricted double height = 0), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] // https://drafts.fxtf.org/geometry/#domrect interface DOMRect : DOMRectReadOnly { + [Throws] constructor(optional unrestricted double x = 0, optional unrestricted double y = 0, + optional unrestricted double width = 0, optional unrestricted double height = 0); inherit attribute unrestricted double x; inherit attribute unrestricted double y; inherit attribute unrestricted double width; diff --git a/components/script/dom/webidls/DOMRectReadOnly.webidl b/components/script/dom/webidls/DOMRectReadOnly.webidl index 6df1e50a02b..9885841c73a 100644 --- a/components/script/dom/webidls/DOMRectReadOnly.webidl +++ b/components/script/dom/webidls/DOMRectReadOnly.webidl @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -[Constructor(optional unrestricted double x = 0, optional unrestricted double y = 0, - optional unrestricted double width = 0, optional unrestricted double height = 0), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] // https://drafts.fxtf.org/geometry/#domrect interface DOMRectReadOnly { + [Throws] constructor(optional unrestricted double x = 0, optional unrestricted double y = 0, + optional unrestricted double width = 0, optional unrestricted double height = 0); // [NewObject] static DOMRectReadOnly fromRect(optional DOMRectInit other); readonly attribute unrestricted double x; @@ -17,6 +17,8 @@ interface DOMRectReadOnly { readonly attribute unrestricted double right; readonly attribute unrestricted double bottom; readonly attribute unrestricted double left; + + [Default] object toJSON(); }; // https://drafts.fxtf.org/geometry/#dictdef-domrectinit diff --git a/components/script/dom/webidls/DOMStringList.webidl b/components/script/dom/webidls/DOMStringList.webidl new file mode 100644 index 00000000000..c2d1f235b63 --- /dev/null +++ b/components/script/dom/webidls/DOMStringList.webidl @@ -0,0 +1,18 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://html.spec.whatwg.org/multipage/#domstringlist + * + * Copyright: + * To the extent possible under law, the editors have waived all copyright and + * related or neighboring rights to this work. + */ + +[Exposed=(Window,Worker)] +interface DOMStringList { + readonly attribute unsigned long length; + getter DOMString? item(unsigned long index); + boolean contains(DOMString string); +}; diff --git a/components/script/dom/webidls/DOMStringMap.webidl b/components/script/dom/webidls/DOMStringMap.webidl index f9801a45174..0acf6f70228 100644 --- a/components/script/dom/webidls/DOMStringMap.webidl +++ b/components/script/dom/webidls/DOMStringMap.webidl @@ -1,12 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#the-domstringmap-interface -[OverrideBuiltins] +[Exposed=Window, OverrideBuiltins] interface DOMStringMap { getter DOMString (DOMString name); - [Throws] + [CEReactions, Throws] setter void (DOMString name, DOMString value); + [CEReactions] deleter void (DOMString name); }; diff --git a/components/script/dom/webidls/DOMTokenList.webidl b/components/script/dom/webidls/DOMTokenList.webidl index 4d2b9f691a4..b67dbb1a1dc 100644 --- a/components/script/dom/webidls/DOMTokenList.webidl +++ b/components/script/dom/webidls/DOMTokenList.webidl @@ -1,8 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://dom.spec.whatwg.org/#domtokenlist +[Exposed=Window] interface DOMTokenList { [Pure] readonly attribute unsigned long length; @@ -11,18 +12,19 @@ interface DOMTokenList { [Pure] boolean contains(DOMString token); - [Throws] + [CEReactions, Throws] void add(DOMString... tokens); - [Throws] + [CEReactions, Throws] void remove(DOMString... tokens); - [Throws] + [CEReactions, Throws] boolean toggle(DOMString token, optional boolean force); - [Throws] - void replace(DOMString token, DOMString newToken); + [CEReactions, Throws] + boolean replace(DOMString token, DOMString newToken); + [Pure, Throws] + boolean supports(DOMString token); - [Pure] - attribute DOMString value; + [CEReactions, Pure] + stringifier attribute DOMString value; - stringifier; iterable<DOMString?>; }; diff --git a/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl b/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl index 53996ee3965..2df97dbacfe 100644 --- a/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl +++ b/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl @@ -1,13 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#dedicatedworkerglobalscope [Global=(Worker,DedicatedWorker), Exposed=DedicatedWorker] /*sealed*/ interface DedicatedWorkerGlobalScope : WorkerGlobalScope { - [Throws] - void postMessage(any message/*, optional sequence<Transferable> transfer*/); - attribute EventHandler onmessage; + [Throws] void postMessage(any message, sequence<object> transfer); + [Throws] void postMessage(any message, optional PostMessageOptions options = {}); + attribute EventHandler onmessage; void close(); }; diff --git a/components/script/dom/webidls/DissimilarOriginLocation.webidl b/components/script/dom/webidls/DissimilarOriginLocation.webidl index 8eca6a790ac..8c077665704 100644 --- a/components/script/dom/webidls/DissimilarOriginLocation.webidl +++ b/components/script/dom/webidls/DissimilarOriginLocation.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This is a Servo-specific interface, used to represent locations @@ -14,7 +14,8 @@ // way to enforce security policy. // https://html.spec.whatwg.org/multipage/#location -[Unforgeable, NoInterfaceObject] interface DissimilarOriginLocation { +[Exposed=(Window,DissimilarOriginWindow), Unforgeable, NoInterfaceObject] +interface DissimilarOriginLocation { [Throws] attribute USVString href; [Throws] void assign(USVString url); [Throws] void replace(USVString url); diff --git a/components/script/dom/webidls/DissimilarOriginWindow.webidl b/components/script/dom/webidls/DissimilarOriginWindow.webidl index a1f3a2f8b6d..b690736469a 100644 --- a/components/script/dom/webidls/DissimilarOriginWindow.webidl +++ b/components/script/dom/webidls/DissimilarOriginWindow.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This is a Servo-specific interface, used to represent windows // that are not similar-origin, so live in another script thread. @@ -13,7 +13,7 @@ // way to enforce security policy. // https://html.spec.whatwg.org/multipage/#window -[Global, NoInterfaceObject] +[Global, Exposed=(Window,DissimilarOriginWindow), NoInterfaceObject] interface DissimilarOriginWindow : GlobalScope { [Unforgeable] readonly attribute WindowProxy window; [BinaryName="Self_", Replaceable] readonly attribute WindowProxy self; @@ -25,7 +25,8 @@ interface DissimilarOriginWindow : GlobalScope { void close(); readonly attribute boolean closed; - [Throws] void postMessage(any message, DOMString targetOrigin); + [Throws] void postMessage(any message, USVString targetOrigin, optional sequence<object> transfer = []); + [Throws] void postMessage(any message, optional WindowPostMessageOptions options = {}); attribute any opener; void blur(); void focus(); diff --git a/components/script/dom/webidls/Document.webidl b/components/script/dom/webidls/Document.webidl index e3c67851011..d2429e51489 100644 --- a/components/script/dom/webidls/Document.webidl +++ b/components/script/dom/webidls/Document.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is: * https://dom.spec.whatwg.org/#interface-document @@ -8,8 +8,9 @@ */ // https://dom.spec.whatwg.org/#interface-document -[Constructor] +[Exposed=Window] interface Document : Node { + [Throws] constructor(); [SameObject] readonly attribute DOMImplementation implementation; [Constant] @@ -32,22 +33,25 @@ interface Document : Node { HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString qualifiedName); HTMLCollection getElementsByClassName(DOMString classNames); - [NewObject, Throws] - Element createElement(DOMString localName); - [NewObject, Throws] - Element createElementNS(DOMString? namespace, DOMString qualifiedName); + [CEReactions, NewObject, Throws] + Element createElement(DOMString localName, optional (DOMString or ElementCreationOptions) options = {}); + [CEReactions, NewObject, Throws] + Element createElementNS(DOMString? namespace, DOMString qualifiedName, + optional (DOMString or ElementCreationOptions) options = {}); [NewObject] DocumentFragment createDocumentFragment(); [NewObject] Text createTextNode(DOMString data); + [NewObject, Throws] + CDATASection createCDATASection(DOMString data); [NewObject] Comment createComment(DOMString data); [NewObject, Throws] ProcessingInstruction createProcessingInstruction(DOMString target, DOMString data); - [NewObject, Throws] + [CEReactions, NewObject, Throws] Node importNode(Node node, optional boolean deep = false); - [Throws] + [CEReactions, Throws] Node adoptNode(Node node); [NewObject, Throws] @@ -70,16 +74,20 @@ interface Document : Node { optional NodeFilter? filter = null); }; -Document implements NonElementParentNode; -Document implements ParentNode; +Document includes NonElementParentNode; +Document includes ParentNode; enum DocumentReadyState { "loading", "interactive", "complete" }; +dictionary ElementCreationOptions { + DOMString is; +}; + // https://html.spec.whatwg.org/multipage/#the-document-object // [OverrideBuiltins] partial /*sealed*/ interface Document { // resource metadata management - [/*PutForwards=href, */Unforgeable] + [PutForwards=href, Unforgeable] readonly attribute Location? location; [SetterThrows] attribute DOMString domain; readonly attribute DOMString referrer; @@ -90,9 +98,11 @@ partial /*sealed*/ interface Document { // DOM tree accessors getter object (DOMString name); + [CEReactions] attribute DOMString title; + // [CEReactions] // attribute DOMString dir; - [SetterThrows] + [CEReactions, SetterThrows] attribute HTMLElement? body; readonly attribute HTMLHeadElement? head; [SameObject] @@ -111,26 +121,28 @@ partial /*sealed*/ interface Document { readonly attribute HTMLScriptElement? currentScript; // dynamic markup insertion - [Throws] - Document open(optional DOMString type = "text/html", optional DOMString replace = ""); - // WindowProxy open(DOMString url, DOMString name, DOMString features, optional boolean replace = false); - [Throws] + [CEReactions, Throws] + Document open(optional DOMString unused1, optional DOMString unused2); + [CEReactions, Throws] + WindowProxy? open(USVString url, DOMString name, DOMString features); + [CEReactions, Throws] void close(); - [Throws] + [CEReactions, Throws] void write(DOMString... text); - [Throws] + [CEReactions, Throws] void writeln(DOMString... text); // user interaction readonly attribute Window?/*Proxy?*/ defaultView; - readonly attribute Element? activeElement; boolean hasFocus(); + // [CEReactions] // attribute DOMString designMode; + // [CEReactions] // boolean execCommand(DOMString commandId, optional boolean showUI = false, optional DOMString value = ""); // boolean queryCommandEnabled(DOMString commandId); // boolean queryCommandIndeterm(DOMString commandId); // boolean queryCommandState(DOMString commandId); - // boolean queryCommandSupported(DOMString commandId); + boolean queryCommandSupported(DOMString commandId); // DOMString queryCommandValue(DOMString commandId); // special event handler IDL attributes that only apply to Document objects @@ -138,26 +150,32 @@ partial /*sealed*/ interface Document { // also has obsolete members }; -Document implements GlobalEventHandlers; -Document implements DocumentAndElementEventHandlers; +Document includes GlobalEventHandlers; +Document includes DocumentAndElementEventHandlers; // https://html.spec.whatwg.org/multipage/#Document-partial partial interface Document { - [TreatNullAs=EmptyString] attribute DOMString fgColor; + [CEReactions] + attribute [TreatNullAs=EmptyString] DOMString fgColor; // https://github.com/servo/servo/issues/8715 - // [TreatNullAs=EmptyString] attribute DOMString linkColor; + // [CEReactions, TreatNullAs=EmptyString] + // attribute DOMString linkColor; // https://github.com/servo/servo/issues/8716 - // [TreatNullAs=EmptyString] attribute DOMString vlinkColor; + // [CEReactions, TreatNullAs=EmptyString] + // attribute DOMString vlinkColor; // https://github.com/servo/servo/issues/8717 - // [TreatNullAs=EmptyString] attribute DOMString alinkColor; + // [CEReactions, TreatNullAs=EmptyString] + // attribute DOMString alinkColor; - [TreatNullAs=EmptyString] attribute DOMString bgColor; + [CEReactions] + attribute [TreatNullAs=EmptyString] DOMString bgColor; [SameObject] readonly attribute HTMLCollection anchors; + [SameObject] readonly attribute HTMLCollection applets; @@ -169,30 +187,6 @@ partial interface Document { // readonly attribute HTMLAllCollection all; }; -// http://w3c.github.io/touch-events/#idl-def-Document -partial interface Document { - Touch createTouch(Window/*Proxy*/ view, - EventTarget target, - long identifier, - double pageX, - double pageY, - double screenX, - double screenY); - - TouchList createTouchList(Touch... touches); -}; - -// https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint -partial interface Document { - Element? elementFromPoint(double x, double y); - sequence<Element> elementsFromPoint(double x, double y); -}; - -// https://drafts.csswg.org/cssom/#extensions-to-the-document-interface -partial interface Document { - [SameObject] readonly attribute StyleSheetList styleSheets; -}; - // https://fullscreen.spec.whatwg.org/#api partial interface Document { [LenientSetter] readonly attribute boolean fullscreenEnabled; @@ -204,3 +198,17 @@ partial interface Document { attribute EventHandler onfullscreenchange; attribute EventHandler onfullscreenerror; }; + +Document includes DocumentOrShadowRoot; + +// https://w3c.github.io/selection-api/#dom-document +partial interface Document { + Selection? getSelection(); +}; + + +// Servo internal API. +partial interface Document { + [Throws] + ShadowRoot servoGetMediaControls(DOMString id); +}; diff --git a/components/script/dom/webidls/DocumentFragment.webidl b/components/script/dom/webidls/DocumentFragment.webidl index 7573dd9f22b..ec97caecf93 100644 --- a/components/script/dom/webidls/DocumentFragment.webidl +++ b/components/script/dom/webidls/DocumentFragment.webidl @@ -1,11 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://dom.spec.whatwg.org/#interface-documentfragment -[Constructor] +[Exposed=Window] interface DocumentFragment : Node { + [Throws] constructor(); }; -DocumentFragment implements NonElementParentNode; -DocumentFragment implements ParentNode; +DocumentFragment includes NonElementParentNode; +DocumentFragment includes ParentNode; diff --git a/components/script/dom/webidls/DocumentOrShadowRoot.webidl b/components/script/dom/webidls/DocumentOrShadowRoot.webidl new file mode 100644 index 00000000000..c833299482b --- /dev/null +++ b/components/script/dom/webidls/DocumentOrShadowRoot.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://dom.spec.whatwg.org/#documentorshadowroot + * https://w3c.github.io/webcomponents/spec/shadow/#extensions-to-the-documentorshadowroot-mixin + */ + +interface mixin DocumentOrShadowRoot { + // Selection? getSelection(); + Element? elementFromPoint (double x, double y); + sequence<Element> elementsFromPoint (double x, double y); + // CaretPosition? caretPositionFromPoint (double x, double y); + readonly attribute Element? activeElement; + readonly attribute StyleSheetList styleSheets; +}; diff --git a/components/script/dom/webidls/DocumentType.webidl b/components/script/dom/webidls/DocumentType.webidl index 1f7b0b83599..8d00b6df451 100644 --- a/components/script/dom/webidls/DocumentType.webidl +++ b/components/script/dom/webidls/DocumentType.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#documenttype @@ -9,6 +9,7 @@ * liability, trademark and document use rules apply. */ +[Exposed=Window] interface DocumentType : Node { [Constant] readonly attribute DOMString name; @@ -18,4 +19,4 @@ interface DocumentType : Node { readonly attribute DOMString systemId; }; -DocumentType implements ChildNode; +DocumentType includes ChildNode; diff --git a/components/script/dom/webidls/DynamicModuleOwner.webidl b/components/script/dom/webidls/DynamicModuleOwner.webidl new file mode 100644 index 00000000000..924481d491b --- /dev/null +++ b/components/script/dom/webidls/DynamicModuleOwner.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +/** + * This is defined for [`Dynamic Module`](https://html.spec.whatwg.org/multipage/#fetch-an-import()-module-script-graph) + * so that we can hold a traceable owner for those dynamic modules which don't hold a owner. + */ + +[NoInterfaceObject, Exposed=Window] +interface DynamicModuleOwner { + readonly attribute Promise<any> promise; +}; diff --git a/components/script/dom/webidls/EXTBlendMinmax.webidl b/components/script/dom/webidls/EXTBlendMinmax.webidl new file mode 100644 index 00000000000..767eace6923 --- /dev/null +++ b/components/script/dom/webidls/EXTBlendMinmax.webidl @@ -0,0 +1,13 @@ +/* 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/. */ +/* + * WebGL IDL definitions scraped from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/EXT_blend_minmax/ + */ + +[NoInterfaceObject, Exposed=Window] +interface EXTBlendMinmax { + const GLenum MIN_EXT = 0x8007; + const GLenum MAX_EXT = 0x8008; +}; diff --git a/components/script/dom/webidls/EXTColorBufferHalfFloat.webidl b/components/script/dom/webidls/EXTColorBufferHalfFloat.webidl new file mode 100644 index 00000000000..77cf23c6cd0 --- /dev/null +++ b/components/script/dom/webidls/EXTColorBufferHalfFloat.webidl @@ -0,0 +1,15 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/EXT_color_buffer_half_float/ + */ + +[NoInterfaceObject, Exposed=Window] +interface EXTColorBufferHalfFloat { + const GLenum RGBA16F_EXT = 0x881A; + const GLenum RGB16F_EXT = 0x881B; + const GLenum FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT = 0x8211; + const GLenum UNSIGNED_NORMALIZED_EXT = 0x8C17; +}; // interface EXT_color_buffer_half_float diff --git a/components/script/dom/webidls/EXTFragDepth.webidl b/components/script/dom/webidls/EXTFragDepth.webidl new file mode 100644 index 00000000000..7fde7896c4c --- /dev/null +++ b/components/script/dom/webidls/EXTFragDepth.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/EXT_frag_depth/ + */ + +[NoInterfaceObject, Exposed=Window] +interface EXTFragDepth { +}; // interface EXT_frag_depth diff --git a/components/script/dom/webidls/EXTShaderTextureLod.webidl b/components/script/dom/webidls/EXTShaderTextureLod.webidl new file mode 100644 index 00000000000..decb5ba86ac --- /dev/null +++ b/components/script/dom/webidls/EXTShaderTextureLod.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +/* + * WebGL IDL definitions scraped from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/EXT_shader_texture_lod/ + */ + +[NoInterfaceObject, Exposed=Window] +interface EXTShaderTextureLod { +}; diff --git a/components/script/dom/webidls/EXTTextureFilterAnisotropic.webidl b/components/script/dom/webidls/EXTTextureFilterAnisotropic.webidl new file mode 100644 index 00000000000..d2957500844 --- /dev/null +++ b/components/script/dom/webidls/EXTTextureFilterAnisotropic.webidl @@ -0,0 +1,13 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/ + */ + +[NoInterfaceObject, Exposed=Window] +interface EXTTextureFilterAnisotropic { + const GLenum TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE; + const GLenum MAX_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FF; +}; diff --git a/components/script/dom/webidls/Element.webidl b/components/script/dom/webidls/Element.webidl index c099b2a04ae..4c44b2cd431 100644 --- a/components/script/dom/webidls/Element.webidl +++ b/components/script/dom/webidls/Element.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#element and @@ -12,6 +12,7 @@ * liability, trademark and document use rules apply. */ +[Exposed=Window] interface Element : Node { [Constant] readonly attribute DOMString? namespaceURI; @@ -23,9 +24,9 @@ interface Element : Node { [Pure] readonly attribute DOMString tagName; - [Pure] + [CEReactions, Pure] attribute DOMString id; - [Pure] + [CEReactions, Pure] attribute DOMString className; [SameObject, PutForwards=value] readonly attribute DOMTokenList classList; @@ -40,11 +41,15 @@ interface Element : Node { DOMString? getAttribute(DOMString name); [Pure] DOMString? getAttributeNS(DOMString? namespace, DOMString localName); - [Throws] + [CEReactions, Throws] + boolean toggleAttribute(DOMString name, optional boolean force); + [CEReactions, Throws] void setAttribute(DOMString name, DOMString value); - [Throws] + [CEReactions, Throws] void setAttributeNS(DOMString? namespace, DOMString name, DOMString value); + [CEReactions] void removeAttribute(DOMString name); + [CEReactions] void removeAttributeNS(DOMString? namespace, DOMString localName); boolean hasAttribute(DOMString name); boolean hasAttributeNS(DOMString? namespace, DOMString localName); @@ -53,11 +58,11 @@ interface Element : Node { Attr? getAttributeNode(DOMString name); [Pure] Attr? getAttributeNodeNS(DOMString? namespace, DOMString localName); - [Throws] + [CEReactions, Throws] Attr? setAttributeNode(Attr attr); - [Throws] + [CEReactions, Throws] Attr? setAttributeNodeNS(Attr attr); - [Throws] + [CEReactions, Throws] Attr removeAttributeNode(Attr oldAttr); [Pure, Throws] @@ -71,12 +76,14 @@ interface Element : Node { HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName); HTMLCollection getElementsByClassName(DOMString classNames); - [Throws] + [CEReactions, Throws] Element? insertAdjacentElement(DOMString where_, Element element); // historical [Throws] void insertAdjacentText(DOMString where_, DOMString data); - [Throws] + [CEReactions, Throws] void insertAdjacentHTML(DOMString position, DOMString html); + + [Throws, Pref="dom.shadowdom.enabled"] ShadowRoot attachShadow(); }; // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-element-interface @@ -85,12 +92,12 @@ partial interface Element { [NewObject] DOMRect getBoundingClientRect(); - void scroll(optional ScrollToOptions options); + void scroll(optional ScrollToOptions options = {}); void scroll(unrestricted double x, unrestricted double y); - void scrollTo(optional ScrollToOptions options); + void scrollTo(optional ScrollToOptions options = {}); void scrollTo(unrestricted double x, unrestricted double y); - void scrollBy(optional ScrollToOptions options); + void scrollBy(optional ScrollToOptions options = {}); void scrollBy(unrestricted double x, unrestricted double y); attribute unrestricted double scrollTop; attribute unrestricted double scrollLeft; @@ -105,10 +112,10 @@ partial interface Element { // https://w3c.github.io/DOM-Parsing/#extensions-to-the-element-interface partial interface Element { - [Throws,TreatNullAs=EmptyString] - attribute DOMString innerHTML; - [Throws,TreatNullAs=EmptyString] - attribute DOMString outerHTML; + [CEReactions, Throws] + attribute [TreatNullAs=EmptyString] DOMString innerHTML; + [CEReactions, Throws] + attribute [TreatNullAs=EmptyString] DOMString outerHTML; }; // https://fullscreen.spec.whatwg.org/#api @@ -116,7 +123,7 @@ partial interface Element { Promise<void> requestFullscreen(); }; -Element implements ChildNode; -Element implements NonDocumentTypeChildNode; -Element implements ParentNode; -Element implements ActivatableElement; +Element includes ChildNode; +Element includes NonDocumentTypeChildNode; +Element includes ParentNode; +Element includes ActivatableElement; diff --git a/components/script/dom/webidls/ElementCSSInlineStyle.webidl b/components/script/dom/webidls/ElementCSSInlineStyle.webidl index 12054f7e65d..3e5696c7e8d 100644 --- a/components/script/dom/webidls/ElementCSSInlineStyle.webidl +++ b/components/script/dom/webidls/ElementCSSInlineStyle.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //http://dev.w3.org/csswg/cssom/#elementcssinlinestyle -[NoInterfaceObject, Exposed=Window] -interface ElementCSSInlineStyle { - [SameObject/*, PutForwards=cssText*/] readonly attribute CSSStyleDeclaration style; +[Exposed=Window] +interface mixin ElementCSSInlineStyle { + [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; }; diff --git a/components/script/dom/webidls/ElementContentEditable.webidl b/components/script/dom/webidls/ElementContentEditable.webidl index e9811eca835..8429700e93e 100644 --- a/components/script/dom/webidls/ElementContentEditable.webidl +++ b/components/script/dom/webidls/ElementContentEditable.webidl @@ -1,10 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#elementcontenteditable -[NoInterfaceObject, Exposed=Window] -interface ElementContentEditable { - // attribute DOMString contentEditable; - // readonly attribute boolean isContentEditable; +[Exposed=Window] +interface mixin ElementContentEditable { + [CEReactions] + attribute DOMString contentEditable; + readonly attribute boolean isContentEditable; }; diff --git a/components/script/dom/webidls/ErrorEvent.webidl b/components/script/dom/webidls/ErrorEvent.webidl index 7507f22410d..6f0782f4653 100644 --- a/components/script/dom/webidls/ErrorEvent.webidl +++ b/components/script/dom/webidls/ErrorEvent.webidl @@ -1,11 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#the-errorevent-interface -[Constructor(DOMString type, optional ErrorEventInit eventInitDict), Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface ErrorEvent : Event { + [Throws] constructor(DOMString type, optional ErrorEventInit eventInitDict = {}); readonly attribute DOMString message; readonly attribute DOMString filename; readonly attribute unsigned long lineno; diff --git a/components/script/dom/webidls/Event.webidl b/components/script/dom/webidls/Event.webidl index c55c2d36cde..0c2c122e044 100644 --- a/components/script/dom/webidls/Event.webidl +++ b/components/script/dom/webidls/Event.webidl @@ -1,16 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * For more information on this interface please see * https://dom.spec.whatwg.org/#event */ -[Constructor(DOMString type, optional EventInit eventInitDict), Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface Event { + [Throws] constructor(DOMString type, optional EventInit eventInitDict = {}); [Pure] readonly attribute DOMString type; readonly attribute EventTarget? target; + readonly attribute EventTarget? srcElement; readonly attribute EventTarget? currentTarget; const unsigned short NONE = 0; @@ -20,12 +22,14 @@ interface Event { readonly attribute unsigned short eventPhase; void stopPropagation(); + attribute boolean cancelBubble; void stopImmediatePropagation(); [Pure] readonly attribute boolean bubbles; [Pure] readonly attribute boolean cancelable; + attribute boolean returnValue; // historical void preventDefault(); [Pure] readonly attribute boolean defaultPrevented; @@ -33,9 +37,9 @@ interface Event { [Unforgeable] readonly attribute boolean isTrusted; [Constant] - readonly attribute DOMTimeStamp timeStamp; + readonly attribute DOMHighResTimeStamp timeStamp; - void initEvent(DOMString type, boolean bubbles, boolean cancelable); + void initEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false); }; dictionary EventInit { diff --git a/components/script/dom/webidls/EventHandler.webidl b/components/script/dom/webidls/EventHandler.webidl index 7bd04bf9e89..57138967792 100644 --- a/components/script/dom/webidls/EventHandler.webidl +++ b/components/script/dom/webidls/EventHandler.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://html.spec.whatwg.org/multipage/#eventhandler @@ -25,8 +25,8 @@ callback OnBeforeUnloadEventHandlerNonNull = DOMString? (Event event); typedef OnBeforeUnloadEventHandlerNonNull? OnBeforeUnloadEventHandler; // https://html.spec.whatwg.org/multipage/#globaleventhandlers -[NoInterfaceObject, Exposed=Window] -interface GlobalEventHandlers { +[Exposed=Window] +interface mixin GlobalEventHandlers { attribute EventHandler onabort; attribute EventHandler onblur; attribute EventHandler oncancel; @@ -51,6 +51,7 @@ interface GlobalEventHandlers { attribute EventHandler onended; attribute OnErrorEventHandler onerror; attribute EventHandler onfocus; + attribute EventHandler onformdata; attribute EventHandler oninput; attribute EventHandler oninvalid; attribute EventHandler onkeydown; @@ -89,20 +90,35 @@ interface GlobalEventHandlers { attribute EventHandler onwaiting; }; +// https://drafts.csswg.org/css-animations/#interface-globaleventhandlers-idl +partial interface mixin GlobalEventHandlers { + attribute EventHandler onanimationend; + attribute EventHandler onanimationiteration; +}; + // https://drafts.csswg.org/css-transitions/#interface-globaleventhandlers-idl -partial interface GlobalEventHandlers { +partial interface mixin GlobalEventHandlers { + attribute EventHandler ontransitionrun; attribute EventHandler ontransitionend; + attribute EventHandler ontransitioncancel; +}; + +// https://w3c.github.io/selection-api/#extensions-to-globaleventhandlers-interface +partial interface mixin GlobalEventHandlers { + attribute EventHandler onselectstart; + attribute EventHandler onselectionchange; }; // https://html.spec.whatwg.org/multipage/#windoweventhandlers -[NoInterfaceObject, Exposed=Window] -interface WindowEventHandlers { +[Exposed=Window] +interface mixin WindowEventHandlers { attribute EventHandler onafterprint; attribute EventHandler onbeforeprint; attribute OnBeforeUnloadEventHandler onbeforeunload; attribute EventHandler onhashchange; attribute EventHandler onlanguagechange; attribute EventHandler onmessage; + attribute EventHandler onmessageerror; attribute EventHandler onoffline; attribute EventHandler ononline; attribute EventHandler onpagehide; @@ -115,8 +131,8 @@ interface WindowEventHandlers { }; // https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers -[NoInterfaceObject, Exposed=Window] -interface DocumentAndElementEventHandlers { +[Exposed=Window] +interface mixin DocumentAndElementEventHandlers { attribute EventHandler oncopy; attribute EventHandler oncut; attribute EventHandler onpaste; diff --git a/components/script/dom/webidls/EventListener.webidl b/components/script/dom/webidls/EventListener.webidl index 9f37b80687c..f384e661c63 100644 --- a/components/script/dom/webidls/EventListener.webidl +++ b/components/script/dom/webidls/EventListener.webidl @@ -1,10 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * https://dom.spec.whatwg.org/#callbackdef-eventlistener */ +[Exposed=Window] callback interface EventListener { void handleEvent(Event event); }; diff --git a/components/script/dom/webidls/EventModifierInit.webidl b/components/script/dom/webidls/EventModifierInit.webidl index f61ba1b8219..c8abfcbddda 100644 --- a/components/script/dom/webidls/EventModifierInit.webidl +++ b/components/script/dom/webidls/EventModifierInit.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/uievents/#dictdef-eventmodifierinit dictionary EventModifierInit : UIEventInit { diff --git a/components/script/dom/webidls/EventSource.webidl b/components/script/dom/webidls/EventSource.webidl index 11c30e959d4..5525970891b 100644 --- a/components/script/dom/webidls/EventSource.webidl +++ b/components/script/dom/webidls/EventSource.webidl @@ -1,14 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is: * https://html.spec.whatwg.org/multipage/#eventsource */ -[Constructor(DOMString url, optional EventSourceInit eventSourceInitDict), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface EventSource : EventTarget { + [Throws] constructor(DOMString url, optional EventSourceInit eventSourceInitDict = {}); readonly attribute DOMString url; readonly attribute boolean withCredentials; diff --git a/components/script/dom/webidls/EventTarget.webidl b/components/script/dom/webidls/EventTarget.webidl index ee6e5d722a8..dd0236c910c 100644 --- a/components/script/dom/webidls/EventTarget.webidl +++ b/components/script/dom/webidls/EventTarget.webidl @@ -1,18 +1,34 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * https://dom.spec.whatwg.org/#interface-eventtarget */ -[Abstract, Exposed=(Window,Worker)] +[Exposed=(Window,Worker,Worklet,DissimilarOriginWindow)] interface EventTarget { - void addEventListener(DOMString type, - EventListener? listener, - optional boolean capture = false); - void removeEventListener(DOMString type, - EventListener? listener, - optional boolean capture = false); + [Throws] constructor(); + void addEventListener( + DOMString type, + EventListener? callback, + optional (AddEventListenerOptions or boolean) options = {} + ); + + void removeEventListener( + DOMString type, + EventListener? callback, + optional (EventListenerOptions or boolean) options = {} + ); + [Throws] boolean dispatchEvent(Event event); }; + +dictionary EventListenerOptions { + boolean capture = false; +}; + +dictionary AddEventListenerOptions : EventListenerOptions { + // boolean passive = false; + boolean once = false; +}; diff --git a/components/script/dom/webidls/ExtendableEvent.webidl b/components/script/dom/webidls/ExtendableEvent.webidl index 09cb253e892..99cafa305a6 100644 --- a/components/script/dom/webidls/ExtendableEvent.webidl +++ b/components/script/dom/webidls/ExtendableEvent.webidl @@ -1,14 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/ServiceWorker/#extendable-event -[Constructor(DOMString type, - optional ExtendableEventInit eventInitDict), - Exposed=ServiceWorker, +[Exposed=ServiceWorker, Pref="dom.serviceworker.enabled"] interface ExtendableEvent : Event { + [Throws] constructor(DOMString type, + optional ExtendableEventInit eventInitDict = {}); [Throws] void waitUntil(/*Promise<*/any/*>*/ f); }; diff --git a/components/script/dom/webidls/ExtendableMessageEvent.webidl b/components/script/dom/webidls/ExtendableMessageEvent.webidl index 4190757d1f2..247c08e7989 100644 --- a/components/script/dom/webidls/ExtendableMessageEvent.webidl +++ b/components/script/dom/webidls/ExtendableMessageEvent.webidl @@ -1,24 +1,24 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/ServiceWorker/#extendablemessage-event-section -[Constructor(DOMString type, optional ExtendableMessageEventInit eventInitDict), - Exposed=ServiceWorker, +[Exposed=ServiceWorker, Pref="dom.serviceworker.enabled"] interface ExtendableMessageEvent : ExtendableEvent { + [Throws] constructor(DOMString type, optional ExtendableMessageEventInit eventInitDict = {}); readonly attribute any data; readonly attribute DOMString origin; readonly attribute DOMString lastEventId; // [SameObject] readonly attribute (Client or ServiceWorker /*or MessagePort*/)? source; - // readonly attribute FrozenArray<MessagePort>? ports; + readonly attribute /*FrozenArray<MessagePort>*/any ports; }; dictionary ExtendableMessageEventInit : ExtendableEventInit { - any data; - DOMString origin; - DOMString lastEventId; + any data = null; + DOMString origin = ""; + DOMString lastEventId = ""; // (Client or ServiceWorker /*or MessagePort*/)? source; - // sequence<MessagePort>? ports; + sequence<MessagePort> ports = []; }; diff --git a/components/script/dom/webidls/FakeXRDevice.webidl b/components/script/dom/webidls/FakeXRDevice.webidl new file mode 100644 index 00000000000..6349d70963b --- /dev/null +++ b/components/script/dom/webidls/FakeXRDevice.webidl @@ -0,0 +1,90 @@ +/* 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/. */ + +// https://github.com/immersive-web/webxr-test-api/ + +[Exposed=Window, Pref="dom.webxr.test"] +interface FakeXRDevice { + // Sets the values to be used for subsequent + // requestAnimationFrame() callbacks. + [Throws] void setViews(sequence<FakeXRViewInit> views); + + [Throws] void setViewerOrigin(FakeXRRigidTransformInit origin, optional boolean emulatedPosition = false); + void clearViewerOrigin(); + + [Throws] void setFloorOrigin(FakeXRRigidTransformInit origin); + void clearFloorOrigin(); + + // // Simulates devices focusing and blurring sessions. + void simulateVisibilityChange(XRVisibilityState state); + + // void setBoundsGeometry(sequence<FakeXRBoundsPoint> boundsCoodinates); + + [Throws] FakeXRInputController simulateInputSourceConnection(FakeXRInputSourceInit init); + + // behaves as if device was disconnected + Promise<void> disconnect(); + + // Hit test extensions: + [Throws] void setWorld(FakeXRWorldInit world); + void clearWorld(); +}; + +// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport +dictionary FakeXRViewInit { + required XREye eye; + // https://immersive-web.github.io/webxr/#view-projection-matrix + required sequence<float> projectionMatrix; + // https://immersive-web.github.io/webxr/#view-offset + required FakeXRRigidTransformInit viewOffset; + // https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport + required FakeXRDeviceResolution resolution; + + FakeXRFieldOfViewInit fieldOfView; +}; + +// https://immersive-web.github.io/webxr/#xrviewport +dictionary FakeXRDeviceResolution { + required long width; + required long height; +}; + +dictionary FakeXRBoundsPoint { + double x; double z; +}; + +dictionary FakeXRRigidTransformInit { + required sequence<float> position; + required sequence<float> orientation; +}; + +dictionary FakeXRFieldOfViewInit { + required float upDegrees; + required float downDegrees; + required float leftDegrees; + required float rightDegrees; +}; + +// hit testing +dictionary FakeXRWorldInit { + required sequence<FakeXRRegionInit> hitTestRegions; +}; + + +dictionary FakeXRRegionInit { + required sequence<FakeXRTriangleInit> faces; + required FakeXRRegionType type; +}; + + +dictionary FakeXRTriangleInit { + required sequence<DOMPointInit> vertices; // size = 3 +}; + + +enum FakeXRRegionType { + "point", + "plane", + "mesh" +}; diff --git a/components/script/dom/webidls/FakeXRInputController.webidl b/components/script/dom/webidls/FakeXRInputController.webidl new file mode 100644 index 00000000000..b8faad1038a --- /dev/null +++ b/components/script/dom/webidls/FakeXRInputController.webidl @@ -0,0 +1,53 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr-test-api/#fakexrinputcontroller + +[Exposed=Window, Pref="dom.webxr.test"] +interface FakeXRInputController { + void setHandedness(XRHandedness handedness); + void setTargetRayMode(XRTargetRayMode targetRayMode); + void setProfiles(sequence<DOMString> profiles); + [Throws] void setGripOrigin(FakeXRRigidTransformInit gripOrigin, optional boolean emulatedPosition = false); + void clearGripOrigin(); + [Throws] void setPointerOrigin(FakeXRRigidTransformInit pointerOrigin, optional boolean emulatedPosition = false); + + void disconnect(); + void reconnect(); + + void startSelection(); + void endSelection(); + void simulateSelect(); + + // void setSupportedButtons(sequence<FakeXRButtonStateInit> supportedButtons); + // void updateButtonState(FakeXRButtonStateInit buttonState); +}; + +dictionary FakeXRInputSourceInit { + required XRHandedness handedness; + required XRTargetRayMode targetRayMode; + required FakeXRRigidTransformInit pointerOrigin; + required sequence<DOMString> profiles; + boolean selectionStarted = false; + boolean selectionClicked = false; + sequence<FakeXRButtonStateInit> supportedButtons; + FakeXRRigidTransformInit gripOrigin; +}; + +enum FakeXRButtonType { + "grip", + "touchpad", + "thumbstick", + "optional-button", + "optional-thumbstick" +}; + +dictionary FakeXRButtonStateInit { + required FakeXRButtonType buttonType; + required boolean pressed; + required boolean touched; + required float pressedValue; + float xValue = 0.0; + float yValue = 0.0; +}; diff --git a/components/script/dom/webidls/Fetch.webidl b/components/script/dom/webidls/Fetch.webidl index fe062994598..abedcf620ea 100644 --- a/components/script/dom/webidls/Fetch.webidl +++ b/components/script/dom/webidls/Fetch.webidl @@ -1,11 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://fetch.spec.whatwg.org/#fetch-method [Exposed=(Window,Worker)] -partial interface WindowOrWorkerGlobalScope { - [NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init); +partial interface mixin WindowOrWorkerGlobalScope { + [NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init = {}); }; diff --git a/components/script/dom/webidls/File.webidl b/components/script/dom/webidls/File.webidl index 9404abe5034..143df2f8f13 100644 --- a/components/script/dom/webidls/File.webidl +++ b/components/script/dom/webidls/File.webidl @@ -1,14 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/FileAPI/#file -[Constructor(sequence<BlobPart> fileBits, - DOMString fileName, - optional FilePropertyBag options), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface File : Blob { + [Throws] constructor(sequence<BlobPart> fileBits, + DOMString fileName, + optional FilePropertyBag options = {}); readonly attribute DOMString name; readonly attribute long long lastModified; }; diff --git a/components/script/dom/webidls/FileList.webidl b/components/script/dom/webidls/FileList.webidl index 59ebae5de3b..d849bd323ef 100644 --- a/components/script/dom/webidls/FileList.webidl +++ b/components/script/dom/webidls/FileList.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/FileAPI/#dfn-filelist diff --git a/components/script/dom/webidls/FileReader.webidl b/components/script/dom/webidls/FileReader.webidl index be00e39fd77..a1c01fb716c 100644 --- a/components/script/dom/webidls/FileReader.webidl +++ b/components/script/dom/webidls/FileReader.webidl @@ -1,12 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // http://dev.w3.org/2006/webapi/FileAPI/#APIASynch typedef (DOMString or object) FileReaderResult; -[Constructor, Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface FileReader: EventTarget { + [Throws] constructor(); // async read methods [Throws] diff --git a/components/script/dom/webidls/FileReaderSync.webidl b/components/script/dom/webidls/FileReaderSync.webidl index cbc18a47921..08248839958 100644 --- a/components/script/dom/webidls/FileReaderSync.webidl +++ b/components/script/dom/webidls/FileReaderSync.webidl @@ -1,15 +1,20 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/FileAPI/#FileReaderSync -[Constructor, Exposed=Worker] +[Exposed=Worker] interface FileReaderSync { + [Throws] constructor(); // Synchronously return strings - // ArrayBuffer readAsArrayBuffer(Blob blob); - // DOMString readAsBinaryString(Blob blob); - // DOMString readAsText(Blob blob, optional DOMString label); - // DOMString readAsDataURL(Blob blob); + [Throws] + ArrayBuffer readAsArrayBuffer(Blob blob); + [Throws] + DOMString readAsBinaryString(Blob blob); + [Throws] + DOMString readAsText(Blob blob, optional DOMString label); + [Throws] + DOMString readAsDataURL(Blob blob); }; diff --git a/components/script/dom/webidls/FocusEvent.webidl b/components/script/dom/webidls/FocusEvent.webidl index 14ec3ae67e9..1d5e4ee4bea 100644 --- a/components/script/dom/webidls/FocusEvent.webidl +++ b/components/script/dom/webidls/FocusEvent.webidl @@ -1,11 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/uievents/#interface-FocusEvent -[Constructor(DOMString typeArg, optional FocusEventInit focusEventInitDict), - Exposed=Window] +[Exposed=Window] interface FocusEvent : UIEvent { + [Throws] constructor(DOMString typeArg, optional FocusEventInit focusEventInitDict = {}); readonly attribute EventTarget? relatedTarget; }; diff --git a/components/script/dom/webidls/ForceTouchEvent.webidl b/components/script/dom/webidls/ForceTouchEvent.webidl deleted file mode 100644 index 4c184214cae..00000000000 --- a/components/script/dom/webidls/ForceTouchEvent.webidl +++ /dev/null @@ -1,34 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://developer.apple.com/library/mac/documentation/AppleApplications/Conceptual/SafariJSProgTopics/RespondingtoForceTouchEventsfromJavaScript.html - -/** - * Events: (copy/paste from apple.com) - * - * webkitmouseforcewillbegin: This event occurs immediately before the mousedown event. It allows you to - * prevent the default system behavior, such as displaying a dictionary window when force clicking on a - * word, in order to perform a custom action instead. To prevent the default system behavior, call the - * preventDefault() method on the event. - * webkitmouseforcedown: This event occurs after the mousedown event, once enough force has been applied - * to register as a force click. The user receives haptic feedback representing the force click when this - * event occurs. - * webkitmouseforceup: This event occurs after a webkitmouseforcedown event, once enough force has been - * released to exit the force click operation. The user receives haptic feedback representing the exit - * from force click when this event occurs. - * webkitmouseforcechanged: This event occurs whenever a change in trackpad force is detected between the - * mousedown and mouseup events. - * - */ - - -[Pref="dom.forcetouch.enabled"] -interface ForceTouchEvent : UIEvent { - // Represents the amount of force required to perform a regular click. - readonly attribute float SERVO_FORCE_AT_MOUSE_DOWN; - // Represents the force required to perform a force click. - readonly attribute float SERVO_FORCE_AT_FORCE_MOUSE_DOWN; - // force level - readonly attribute float servoForce; -}; diff --git a/components/script/dom/webidls/FormData.webidl b/components/script/dom/webidls/FormData.webidl index 3c3b3a8d01a..4e2f5de04ec 100644 --- a/components/script/dom/webidls/FormData.webidl +++ b/components/script/dom/webidls/FormData.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://xhr.spec.whatwg.org/#interface-formdata @@ -8,9 +8,9 @@ typedef (File or USVString) FormDataEntryValue; -[Constructor(optional HTMLFormElement form), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface FormData { + [Throws] constructor(optional HTMLFormElement form); void append(USVString name, USVString value); void append(USVString name, Blob value, optional USVString filename); void delete(USVString name); diff --git a/components/script/dom/webidls/FormDataEvent.webidl b/components/script/dom/webidls/FormDataEvent.webidl new file mode 100644 index 00000000000..0cb81b93962 --- /dev/null +++ b/components/script/dom/webidls/FormDataEvent.webidl @@ -0,0 +1,14 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#the-formdataevent-interface +[Exposed=Window] +interface FormDataEvent : Event { + [Throws] constructor(DOMString type, FormDataEventInit eventInitDict); + readonly attribute FormData formData; +}; + +dictionary FormDataEventInit : EventInit { + required FormData formData; +}; diff --git a/components/script/dom/webidls/Function.webidl b/components/script/dom/webidls/Function.webidl index 08513dfadf3..4694df61999 100644 --- a/components/script/dom/webidls/Function.webidl +++ b/components/script/dom/webidls/Function.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://heycam.github.io/webidl/#common-Function diff --git a/components/script/dom/webidls/GPU.webidl b/components/script/dom/webidls/GPU.webidl new file mode 100644 index 00000000000..e0e6eb57b27 --- /dev/null +++ b/components/script/dom/webidls/GPU.webidl @@ -0,0 +1,20 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpu-interface +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPU { + Promise<GPUAdapter?> requestAdapter(optional GPURequestAdapterOptions options = {}); +}; + +// https://gpuweb.github.io/gpuweb/#dictdef-gpurequestadapteroptions +dictionary GPURequestAdapterOptions { + GPUPowerPreference powerPreference; +}; + +// https://gpuweb.github.io/gpuweb/#enumdef-gpupowerpreference +enum GPUPowerPreference { + "low-power", + "high-performance" +}; diff --git a/components/script/dom/webidls/GPUAdapter.webidl b/components/script/dom/webidls/GPUAdapter.webidl new file mode 100644 index 00000000000..e4e4e86791c --- /dev/null +++ b/components/script/dom/webidls/GPUAdapter.webidl @@ -0,0 +1,39 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpuadapter +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUAdapter { + readonly attribute DOMString name; + readonly attribute object extensions; + //readonly attribute GPULimits limits; Don’t expose higher limits for now. + + Promise<GPUDevice?> requestDevice(optional GPUDeviceDescriptor descriptor = {}); +}; + +dictionary GPUDeviceDescriptor : GPUObjectDescriptorBase { + sequence<GPUExtensionName> extensions = []; + GPULimits limits = {}; +}; + +enum GPUExtensionName { + "depth-clamping", + "depth24unorm-stencil8", + "depth32float-stencil8", + "pipeline-statistics-query", + "texture-compression-bc", + "timestamp-query", +}; + +dictionary GPULimits { + GPUSize32 maxBindGroups = 4; + GPUSize32 maxDynamicUniformBuffersPerPipelineLayout = 8; + GPUSize32 maxDynamicStorageBuffersPerPipelineLayout = 4; + GPUSize32 maxSampledTexturesPerShaderStage = 16; + GPUSize32 maxSamplersPerShaderStage = 16; + GPUSize32 maxStorageBuffersPerShaderStage = 4; + GPUSize32 maxStorageTexturesPerShaderStage = 4; + GPUSize32 maxUniformBuffersPerShaderStage = 12; + GPUSize32 maxUniformBufferBindingSize = 16384; +}; diff --git a/components/script/dom/webidls/GPUBindGroup.webidl b/components/script/dom/webidls/GPUBindGroup.webidl new file mode 100644 index 00000000000..7bfa023acba --- /dev/null +++ b/components/script/dom/webidls/GPUBindGroup.webidl @@ -0,0 +1,29 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpubindgrouplayout +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUBindGroup { +}; +GPUBindGroup includes GPUObjectBase; + +dictionary GPUBindGroupDescriptor : GPUObjectDescriptorBase { + required GPUBindGroupLayout layout; + required sequence<GPUBindGroupEntry> entries; +}; + +typedef (GPUSampler or GPUTextureView or GPUBufferBindings) GPUBindingResource; + +dictionary GPUBindGroupEntry { + required GPUIndex32 binding; + required GPUBindingResource resource; +}; + +// Note: Servo codegen doesn't like the name `GPUBufferBinding` because it's already occupied +// dictionary GPUBufferBinding { +dictionary GPUBufferBindings { + required GPUBuffer buffer; + GPUSize64 offset = 0; + GPUSize64 size; +}; diff --git a/components/script/dom/webidls/GPUBindGroupLayout.webidl b/components/script/dom/webidls/GPUBindGroupLayout.webidl new file mode 100644 index 00000000000..5fc58139b14 --- /dev/null +++ b/components/script/dom/webidls/GPUBindGroupLayout.webidl @@ -0,0 +1,36 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpubindgrouplayout +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUBindGroupLayout { +}; +GPUBindGroupLayout includes GPUObjectBase; + +dictionary GPUBindGroupLayoutDescriptor : GPUObjectDescriptorBase { + required sequence<GPUBindGroupLayoutEntry> entries; +}; + +dictionary GPUBindGroupLayoutEntry { + required GPUIndex32 binding; + required GPUShaderStageFlags visibility; + required GPUBindingType type; + boolean hasDynamicOffset; + GPUSize64 minBufferBindingSize; + GPUTextureViewDimension viewDimension; + GPUTextureComponentType textureComponentType; + GPUTextureFormat storageTextureFormat; +}; + +enum GPUBindingType { + "uniform-buffer", + "storage-buffer", + "readonly-storage-buffer", + "sampler", + "comparison-sampler", + "sampled-texture", + "multisampled-texture", + "readonly-storage-texture", + "writeonly-storage-texture" +}; diff --git a/components/script/dom/webidls/GPUBuffer.webidl b/components/script/dom/webidls/GPUBuffer.webidl new file mode 100644 index 00000000000..2097db233e9 --- /dev/null +++ b/components/script/dom/webidls/GPUBuffer.webidl @@ -0,0 +1,22 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpubuffer +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUBuffer { + Promise<void> mapAsync(GPUMapModeFlags mode, optional GPUSize64 offset = 0, optional GPUSize64 size); + [Throws] ArrayBuffer getMappedRange(optional GPUSize64 offset = 0, optional GPUSize64 size); + void unmap(); + + void destroy(); +}; +GPUBuffer includes GPUObjectBase; + +dictionary GPUBufferDescriptor : GPUObjectDescriptorBase { + required GPUSize64 size; + required GPUBufferUsageFlags usage; + boolean mappedAtCreation = false; +}; + +typedef unsigned long long GPUSize64; diff --git a/components/script/dom/webidls/GPUBufferUsage.webidl b/components/script/dom/webidls/GPUBufferUsage.webidl new file mode 100644 index 00000000000..6db1d029f18 --- /dev/null +++ b/components/script/dom/webidls/GPUBufferUsage.webidl @@ -0,0 +1,20 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#buffer-usage +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUBufferUsage { + const GPUBufferUsageFlags MAP_READ = 0x0001; + const GPUBufferUsageFlags MAP_WRITE = 0x0002; + const GPUBufferUsageFlags COPY_SRC = 0x0004; + const GPUBufferUsageFlags COPY_DST = 0x0008; + const GPUBufferUsageFlags INDEX = 0x0010; + const GPUBufferUsageFlags VERTEX = 0x0020; + const GPUBufferUsageFlags UNIFORM = 0x0040; + const GPUBufferUsageFlags STORAGE = 0x0080; + const GPUBufferUsageFlags INDIRECT = 0x0100; + const GPUBufferUsageFlags QUERY_RESOLVE = 0x0200; +}; + +typedef [EnforceRange] unsigned long GPUBufferUsageFlags; diff --git a/components/script/dom/webidls/GPUCanvasContext.webidl b/components/script/dom/webidls/GPUCanvasContext.webidl new file mode 100644 index 00000000000..5e19f1467e6 --- /dev/null +++ b/components/script/dom/webidls/GPUCanvasContext.webidl @@ -0,0 +1,17 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpucanvascontext +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUCanvasContext { + GPUSwapChain configureSwapChain(GPUSwapChainDescriptor descriptor); + + //Promise<GPUTextureFormat> getSwapChainPreferredFormat(GPUDevice device); +}; + +dictionary GPUSwapChainDescriptor : GPUObjectDescriptorBase { + required GPUDevice device; + required GPUTextureFormat format; + GPUTextureUsageFlags usage = 0x10; // GPUTextureUsage.OUTPUT_ATTACHMENT +}; diff --git a/components/script/dom/webidls/GPUColorWrite.webidl b/components/script/dom/webidls/GPUColorWrite.webidl new file mode 100644 index 00000000000..e4a74fe5f6a --- /dev/null +++ b/components/script/dom/webidls/GPUColorWrite.webidl @@ -0,0 +1,15 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpucolorwrite +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUColorWrite { + const GPUColorWriteFlags RED = 0x1; + const GPUColorWriteFlags GREEN = 0x2; + const GPUColorWriteFlags BLUE = 0x4; + const GPUColorWriteFlags ALPHA = 0x8; + const GPUColorWriteFlags ALL = 0xF; +}; + +typedef [EnforceRange] unsigned long GPUColorWriteFlags; diff --git a/components/script/dom/webidls/GPUCommandBuffer.webidl b/components/script/dom/webidls/GPUCommandBuffer.webidl new file mode 100644 index 00000000000..7fca9a908ac --- /dev/null +++ b/components/script/dom/webidls/GPUCommandBuffer.webidl @@ -0,0 +1,10 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpucommandbuffer +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUCommandBuffer { + //readonly attribute Promise<double> executionTime; +}; +GPUCommandBuffer includes GPUObjectBase; diff --git a/components/script/dom/webidls/GPUCommandEncoder.webidl b/components/script/dom/webidls/GPUCommandEncoder.webidl new file mode 100644 index 00000000000..00a8e1ad537 --- /dev/null +++ b/components/script/dom/webidls/GPUCommandEncoder.webidl @@ -0,0 +1,122 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpucommandencoder +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUCommandEncoder { + GPURenderPassEncoder beginRenderPass(GPURenderPassDescriptor descriptor); + GPUComputePassEncoder beginComputePass(optional GPUComputePassDescriptor descriptor = {}); + + void copyBufferToBuffer( + GPUBuffer source, + GPUSize64 sourceOffset, + GPUBuffer destination, + GPUSize64 destinationOffset, + GPUSize64 size); + + void copyBufferToTexture( + GPUBufferCopyView source, + GPUTextureCopyView destination, + GPUExtent3D copySize); + + void copyTextureToBuffer( + GPUTextureCopyView source, + GPUBufferCopyView destination, + GPUExtent3D copySize); + + void copyTextureToTexture( + GPUTextureCopyView source, + GPUTextureCopyView destination, + GPUExtent3D copySize); + + //void pushDebugGroup(USVString groupLabel); + //void popDebugGroup(); + //void insertDebugMarker(USVString markerLabel); + + //void writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + + //void resolveQuerySet( + // GPUQuerySet querySet, + // GPUSize32 firstQuery, + // GPUSize32 queryCount, + // GPUBuffer destination, + // GPUSize64 destinationOffset); + + GPUCommandBuffer finish(optional GPUCommandBufferDescriptor descriptor = {}); +}; +GPUCommandEncoder includes GPUObjectBase; + +dictionary GPUComputePassDescriptor : GPUObjectDescriptorBase { +}; + +dictionary GPUCommandBufferDescriptor : GPUObjectDescriptorBase { +}; + +dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase { + required sequence<GPURenderPassColorAttachmentDescriptor> colorAttachments; + GPURenderPassDepthStencilAttachmentDescriptor depthStencilAttachment; + //GPUQuerySet occlusionQuerySet; +}; + +dictionary GPURenderPassColorAttachmentDescriptor { + required GPUTextureView attachment; + GPUTextureView resolveTarget; + + required (GPULoadOp or GPUColor) loadValue; + GPUStoreOp storeOp = "store"; +}; + +dictionary GPURenderPassDepthStencilAttachmentDescriptor { + required GPUTextureView attachment; + + required (GPULoadOp or float) depthLoadValue; + required GPUStoreOp depthStoreOp; + boolean depthReadOnly = false; + + required GPUStencilLoadValue stencilLoadValue; + required GPUStoreOp stencilStoreOp; + boolean stencilReadOnly = false; +}; + +typedef (GPULoadOp or GPUStencilValue) GPUStencilLoadValue; + +enum GPULoadOp { + "load" +}; + +enum GPUStoreOp { + "store", + "clear" +}; + +dictionary GPUColorDict { + required double r; + required double g; + required double b; + required double a; +}; +typedef (sequence<double> or GPUColorDict) GPUColor; + +dictionary GPUTextureDataLayout { + GPUSize64 offset = 0; + required GPUSize32 bytesPerRow; + GPUSize32 rowsPerImage = 0; +}; + +dictionary GPUBufferCopyView : GPUTextureDataLayout { + required GPUBuffer buffer; +}; + +dictionary GPUTextureCopyView { + required GPUTexture texture; + GPUIntegerCoordinate mipLevel = 0; + GPUOrigin3D origin = {}; +}; + +dictionary GPUOrigin3DDict { + GPUIntegerCoordinate x = 0; + GPUIntegerCoordinate y = 0; + GPUIntegerCoordinate z = 0; +}; +typedef (sequence<GPUIntegerCoordinate> or GPUOrigin3DDict) GPUOrigin3D; diff --git a/components/script/dom/webidls/GPUComputePassEncoder.webidl b/components/script/dom/webidls/GPUComputePassEncoder.webidl new file mode 100644 index 00000000000..f7c0eba2138 --- /dev/null +++ b/components/script/dom/webidls/GPUComputePassEncoder.webidl @@ -0,0 +1,22 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpucomputepassencoder +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUComputePassEncoder { + void setPipeline(GPUComputePipeline pipeline); + void dispatch(GPUSize32 x, optional GPUSize32 y = 1, optional GPUSize32 z = 1); + void dispatchIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); + + //void beginPipelineStatisticsQuery(GPUQuerySet querySet, GPUSize32 queryIndex); + //void endPipelineStatisticsQuery(); + + //void writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + + void endPass(); +}; +GPUComputePassEncoder includes GPUObjectBase; +GPUComputePassEncoder includes GPUProgrammablePassEncoder; + +typedef [EnforceRange] unsigned long GPUSize32; diff --git a/components/script/dom/webidls/GPUComputePipeline.webidl b/components/script/dom/webidls/GPUComputePipeline.webidl new file mode 100644 index 00000000000..a7599a13803 --- /dev/null +++ b/components/script/dom/webidls/GPUComputePipeline.webidl @@ -0,0 +1,27 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpucomputepipeline +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUComputePipeline { +}; +GPUComputePipeline includes GPUObjectBase; +GPUComputePipeline includes GPUPipelineBase; + +dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase { + GPUPipelineLayout layout; +}; + +dictionary GPUProgrammableStageDescriptor { + required GPUShaderModule module; + required DOMString entryPoint; +}; + +dictionary GPUComputePipelineDescriptor : GPUPipelineDescriptorBase { + required GPUProgrammableStageDescriptor computeStage; +}; + +interface mixin GPUPipelineBase { + [Throws] GPUBindGroupLayout getBindGroupLayout(unsigned long index); +}; diff --git a/components/script/dom/webidls/GPUDevice.webidl b/components/script/dom/webidls/GPUDevice.webidl new file mode 100644 index 00000000000..a82bea80ced --- /dev/null +++ b/components/script/dom/webidls/GPUDevice.webidl @@ -0,0 +1,37 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpudevice +[Exposed=(Window, DedicatedWorker), /*Serializable,*/ Pref="dom.webgpu.enabled"] +interface GPUDevice : EventTarget { + [SameObject] readonly attribute GPUAdapter adapter; + readonly attribute object extensions; + readonly attribute object limits; + + [SameObject] readonly attribute GPUQueue defaultQueue; + + GPUBuffer createBuffer(GPUBufferDescriptor descriptor); + GPUTexture createTexture(GPUTextureDescriptor descriptor); + GPUSampler createSampler(optional GPUSamplerDescriptor descriptor = {}); + + GPUBindGroupLayout createBindGroupLayout(GPUBindGroupLayoutDescriptor descriptor); + GPUPipelineLayout createPipelineLayout(GPUPipelineLayoutDescriptor descriptor); + GPUBindGroup createBindGroup(GPUBindGroupDescriptor descriptor); + + GPUShaderModule createShaderModule(GPUShaderModuleDescriptor descriptor); + GPUComputePipeline createComputePipeline(GPUComputePipelineDescriptor descriptor); + GPURenderPipeline createRenderPipeline(GPURenderPipelineDescriptor descriptor); + //Promise<GPUComputePipeline> createReadyComputePipeline(GPUComputePipelineDescriptor descriptor); + //Promise<GPURenderPipeline> createReadyRenderPipeline(GPURenderPipelineDescriptor descriptor); + + GPUCommandEncoder createCommandEncoder(optional GPUCommandEncoderDescriptor descriptor = {}); + GPURenderBundleEncoder createRenderBundleEncoder(GPURenderBundleEncoderDescriptor descriptor); + + //GPUQuerySet createQuerySet(GPUQuerySetDescriptor descriptor); +}; +GPUDevice includes GPUObjectBase; + +dictionary GPUCommandEncoderDescriptor : GPUObjectDescriptorBase { + boolean measureExecutionTime = false; +}; diff --git a/components/script/dom/webidls/GPUDeviceLostInfo.webidl b/components/script/dom/webidls/GPUDeviceLostInfo.webidl new file mode 100644 index 00000000000..cd9ec544408 --- /dev/null +++ b/components/script/dom/webidls/GPUDeviceLostInfo.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpudevicelostinfo +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUDeviceLostInfo { + readonly attribute DOMString message; +}; + +partial interface GPUDevice { + readonly attribute Promise<GPUDeviceLostInfo> lost; +}; diff --git a/components/script/dom/webidls/GPUMapMode.webidl b/components/script/dom/webidls/GPUMapMode.webidl new file mode 100644 index 00000000000..0dc52c4b448 --- /dev/null +++ b/components/script/dom/webidls/GPUMapMode.webidl @@ -0,0 +1,12 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpumapmode +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUMapMode { + const GPUMapModeFlags READ = 0x0001; + const GPUMapModeFlags WRITE = 0x0002; +}; + +typedef [EnforceRange] unsigned long GPUMapModeFlags; diff --git a/components/script/dom/webidls/GPUObjectBase.webidl b/components/script/dom/webidls/GPUObjectBase.webidl new file mode 100644 index 00000000000..524016d15ce --- /dev/null +++ b/components/script/dom/webidls/GPUObjectBase.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpuobjectbase +[Exposed=(Window)] +interface mixin GPUObjectBase { + attribute USVString? label; +}; + +dictionary GPUObjectDescriptorBase { + USVString label; +}; diff --git a/components/script/dom/webidls/GPUOutOfMemoryError.webidl b/components/script/dom/webidls/GPUOutOfMemoryError.webidl new file mode 100644 index 00000000000..470684edaab --- /dev/null +++ b/components/script/dom/webidls/GPUOutOfMemoryError.webidl @@ -0,0 +1,9 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpuoutofmemoryerror +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUOutOfMemoryError { + constructor(); +}; diff --git a/components/script/dom/webidls/GPUPipelineLayout.webidl b/components/script/dom/webidls/GPUPipelineLayout.webidl new file mode 100644 index 00000000000..a50c2c60f40 --- /dev/null +++ b/components/script/dom/webidls/GPUPipelineLayout.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#pipeline-layout +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUPipelineLayout { +}; +GPUPipelineLayout includes GPUObjectBase; + +dictionary GPUPipelineLayoutDescriptor : GPUObjectDescriptorBase { + required sequence<GPUBindGroupLayout> bindGroupLayouts; +}; diff --git a/components/script/dom/webidls/GPUProgrammablePassEncoder.webidl b/components/script/dom/webidls/GPUProgrammablePassEncoder.webidl new file mode 100644 index 00000000000..71bfdcb163e --- /dev/null +++ b/components/script/dom/webidls/GPUProgrammablePassEncoder.webidl @@ -0,0 +1,22 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpuprogrammablepassencoder +[Exposed=(Window, DedicatedWorker)] +interface mixin GPUProgrammablePassEncoder { + void setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + optional sequence<GPUBufferDynamicOffset> dynamicOffsets = []); + + // void setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + // Uint32Array dynamicOffsetsData, + // GPUSize64 dynamicOffsetsDataStart, + // GPUSize64 dynamicOffsetsDataLength); + + // void pushDebugGroup(DOMString groupLabel); + // void popDebugGroup(); + // void insertDebugMarker(DOMString markerLabel); +}; + +typedef [EnforceRange] unsigned long GPUBufferDynamicOffset; +typedef [EnforceRange] unsigned long GPUIndex32; diff --git a/components/script/dom/webidls/GPUQueue.webidl b/components/script/dom/webidls/GPUQueue.webidl new file mode 100644 index 00000000000..a0fede8415e --- /dev/null +++ b/components/script/dom/webidls/GPUQueue.webidl @@ -0,0 +1,31 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpuqueue +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUQueue { + void submit(sequence<GPUCommandBuffer> commandBuffers); + + //GPUFence createFence(optional GPUFenceDescriptor descriptor = {}); + //void signal(GPUFence fence, GPUFenceValue signalValue); + + [Throws] void writeBuffer( + GPUBuffer buffer, + GPUSize64 bufferOffset, + /*[AllowShared]*/ BufferSource data, + optional GPUSize64 dataOffset = 0, + optional GPUSize64 size); + + [Throws] void writeTexture( + GPUTextureCopyView destination, + /*[AllowShared]*/ BufferSource data, + GPUTextureDataLayout dataLayout, + GPUExtent3D size); + + //void copyImageBitmapToTexture( + // GPUImageBitmapCopyView source, + // GPUTextureCopyView destination, + // GPUExtent3D copySize); +}; +GPUQueue includes GPUObjectBase; diff --git a/components/script/dom/webidls/GPURenderBundle.webidl b/components/script/dom/webidls/GPURenderBundle.webidl new file mode 100644 index 00000000000..52a8e5b0bc8 --- /dev/null +++ b/components/script/dom/webidls/GPURenderBundle.webidl @@ -0,0 +1,12 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpurenderbundle +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPURenderBundle { +}; +GPURenderBundle includes GPUObjectBase; + +dictionary GPURenderBundleDescriptor : GPUObjectDescriptorBase { +}; diff --git a/components/script/dom/webidls/GPURenderBundleEncoder.webidl b/components/script/dom/webidls/GPURenderBundleEncoder.webidl new file mode 100644 index 00000000000..50676b431b9 --- /dev/null +++ b/components/script/dom/webidls/GPURenderBundleEncoder.webidl @@ -0,0 +1,18 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpurenderbundleencoder +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPURenderBundleEncoder { + GPURenderBundle finish(optional GPURenderBundleDescriptor descriptor = {}); +}; +GPURenderBundleEncoder includes GPUObjectBase; +GPURenderBundleEncoder includes GPUProgrammablePassEncoder; +GPURenderBundleEncoder includes GPURenderEncoderBase; + +dictionary GPURenderBundleEncoderDescriptor : GPUObjectDescriptorBase { + required sequence<GPUTextureFormat> colorFormats; + GPUTextureFormat depthStencilFormat; + GPUSize32 sampleCount = 1; +}; diff --git a/components/script/dom/webidls/GPURenderEncoderBase.webidl b/components/script/dom/webidls/GPURenderEncoderBase.webidl new file mode 100644 index 00000000000..f0c4532b446 --- /dev/null +++ b/components/script/dom/webidls/GPURenderEncoderBase.webidl @@ -0,0 +1,24 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpurenderencoderbase +[Exposed=(Window, DedicatedWorker)] +interface mixin GPURenderEncoderBase { + void setPipeline(GPURenderPipeline pipeline); + + void setIndexBuffer(GPUBuffer buffer, optional GPUSize64 offset = 0, optional GPUSize64 size = 0); + void setVertexBuffer(GPUIndex32 slot, GPUBuffer buffer, optional GPUSize64 offset = 0, optional GPUSize64 size = 0); + + void draw(GPUSize32 vertexCount, optional GPUSize32 instanceCount = 1, + optional GPUSize32 firstVertex = 0, optional GPUSize32 firstInstance = 0); + void drawIndexed(GPUSize32 indexCount, optional GPUSize32 instanceCount = 1, + optional GPUSize32 firstIndex = 0, + optional GPUSignedOffset32 baseVertex = 0, + optional GPUSize32 firstInstance = 0); + + void drawIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); + void drawIndexedIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); +}; + +typedef [EnforceRange] long GPUSignedOffset32; diff --git a/components/script/dom/webidls/GPURenderPassEncoder.webidl b/components/script/dom/webidls/GPURenderPassEncoder.webidl new file mode 100644 index 00000000000..6261cf89453 --- /dev/null +++ b/components/script/dom/webidls/GPURenderPassEncoder.webidl @@ -0,0 +1,31 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpurenderpassencoder +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPURenderPassEncoder { + void setViewport(float x, float y, + float width, float height, + float minDepth, float maxDepth); + + void setScissorRect(GPUIntegerCoordinate x, GPUIntegerCoordinate y, + GPUIntegerCoordinate width, GPUIntegerCoordinate height); + + void setBlendColor(GPUColor color); + void setStencilReference(GPUStencilValue reference); + + //void beginOcclusionQuery(GPUSize32 queryIndex); + //void endOcclusionQuery(); + + //void beginPipelineStatisticsQuery(GPUQuerySet querySet, GPUSize32 queryIndex); + //void endPipelineStatisticsQuery(); + + //void writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + + void executeBundles(sequence<GPURenderBundle> bundles); + void endPass(); +}; +GPURenderPassEncoder includes GPUObjectBase; +GPURenderPassEncoder includes GPUProgrammablePassEncoder; +GPURenderPassEncoder includes GPURenderEncoderBase; diff --git a/components/script/dom/webidls/GPURenderPipeline.webidl b/components/script/dom/webidls/GPURenderPipeline.webidl new file mode 100644 index 00000000000..7def381780a --- /dev/null +++ b/components/script/dom/webidls/GPURenderPipeline.webidl @@ -0,0 +1,191 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpurenderpipeline +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPURenderPipeline { +}; +GPURenderPipeline includes GPUObjectBase; +GPURenderPipeline includes GPUPipelineBase; + +dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase { + required GPUProgrammableStageDescriptor vertexStage; + GPUProgrammableStageDescriptor fragmentStage; + + required GPUPrimitiveTopology primitiveTopology; + GPURasterizationStateDescriptor rasterizationState = {}; + required sequence<GPUColorStateDescriptor> colorStates; + GPUDepthStencilStateDescriptor depthStencilState; + GPUVertexStateDescriptor vertexState = {}; + + GPUSize32 sampleCount = 1; + GPUSampleMask sampleMask = 0xFFFFFFFF; + boolean alphaToCoverageEnabled = false; +}; + +typedef [EnforceRange] unsigned long GPUSampleMask; + +enum GPUPrimitiveTopology { + "point-list", + "line-list", + "line-strip", + "triangle-list", + "triangle-strip" +}; + +typedef [EnforceRange] long GPUDepthBias; + +dictionary GPURasterizationStateDescriptor { + GPUFrontFace frontFace = "ccw"; + GPUCullMode cullMode = "none"; + // Enable depth clamping (requires "depth-clamping" extension) + boolean clampDepth = false; + + GPUDepthBias depthBias = 0; + float depthBiasSlopeScale = 0; + float depthBiasClamp = 0; +}; + +enum GPUFrontFace { + "ccw", + "cw" +}; + +enum GPUCullMode { + "none", + "front", + "back" +}; + +dictionary GPUColorStateDescriptor { + required GPUTextureFormat format; + + GPUBlendDescriptor alphaBlend = {}; + GPUBlendDescriptor colorBlend = {}; + GPUColorWriteFlags writeMask = 0xF; // GPUColorWrite.ALL +}; + +dictionary GPUBlendDescriptor { + GPUBlendFactor srcFactor = "one"; + GPUBlendFactor dstFactor = "zero"; + GPUBlendOperation operation = "add"; +}; + +enum GPUBlendFactor { + "zero", + "one", + "src-color", + "one-minus-src-color", + "src-alpha", + "one-minus-src-alpha", + "dst-color", + "one-minus-dst-color", + "dst-alpha", + "one-minus-dst-alpha", + "src-alpha-saturated", + "blend-color", + "one-minus-blend-color" +}; + +enum GPUBlendOperation { + "add", + "subtract", + "reverse-subtract", + "min", + "max" +}; + +enum GPUStencilOperation { + "keep", + "zero", + "replace", + "invert", + "increment-clamp", + "decrement-clamp", + "increment-wrap", + "decrement-wrap" +}; + +typedef [EnforceRange] unsigned long GPUStencilValue; + +dictionary GPUDepthStencilStateDescriptor { + required GPUTextureFormat format; + + boolean depthWriteEnabled = false; + GPUCompareFunction depthCompare = "always"; + + GPUStencilStateFaceDescriptor stencilFront = {}; + GPUStencilStateFaceDescriptor stencilBack = {}; + + GPUStencilValue stencilReadMask = 0xFFFFFFFF; + GPUStencilValue stencilWriteMask = 0xFFFFFFFF; +}; + +dictionary GPUStencilStateFaceDescriptor { + GPUCompareFunction compare = "always"; + GPUStencilOperation failOp = "keep"; + GPUStencilOperation depthFailOp = "keep"; + GPUStencilOperation passOp = "keep"; +}; + +enum GPUIndexFormat { + "uint16", + "uint32" +}; + +enum GPUVertexFormat { + "uchar2", + "uchar4", + "char2", + "char4", + "uchar2norm", + "uchar4norm", + "char2norm", + "char4norm", + "ushort2", + "ushort4", + "short2", + "short4", + "ushort2norm", + "ushort4norm", + "short2norm", + "short4norm", + "half2", + "half4", + "float", + "float2", + "float3", + "float4", + "uint", + "uint2", + "uint3", + "uint4", + "int", + "int2", + "int3", + "int4" +}; + +enum GPUInputStepMode { + "vertex", + "instance" +}; + +dictionary GPUVertexStateDescriptor { + GPUIndexFormat indexFormat = "uint32"; + sequence<GPUVertexBufferLayoutDescriptor?> vertexBuffers = []; +}; + +dictionary GPUVertexBufferLayoutDescriptor { + required GPUSize64 arrayStride; + GPUInputStepMode stepMode = "vertex"; + required sequence<GPUVertexAttributeDescriptor> attributes; +}; + +dictionary GPUVertexAttributeDescriptor { + required GPUVertexFormat format; + required GPUSize64 offset; + + required GPUIndex32 shaderLocation; +}; diff --git a/components/script/dom/webidls/GPUSampler.webidl b/components/script/dom/webidls/GPUSampler.webidl new file mode 100644 index 00000000000..539bc2695de --- /dev/null +++ b/components/script/dom/webidls/GPUSampler.webidl @@ -0,0 +1,44 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpusampler +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUSampler { +}; +GPUSampler includes GPUObjectBase; + +dictionary GPUSamplerDescriptor : GPUObjectDescriptorBase { + GPUAddressMode addressModeU = "clamp-to-edge"; + GPUAddressMode addressModeV = "clamp-to-edge"; + GPUAddressMode addressModeW = "clamp-to-edge"; + GPUFilterMode magFilter = "nearest"; + GPUFilterMode minFilter = "nearest"; + GPUFilterMode mipmapFilter = "nearest"; + float lodMinClamp = 0; + float lodMaxClamp = 0xfffff; // TODO: What should this be? Was Number.MAX_VALUE. + GPUCompareFunction compare; + unsigned short maxAnisotropy = 1; +}; + +enum GPUAddressMode { + "clamp-to-edge", + "repeat", + "mirror-repeat" +}; + +enum GPUFilterMode { + "nearest", + "linear" +}; + +enum GPUCompareFunction { + "never", + "less", + "equal", + "less-equal", + "greater", + "not-equal", + "greater-equal", + "always" +}; diff --git a/components/script/dom/webidls/GPUShaderModule.webidl b/components/script/dom/webidls/GPUShaderModule.webidl new file mode 100644 index 00000000000..0fdfc7c0327 --- /dev/null +++ b/components/script/dom/webidls/GPUShaderModule.webidl @@ -0,0 +1,15 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpushadermodule +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUShaderModule { +}; +GPUShaderModule includes GPUObjectBase; + +typedef (Uint32Array or DOMString) GPUShaderCode; + +dictionary GPUShaderModuleDescriptor : GPUObjectDescriptorBase { + required GPUShaderCode code; +}; diff --git a/components/script/dom/webidls/GPUShaderStage.webidl b/components/script/dom/webidls/GPUShaderStage.webidl new file mode 100644 index 00000000000..27fcb550cc3 --- /dev/null +++ b/components/script/dom/webidls/GPUShaderStage.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#typedefdef-gpushaderstageflags +[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] +interface GPUShaderStage { + const GPUShaderStageFlags VERTEX = 0x1; + const GPUShaderStageFlags FRAGMENT = 0x2; + const GPUShaderStageFlags COMPUTE = 0x4; +}; + +typedef unsigned long GPUShaderStageFlags; diff --git a/components/script/dom/webidls/GPUSwapChain.webidl b/components/script/dom/webidls/GPUSwapChain.webidl new file mode 100644 index 00000000000..aa64e232134 --- /dev/null +++ b/components/script/dom/webidls/GPUSwapChain.webidl @@ -0,0 +1,10 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpuswapchain +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUSwapChain { + GPUTexture getCurrentTexture(); +}; +GPUSwapChain includes GPUObjectBase; diff --git a/components/script/dom/webidls/GPUTexture.webidl b/components/script/dom/webidls/GPUTexture.webidl new file mode 100644 index 00000000000..9a02a98d577 --- /dev/null +++ b/components/script/dom/webidls/GPUTexture.webidl @@ -0,0 +1,122 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gputexture +[Exposed=(Window, DedicatedWorker), Serializable , Pref="dom.webgpu.enabled"] +interface GPUTexture { + GPUTextureView createView(optional GPUTextureViewDescriptor descriptor = {}); + + void destroy(); +}; +GPUTexture includes GPUObjectBase; + +dictionary GPUTextureDescriptor : GPUObjectDescriptorBase { + required GPUExtent3D size; + GPUIntegerCoordinate mipLevelCount = 1; + GPUSize32 sampleCount = 1; + GPUTextureDimension dimension = "2d"; + required GPUTextureFormat format; + required GPUTextureUsageFlags usage; +}; + +enum GPUTextureDimension { + "1d", + "2d", + "3d" +}; + +enum GPUTextureFormat { + // 8-bit formats + "r8unorm", + "r8snorm", + "r8uint", + "r8sint", + + // 16-bit formats + "r16uint", + "r16sint", + "r16float", + "rg8unorm", + "rg8snorm", + "rg8uint", + "rg8sint", + + // 32-bit formats + "r32uint", + "r32sint", + "r32float", + "rg16uint", + "rg16sint", + "rg16float", + "rgba8unorm", + "rgba8unorm-srgb", + "rgba8snorm", + "rgba8uint", + "rgba8sint", + "bgra8unorm", + "bgra8unorm-srgb", + // Packed 32-bit formats + //"rgb9e5ufloat", + "rgb10a2unorm", + //"rg11b10ufloat", + + // 64-bit formats + "rg32uint", + "rg32sint", + "rg32float", + "rgba16uint", + "rgba16sint", + "rgba16float", + + // 128-bit formats + "rgba32uint", + "rgba32sint", + "rgba32float", + + // Depth and stencil formats + //"stencil8", + //"depth16unorm", + "depth24plus", + "depth24plus-stencil8", + "depth32float", + + // BC compressed formats usable if "texture-compression-bc" is both + // supported by the device/user agent and enabled in requestDevice. + "bc1-rgba-unorm", + "bc1-rgba-unorm-srgb", + "bc2-rgba-unorm", + "bc2-rgba-unorm-srgb", + "bc3-rgba-unorm", + "bc3-rgba-unorm-srgb", + "bc4-r-unorm", + "bc4-r-snorm", + "bc5-rg-unorm", + "bc5-rg-snorm", + "bc6h-rgb-ufloat", + //"bc6h-rgb-float", + "bc7-rgba-unorm", + "bc7-rgba-unorm-srgb", + + // "depth24unorm-stencil8" extension + //"depth24unorm-stencil8", + + // "depth32float-stencil8" extension + //"depth32float-stencil8", +}; + +enum GPUTextureComponentType { + "float", + "sint", + "uint", + // Texture is used with comparison sampling only. + "depth-comparison" +}; + +dictionary GPUExtent3DDict { + required GPUIntegerCoordinate width; + required GPUIntegerCoordinate height; + required GPUIntegerCoordinate depth; +}; +typedef [EnforceRange] unsigned long GPUIntegerCoordinate; +typedef (sequence<GPUIntegerCoordinate> or GPUExtent3DDict) GPUExtent3D; diff --git a/components/script/dom/webidls/GPUTextureUsage.webidl b/components/script/dom/webidls/GPUTextureUsage.webidl new file mode 100644 index 00000000000..dbb93c37fa7 --- /dev/null +++ b/components/script/dom/webidls/GPUTextureUsage.webidl @@ -0,0 +1,15 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gputextureusage +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUTextureUsage { + const GPUTextureUsageFlags COPY_SRC = 0x01; + const GPUTextureUsageFlags COPY_DST = 0x02; + const GPUTextureUsageFlags SAMPLED = 0x04; + const GPUTextureUsageFlags STORAGE = 0x08; + const GPUTextureUsageFlags OUTPUT_ATTACHMENT = 0x10; +}; + +typedef [EnforceRange] unsigned long GPUTextureUsageFlags; diff --git a/components/script/dom/webidls/GPUTextureView.webidl b/components/script/dom/webidls/GPUTextureView.webidl new file mode 100644 index 00000000000..f6b8b7d1317 --- /dev/null +++ b/components/script/dom/webidls/GPUTextureView.webidl @@ -0,0 +1,34 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gputextureview +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUTextureView { +}; +GPUTextureView includes GPUObjectBase; + +dictionary GPUTextureViewDescriptor : GPUObjectDescriptorBase { + GPUTextureFormat format; + GPUTextureViewDimension dimension; + GPUTextureAspect aspect = "all"; + GPUIntegerCoordinate baseMipLevel = 0; + GPUIntegerCoordinate mipLevelCount; + GPUIntegerCoordinate baseArrayLayer = 0; + GPUIntegerCoordinate arrayLayerCount; +}; + +enum GPUTextureViewDimension { + "1d", + "2d", + "2d-array", + "cube", + "cube-array", + "3d" +}; + +enum GPUTextureAspect { + "all", + "stencil-only", + "depth-only" +}; diff --git a/components/script/dom/webidls/GPUUncapturedErrorEvent.webidl b/components/script/dom/webidls/GPUUncapturedErrorEvent.webidl new file mode 100644 index 00000000000..565c72ef9be --- /dev/null +++ b/components/script/dom/webidls/GPUUncapturedErrorEvent.webidl @@ -0,0 +1,22 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpuuncapturederrorevent +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUUncapturedErrorEvent : Event { + constructor( + DOMString type, + GPUUncapturedErrorEventInit gpuUncapturedErrorEventInitDict + ); + /*[SameObject]*/ readonly attribute GPUError error; +}; + +dictionary GPUUncapturedErrorEventInit : EventInit { + required GPUError error; +}; + +partial interface GPUDevice { + [Exposed=(Window, DedicatedWorker)] + attribute EventHandler onuncapturederror; +}; diff --git a/components/script/dom/webidls/GPUValidationError.webidl b/components/script/dom/webidls/GPUValidationError.webidl new file mode 100644 index 00000000000..8e5d211d981 --- /dev/null +++ b/components/script/dom/webidls/GPUValidationError.webidl @@ -0,0 +1,22 @@ +/* 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/. */ + +// https://gpuweb.github.io/gpuweb/#gpuvalidationerror +[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] +interface GPUValidationError { + constructor(DOMString message); + readonly attribute DOMString message; +}; + +typedef (GPUOutOfMemoryError or GPUValidationError) GPUError; + +enum GPUErrorFilter { + "out-of-memory", + "validation" +}; + +partial interface GPUDevice { + void pushErrorScope(GPUErrorFilter filter); + Promise<GPUError?> popErrorScope(); +}; diff --git a/components/script/dom/webidls/GainNode.webidl b/components/script/dom/webidls/GainNode.webidl new file mode 100644 index 00000000000..1247aeda95d --- /dev/null +++ b/components/script/dom/webidls/GainNode.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#gainnode + */ + +dictionary GainOptions : AudioNodeOptions { + float gain = 1.0; +}; + +[Exposed=Window] + interface GainNode : AudioNode { + [Throws] constructor(BaseAudioContext context, optional GainOptions options = {}); + readonly attribute AudioParam gain; + }; diff --git a/components/script/dom/webidls/Gamepad.webidl b/components/script/dom/webidls/Gamepad.webidl index 0f666a495cf..925eaf2a544 100644 --- a/components/script/dom/webidls/Gamepad.webidl +++ b/components/script/dom/webidls/Gamepad.webidl @@ -1,9 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/gamepad/#gamepad-interface -[Pref="dom.gamepad.enabled"] +[Exposed=Window, Pref="dom.gamepad.enabled"] interface Gamepad { readonly attribute DOMString id; readonly attribute long index; @@ -14,13 +14,16 @@ interface Gamepad { [SameObject] readonly attribute GamepadButtonList buttons; }; -// https://w3c.github.io/gamepad/extensions.html#dom-gamepad +// https://w3c.github.io/gamepad/extensions.html#partial-gamepad-interface partial interface Gamepad { - readonly attribute DOMString hand; - readonly attribute VRPose? pose; + readonly attribute GamepadHand hand; + // readonly attribute FrozenArray<GamepadHapticActuator> hapticActuators; + readonly attribute GamepadPose? pose; }; -// https://w3c.github.io/webvr/spec/1.1/#interface-gamepad -partial interface Gamepad { - readonly attribute unsigned long displayId; +// https://w3c.github.io/gamepad/extensions.html#gamepadhand-enum +enum GamepadHand { + "", /* unknown, both hands, or not applicable */ + "left", + "right" }; diff --git a/components/script/dom/webidls/GamepadButton.webidl b/components/script/dom/webidls/GamepadButton.webidl index 2fa04c8ba3c..748d47232ac 100644 --- a/components/script/dom/webidls/GamepadButton.webidl +++ b/components/script/dom/webidls/GamepadButton.webidl @@ -1,9 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/gamepad/#gamepadbutton-interface -[Pref="dom.gamepad.enabled"] +[Exposed=Window, Pref="dom.gamepad.enabled"] interface GamepadButton { readonly attribute boolean pressed; readonly attribute boolean touched; diff --git a/components/script/dom/webidls/GamepadButtonList.webidl b/components/script/dom/webidls/GamepadButtonList.webidl index c8fb75a4350..34ea9857071 100644 --- a/components/script/dom/webidls/GamepadButtonList.webidl +++ b/components/script/dom/webidls/GamepadButtonList.webidl @@ -1,9 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/gamepad/#dom-gamepad-buttons -[Pref="dom.gamepad.enabled"] +[Exposed=Window, Pref="dom.gamepad.enabled"] interface GamepadButtonList { getter GamepadButton? item(unsigned long index); readonly attribute unsigned long length; diff --git a/components/script/dom/webidls/GamepadEvent.webidl b/components/script/dom/webidls/GamepadEvent.webidl index ea40fd4261c..0da800bafe2 100644 --- a/components/script/dom/webidls/GamepadEvent.webidl +++ b/components/script/dom/webidls/GamepadEvent.webidl @@ -1,10 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/gamepad/#gamepadevent-interface -[Pref="dom.gamepad.enabled", Constructor(DOMString type, GamepadEventInit eventInitDict)] +[Exposed=Window, Pref="dom.gamepad.enabled"] interface GamepadEvent : Event { + [Throws] constructor(DOMString type, GamepadEventInit eventInitDict); readonly attribute Gamepad gamepad; }; diff --git a/components/script/dom/webidls/GamepadList.webidl b/components/script/dom/webidls/GamepadList.webidl index 2d99e4e8f3c..926ab7ac848 100644 --- a/components/script/dom/webidls/GamepadList.webidl +++ b/components/script/dom/webidls/GamepadList.webidl @@ -1,9 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/gamepad/#navigator-interface-extension -[Pref="dom.gamepad.enabled"] +[Exposed=Window, Pref="dom.gamepad.enabled"] interface GamepadList { getter Gamepad? item(unsigned long index); readonly attribute unsigned long length; diff --git a/components/script/dom/webidls/VRPose.webidl b/components/script/dom/webidls/GamepadPose.webidl index ffbd931cfbc..5afa9f3251e 100644 --- a/components/script/dom/webidls/VRPose.webidl +++ b/components/script/dom/webidls/GamepadPose.webidl @@ -1,10 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// https://w3c.github.io/gamepad/extensions.html#gamepadpose-interface +[Exposed=Window, Pref="dom.gamepad.enabled"] +interface GamepadPose { + readonly attribute boolean hasOrientation; + readonly attribute boolean hasPosition; -// https://w3c.github.io/webvr/#interface-vrpose -[Pref="dom.webvr.enabled"] -interface VRPose { readonly attribute Float32Array? position; readonly attribute Float32Array? linearVelocity; readonly attribute Float32Array? linearAcceleration; diff --git a/components/script/dom/webidls/GlobalScope.webidl b/components/script/dom/webidls/GlobalScope.webidl index 7dab4f3afa7..57206d13e6a 100644 --- a/components/script/dom/webidls/GlobalScope.webidl +++ b/components/script/dom/webidls/GlobalScope.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This interface is entirely internal to Servo, and should not be accessible to // web pages. -[Exposed=(Window,Worker), +[Exposed=(Window,Worker,Worklet,DissimilarOriginWindow), Inline] interface GlobalScope : EventTarget {}; diff --git a/components/script/dom/webidls/HTMLAnchorElement.webidl b/components/script/dom/webidls/HTMLAnchorElement.webidl index 41026e25bce..f6e48281102 100644 --- a/components/script/dom/webidls/HTMLAnchorElement.webidl +++ b/components/script/dom/webidls/HTMLAnchorElement.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://html.spec.whatwg.org/multipage/#the-a-element @@ -11,27 +11,41 @@ */ // https://html.spec.whatwg.org/multipage/#htmlanchorelement +[Exposed=Window] interface HTMLAnchorElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute DOMString target; + // [CEReactions] // attribute DOMString download; + // [CEReactions] // attribute USVString ping; + [CEReactions] attribute DOMString rel; - readonly attribute DOMTokenList relList; + [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; + // [CEReactions] // attribute DOMString hreflang; + // [CEReactions] // attribute DOMString type; - [Pure] + [CEReactions, Pure] attribute DOMString text; // also has obsolete members }; -HTMLAnchorElement implements HTMLHyperlinkElementUtils; +HTMLAnchorElement includes HTMLHyperlinkElementUtils; // https://html.spec.whatwg.org/multipage/#HTMLAnchorElement-partial partial interface HTMLAnchorElement { + [CEReactions] attribute DOMString coords; - // attribute DOMString charset; + // [CEReactions] + // attribute DOMString charset; + [CEReactions] attribute DOMString name; + [CEReactions] attribute DOMString rev; + [CEReactions] attribute DOMString shape; }; diff --git a/components/script/dom/webidls/HTMLAppletElement.webidl b/components/script/dom/webidls/HTMLAppletElement.webidl deleted file mode 100644 index 9cfeb4183df..00000000000 --- a/components/script/dom/webidls/HTMLAppletElement.webidl +++ /dev/null @@ -1,18 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://html.spec.whatwg.org/multipage/#htmlappletelement -interface HTMLAppletElement : HTMLElement { - // attribute DOMString align; - // attribute DOMString alt; - // attribute DOMString archive; - // attribute DOMString code; - // attribute DOMString codeBase; - // attribute DOMString height; - // attribute unsigned long hspace; - attribute DOMString name; - // attribute DOMString _object; // the underscore is not part of the identifier - // attribute unsigned long vspace; - // attribute DOMString width; -}; diff --git a/components/script/dom/webidls/HTMLAreaElement.webidl b/components/script/dom/webidls/HTMLAreaElement.webidl index 14883df3613..74c281e93c5 100644 --- a/components/script/dom/webidls/HTMLAreaElement.webidl +++ b/components/script/dom/webidls/HTMLAreaElement.webidl @@ -1,22 +1,33 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlareaelement +[Exposed=Window] interface HTMLAreaElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute DOMString alt; + // [CEReactions] // attribute DOMString coords; + // [CEReactions] // attribute DOMString shape; - // attribute DOMString target; + [CEReactions] + attribute DOMString target; + // [CEReactions] // attribute DOMString download; + // [CEReactions] // attribute USVString ping; - // attribute DOMString rel; - readonly attribute DOMTokenList relList; + [CEReactions] + attribute DOMString rel; + [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; // hreflang and type are not reflected }; -//HTMLAreaElement implements HTMLHyperlinkElementUtils; +//HTMLAreaElement includes HTMLHyperlinkElementUtils; // https://html.spec.whatwg.org/multipage/#HTMLAreaElement-partial partial interface HTMLAreaElement { - // attribute boolean noHref; + // [CEReactions] + // attribute boolean noHref; }; diff --git a/components/script/dom/webidls/HTMLAudioElement.webidl b/components/script/dom/webidls/HTMLAudioElement.webidl index 09ad8a7cdb3..bad06df5cfc 100644 --- a/components/script/dom/webidls/HTMLAudioElement.webidl +++ b/components/script/dom/webidls/HTMLAudioElement.webidl @@ -1,7 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlaudioelement -//[NamedConstructor=Audio(optional DOMString src)] -interface HTMLAudioElement : HTMLMediaElement {}; +[Exposed=Window, NamedConstructor=Audio(optional DOMString src)] +interface HTMLAudioElement : HTMLMediaElement { + [HTMLConstructor] constructor(); +}; diff --git a/components/script/dom/webidls/HTMLBRElement.webidl b/components/script/dom/webidls/HTMLBRElement.webidl index ab277396bdd..367f3bd36a9 100644 --- a/components/script/dom/webidls/HTMLBRElement.webidl +++ b/components/script/dom/webidls/HTMLBRElement.webidl @@ -1,9 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlbrelement +[Exposed=Window] interface HTMLBRElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; diff --git a/components/script/dom/webidls/HTMLBaseElement.webidl b/components/script/dom/webidls/HTMLBaseElement.webidl index a13be544cb9..813a4cffbd1 100644 --- a/components/script/dom/webidls/HTMLBaseElement.webidl +++ b/components/script/dom/webidls/HTMLBaseElement.webidl @@ -1,9 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlbaseelement +[Exposed=Window] interface HTMLBaseElement : HTMLElement { - attribute DOMString href; -// attribute DOMString target; + [HTMLConstructor] constructor(); + + [CEReactions] + attribute DOMString href; + // [CEReactions] + // attribute DOMString target; }; diff --git a/components/script/dom/webidls/HTMLBodyElement.webidl b/components/script/dom/webidls/HTMLBodyElement.webidl index 36c6f4d64e3..a84d800ff10 100644 --- a/components/script/dom/webidls/HTMLBodyElement.webidl +++ b/components/script/dom/webidls/HTMLBodyElement.webidl @@ -1,26 +1,29 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#the-body-element +[Exposed=Window] interface HTMLBodyElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; -HTMLBodyElement implements WindowEventHandlers; +HTMLBodyElement includes WindowEventHandlers; // https://html.spec.whatwg.org/multipage/#HTMLBodyElement-partial partial interface HTMLBodyElement { - [TreatNullAs=EmptyString] attribute DOMString text; + [CEReactions] attribute [TreatNullAs=EmptyString] DOMString text; // https://github.com/servo/servo/issues/8715 - //[TreatNullAs=EmptyString] attribute DOMString link; + //[CEReactions, TreatNullAs=EmptyString] attribute DOMString link; // https://github.com/servo/servo/issues/8716 - //[TreatNullAs=EmptyString] attribute DOMString vLink; + //[CEReactions, TreatNullAs=EmptyString] attribute DOMString vLink; // https://github.com/servo/servo/issues/8717 - //[TreatNullAs=EmptyString] attribute DOMString aLink; + //[CEReactions, TreatNullAs=EmptyString] attribute DOMString aLink; - [TreatNullAs=EmptyString] attribute DOMString bgColor; - attribute DOMString background; + [CEReactions] attribute [TreatNullAs=EmptyString] DOMString bgColor; + [CEReactions] attribute DOMString background; }; diff --git a/components/script/dom/webidls/HTMLButtonElement.webidl b/components/script/dom/webidls/HTMLButtonElement.webidl index 7f663fd305f..a221f6669cf 100644 --- a/components/script/dom/webidls/HTMLButtonElement.webidl +++ b/components/script/dom/webidls/HTMLButtonElement.webidl @@ -1,28 +1,41 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlbuttonelement +[Exposed=Window] interface HTMLButtonElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute boolean autofocus; - attribute boolean disabled; + [CEReactions] + attribute boolean disabled; readonly attribute HTMLFormElement? form; - attribute DOMString formAction; - attribute DOMString formEnctype; - attribute DOMString formMethod; - attribute boolean formNoValidate; - attribute DOMString formTarget; - attribute DOMString name; - attribute DOMString type; - attribute DOMString value; + [CEReactions] + attribute DOMString formAction; + [CEReactions] + attribute DOMString formEnctype; + [CEReactions] + attribute DOMString formMethod; + [CEReactions] + attribute boolean formNoValidate; + [CEReactions] + attribute DOMString formTarget; + [CEReactions] + attribute DOMString name; + [CEReactions] + attribute DOMString type; + [CEReactions] + attribute DOMString value; // attribute HTMLMenuElement? menu; - //readonly attribute boolean willValidate; + readonly attribute boolean willValidate; readonly attribute ValidityState validity; - //readonly attribute DOMString validationMessage; - //boolean checkValidity(); - //boolean reportValidity(); - //void setCustomValidity(DOMString error); + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); readonly attribute NodeList labels; }; diff --git a/components/script/dom/webidls/HTMLCanvasElement.webidl b/components/script/dom/webidls/HTMLCanvasElement.webidl index fbb53016605..5c33aa3532e 100644 --- a/components/script/dom/webidls/HTMLCanvasElement.webidl +++ b/components/script/dom/webidls/HTMLCanvasElement.webidl @@ -1,23 +1,31 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlcanvaselement -typedef (CanvasRenderingContext2D or WebGLRenderingContext) RenderingContext; +typedef (CanvasRenderingContext2D + or WebGLRenderingContext + or WebGL2RenderingContext + or GPUCanvasContext) RenderingContext; +[Exposed=Window] interface HTMLCanvasElement : HTMLElement { - [Pure] - attribute unsigned long width; - [Pure] - attribute unsigned long height; + [HTMLConstructor] constructor(); - RenderingContext? getContext(DOMString contextId, any... arguments); - //boolean probablySupportsContext(DOMString contextId, any... arguments); + [CEReactions, Pure] attribute unsigned long width; + [CEReactions, Pure] attribute unsigned long height; - //void setContext(RenderingContext context); - //CanvasProxy transferControlToProxy(); + RenderingContext? getContext(DOMString contextId, optional any options = null); [Throws] - DOMString toDataURL(optional DOMString type, any... arguments); - //void toBlob(FileCallback? _callback, optional DOMString type, any... arguments); + USVString toDataURL(optional DOMString type, optional any quality); + //void toBlob(BlobCallback _callback, optional DOMString type, optional any quality); + //OffscreenCanvas transferControlToOffscreen(); }; + +partial interface HTMLCanvasElement { + [Pref="dom.canvas_capture.enabled"] + MediaStream captureStream (optional double frameRequestRate); +}; + +//callback BlobCallback = void (Blob? blob); diff --git a/components/script/dom/webidls/HTMLCollection.webidl b/components/script/dom/webidls/HTMLCollection.webidl index 79f82046652..ac0962a5d10 100644 --- a/components/script/dom/webidls/HTMLCollection.webidl +++ b/components/script/dom/webidls/HTMLCollection.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://dom.spec.whatwg.org/#interface-htmlcollection -[LegacyUnenumerableNamedProperties] +[Exposed=Window, LegacyUnenumerableNamedProperties] interface HTMLCollection { [Pure] readonly attribute unsigned long length; diff --git a/components/script/dom/webidls/HTMLDListElement.webidl b/components/script/dom/webidls/HTMLDListElement.webidl index b6275107db5..76cf662620e 100644 --- a/components/script/dom/webidls/HTMLDListElement.webidl +++ b/components/script/dom/webidls/HTMLDListElement.webidl @@ -1,13 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmldlistelement +[Exposed=Window] interface HTMLDListElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLDListElement-partial partial interface HTMLDListElement { + // [CEReactions] // attribute boolean compact; }; diff --git a/components/script/dom/webidls/HTMLDataElement.webidl b/components/script/dom/webidls/HTMLDataElement.webidl index 658f5274491..b11368f3de2 100644 --- a/components/script/dom/webidls/HTMLDataElement.webidl +++ b/components/script/dom/webidls/HTMLDataElement.webidl @@ -1,8 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmldataelement +[Exposed=Window] interface HTMLDataElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute DOMString value; }; diff --git a/components/script/dom/webidls/HTMLDataListElement.webidl b/components/script/dom/webidls/HTMLDataListElement.webidl index b8673b21c77..5bd9ac9d362 100644 --- a/components/script/dom/webidls/HTMLDataListElement.webidl +++ b/components/script/dom/webidls/HTMLDataListElement.webidl @@ -1,8 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmldatalistelement +[Exposed=Window] interface HTMLDataListElement : HTMLElement { + [HTMLConstructor] constructor(); + readonly attribute HTMLCollection options; }; diff --git a/components/script/dom/webidls/HTMLDetailsElement.webidl b/components/script/dom/webidls/HTMLDetailsElement.webidl index 811465c1c02..e860186a8ce 100644 --- a/components/script/dom/webidls/HTMLDetailsElement.webidl +++ b/components/script/dom/webidls/HTMLDetailsElement.webidl @@ -1,8 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmldetailselement +[Exposed=Window] interface HTMLDetailsElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute boolean open; }; diff --git a/components/script/dom/webidls/HTMLDialogElement.webidl b/components/script/dom/webidls/HTMLDialogElement.webidl index 0ac76a0465e..f83f2547a72 100644 --- a/components/script/dom/webidls/HTMLDialogElement.webidl +++ b/components/script/dom/webidls/HTMLDialogElement.webidl @@ -1,12 +1,19 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmldialogelement +[Exposed=Window] interface HTMLDialogElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute boolean open; attribute DOMString returnValue; - //void show(optional (MouseEvent or Element) anchor); - //void showModal(optional (MouseEvent or Element) anchor); + // [CEReactions] + // void show(); + // [CEReactions] + // void showModal(); + [CEReactions] void close(optional DOMString returnValue); }; diff --git a/components/script/dom/webidls/HTMLDirectoryElement.webidl b/components/script/dom/webidls/HTMLDirectoryElement.webidl index 5df65cd90c2..0fc1a65949c 100644 --- a/components/script/dom/webidls/HTMLDirectoryElement.webidl +++ b/components/script/dom/webidls/HTMLDirectoryElement.webidl @@ -1,8 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmldirectoryelement +[Exposed=Window] interface HTMLDirectoryElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute boolean compact; }; diff --git a/components/script/dom/webidls/HTMLDivElement.webidl b/components/script/dom/webidls/HTMLDivElement.webidl index 46ee67ee0e5..c38127c87eb 100644 --- a/components/script/dom/webidls/HTMLDivElement.webidl +++ b/components/script/dom/webidls/HTMLDivElement.webidl @@ -1,13 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmldivelement +[Exposed=Window] interface HTMLDivElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLDivElement-partial partial interface HTMLDivElement { + [CEReactions] attribute DOMString align; }; diff --git a/components/script/dom/webidls/HTMLElement.webidl b/components/script/dom/webidls/HTMLElement.webidl index 188c0421154..c743c0a7129 100644 --- a/components/script/dom/webidls/HTMLElement.webidl +++ b/components/script/dom/webidls/HTMLElement.webidl @@ -1,43 +1,62 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlelement +[Exposed=Window] interface HTMLElement : Element { + [HTMLConstructor] constructor(); + // metadata attributes + [CEReactions] attribute DOMString title; + [CEReactions] attribute DOMString lang; - // attribute boolean translate; - // attribute DOMString dir; + [CEReactions] + attribute boolean translate; + [CEReactions] + attribute DOMString dir; readonly attribute DOMStringMap dataset; // microdata // attribute boolean itemScope; + // attribute DOMString itemId; //readonly attribute HTMLPropertiesCollection properties; // attribute any itemValue; // acts as DOMString on setting + [Pref="dom.microdata.testing.enabled"] + sequence<DOMString>? propertyNames(); + [Pref="dom.microdata.testing.enabled"] + sequence<DOMString>? itemtypes(); // user interaction + [CEReactions] attribute boolean hidden; void click(); + // [CEReactions] // attribute long tabIndex; void focus(); void blur(); + // [CEReactions] // attribute DOMString accessKey; //readonly attribute DOMString accessKeyLabel; + // [CEReactions] // attribute boolean draggable; - //[SameObject, PutForwards=value] readonly attribute DOMTokenList dropzone; + // [SameObject, PutForwards=value] readonly attribute DOMTokenList dropzone; // attribute HTMLMenuElement? contextMenu; + // [CEReactions] // attribute boolean spellcheck; - //void forceSpellCheck(); + // void forceSpellCheck(); + + attribute [TreatNullAs=EmptyString] DOMString innerText; // command API - //readonly attribute DOMString? commandType; - //readonly attribute DOMString? commandLabel; - //readonly attribute DOMString? commandIcon; - //readonly attribute boolean? commandHidden; - //readonly attribute boolean? commandDisabled; - //readonly attribute boolean? commandChecked; + // readonly attribute DOMString? commandType; + // readonly attribute DOMString? commandLabel; + // readonly attribute DOMString? commandIcon; + // readonly attribute boolean? commandHidden; + // readonly attribute boolean? commandDisabled; + // readonly attribute boolean? commandChecked; }; // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-htmlelement-interface @@ -50,7 +69,7 @@ partial interface HTMLElement { readonly attribute long offsetHeight; }; -HTMLElement implements GlobalEventHandlers; -HTMLElement implements DocumentAndElementEventHandlers; -HTMLElement implements ElementContentEditable; -HTMLElement implements ElementCSSInlineStyle; +HTMLElement includes GlobalEventHandlers; +HTMLElement includes DocumentAndElementEventHandlers; +HTMLElement includes ElementContentEditable; +HTMLElement includes ElementCSSInlineStyle; diff --git a/components/script/dom/webidls/HTMLEmbedElement.webidl b/components/script/dom/webidls/HTMLEmbedElement.webidl index 26fa4c3ea5a..50d8ce1a16a 100644 --- a/components/script/dom/webidls/HTMLEmbedElement.webidl +++ b/components/script/dom/webidls/HTMLEmbedElement.webidl @@ -1,12 +1,19 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlembedelement +[Exposed=Window] interface HTMLEmbedElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute DOMString src; + // [CEReactions] // attribute DOMString type; + // [CEReactions] // attribute DOMString width; + // [CEReactions] // attribute DOMString height; //legacycaller any (any... arguments); @@ -15,6 +22,8 @@ interface HTMLEmbedElement : HTMLElement { // https://html.spec.whatwg.org/multipage/#HTMLEmbedElement-partial partial interface HTMLEmbedElement { + // [CEReactions] // attribute DOMString align; + // [CEReactions] // attribute DOMString name; }; diff --git a/components/script/dom/webidls/HTMLFieldSetElement.webidl b/components/script/dom/webidls/HTMLFieldSetElement.webidl index d041cdd612f..1257540adb3 100644 --- a/components/script/dom/webidls/HTMLFieldSetElement.webidl +++ b/components/script/dom/webidls/HTMLFieldSetElement.webidl @@ -1,21 +1,26 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlfieldsetelement +[Exposed=Window] interface HTMLFieldSetElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute boolean disabled; readonly attribute HTMLFormElement? form; - // attribute DOMString name; + [CEReactions] + attribute DOMString name; //readonly attribute DOMString type; [SameObject] readonly attribute HTMLCollection elements; - //readonly attribute boolean willValidate; + readonly attribute boolean willValidate; [SameObject] readonly attribute ValidityState validity; - //readonly attribute DOMString validationMessage; - //boolean checkValidity(); - //boolean reportValidity(); - //void setCustomValidity(DOMString error); + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); }; diff --git a/components/script/dom/webidls/HTMLFontElement.webidl b/components/script/dom/webidls/HTMLFontElement.webidl index 74db3f45057..c85a3cdd3f2 100644 --- a/components/script/dom/webidls/HTMLFontElement.webidl +++ b/components/script/dom/webidls/HTMLFontElement.webidl @@ -1,10 +1,16 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlfontelement +[Exposed=Window] interface HTMLFontElement : HTMLElement { - [TreatNullAs=EmptyString] attribute DOMString color; + [HTMLConstructor] constructor(); + + [CEReactions] + attribute [TreatNullAs=EmptyString] DOMString color; + [CEReactions] attribute DOMString face; + [CEReactions] attribute DOMString size; }; diff --git a/components/script/dom/webidls/HTMLFormControlsCollection.webidl b/components/script/dom/webidls/HTMLFormControlsCollection.webidl index 61af522772f..c1b222dee23 100644 --- a/components/script/dom/webidls/HTMLFormControlsCollection.webidl +++ b/components/script/dom/webidls/HTMLFormControlsCollection.webidl @@ -1,8 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlformcontrolscollection +[Exposed=Window] interface HTMLFormControlsCollection : HTMLCollection { // inherits length and item() getter (RadioNodeList or Element)? namedItem(DOMString name); // shadows inherited namedItem() diff --git a/components/script/dom/webidls/HTMLFormElement.webidl b/components/script/dom/webidls/HTMLFormElement.webidl index fba10d25509..8bd872eba95 100644 --- a/components/script/dom/webidls/HTMLFormElement.webidl +++ b/components/script/dom/webidls/HTMLFormElement.webidl @@ -1,29 +1,51 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlformelement -//[OverrideBuiltins] +[Exposed=Window, LegacyUnenumerableNamedProperties] interface HTMLFormElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute DOMString acceptCharset; + [CEReactions] attribute DOMString action; + [CEReactions] attribute DOMString autocomplete; + [CEReactions] attribute DOMString enctype; + [CEReactions] attribute DOMString encoding; + [CEReactions] attribute DOMString method; + [CEReactions] attribute DOMString name; + [CEReactions] attribute boolean noValidate; + [CEReactions] attribute DOMString target; + [CEReactions] + attribute DOMString rel; + [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; [SameObject] readonly attribute HTMLFormControlsCollection elements; readonly attribute unsigned long length; getter Element? (unsigned long index); - //getter (RadioNodeList or Element) (DOMString name); + getter (RadioNodeList or Element) (DOMString name); void submit(); + [Throws] void requestSubmit(optional HTMLElement? submitter = null); + [CEReactions] void reset(); - //boolean checkValidity(); - //boolean reportValidity(); + boolean checkValidity(); + boolean reportValidity(); +}; - //void requestAutocomplete(); +// https://html.spec.whatwg.org/multipage/#selectionmode +enum SelectionMode { + "preserve", // default + "select", + "start", + "end" }; diff --git a/components/script/dom/webidls/HTMLFrameElement.webidl b/components/script/dom/webidls/HTMLFrameElement.webidl index ecac61f6860..fe6cab1a5c2 100644 --- a/components/script/dom/webidls/HTMLFrameElement.webidl +++ b/components/script/dom/webidls/HTMLFrameElement.webidl @@ -1,18 +1,29 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlframeelement +[Exposed=Window] interface HTMLFrameElement : HTMLElement { - // attribute DOMString name; - // attribute DOMString scrolling; - // attribute DOMString src; - // attribute DOMString frameBorder; - // attribute DOMString longDesc; - // attribute boolean noResize; - //readonly attribute Document? contentDocument; - //readonly attribute WindowProxy? contentWindow; + [HTMLConstructor] constructor(); - //[TreatNullAs=EmptyString] attribute DOMString marginHeight; - //[TreatNullAs=EmptyString] attribute DOMString marginWidth; + // [CEReactions] + // attribute DOMString name; + // [CEReactions] + // attribute DOMString scrolling; + // [CEReactions] + // attribute DOMString src; + // [CEReactions] + // attribute DOMString frameBorder; + // [CEReactions] + // attribute DOMString longDesc; + // [CEReactions] + // attribute boolean noResize; + // readonly attribute Document? contentDocument; + // readonly attribute WindowProxy? contentWindow; + + // [CEReactions, TreatNullAs=EmptyString] + // attribute DOMString marginHeight; + // [CEReactions, TreatNullAs=EmptyString] + // attribute DOMString marginWidth; }; diff --git a/components/script/dom/webidls/HTMLFrameSetElement.webidl b/components/script/dom/webidls/HTMLFrameSetElement.webidl index 5addd41d253..24aa80dca28 100644 --- a/components/script/dom/webidls/HTMLFrameSetElement.webidl +++ b/components/script/dom/webidls/HTMLFrameSetElement.webidl @@ -1,11 +1,16 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlframesetelement +[Exposed=Window] interface HTMLFrameSetElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute DOMString cols; + // [CEReactions] // attribute DOMString rows; }; -HTMLFrameSetElement implements WindowEventHandlers; +HTMLFrameSetElement includes WindowEventHandlers; diff --git a/components/script/dom/webidls/HTMLHRElement.webidl b/components/script/dom/webidls/HTMLHRElement.webidl index 56e2f6ae19b..8963d5e8901 100644 --- a/components/script/dom/webidls/HTMLHRElement.webidl +++ b/components/script/dom/webidls/HTMLHRElement.webidl @@ -1,17 +1,25 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlhrelement +[Exposed=Window] interface HTMLHRElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLHRElement-partial partial interface HTMLHRElement { + [CEReactions] attribute DOMString align; + [CEReactions] attribute DOMString color; - // attribute boolean noShade; - // attribute DOMString size; + // [CEReactions] + // attribute boolean noShade; + // [CEReactions] + // attribute DOMString size; + [CEReactions] attribute DOMString width; }; diff --git a/components/script/dom/webidls/HTMLHeadElement.webidl b/components/script/dom/webidls/HTMLHeadElement.webidl index 18e2b351d64..72fd1f7eb6d 100644 --- a/components/script/dom/webidls/HTMLHeadElement.webidl +++ b/components/script/dom/webidls/HTMLHeadElement.webidl @@ -1,6 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlheadelement -interface HTMLHeadElement : HTMLElement {}; +[Exposed=Window] +interface HTMLHeadElement : HTMLElement { + [HTMLConstructor] constructor(); +}; diff --git a/components/script/dom/webidls/HTMLHeadingElement.webidl b/components/script/dom/webidls/HTMLHeadingElement.webidl index 2c47d6fa10f..b2e6be1ca8f 100644 --- a/components/script/dom/webidls/HTMLHeadingElement.webidl +++ b/components/script/dom/webidls/HTMLHeadingElement.webidl @@ -1,13 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlheadingelement +[Exposed=Window] interface HTMLHeadingElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLHeadingElement-partial partial interface HTMLHeadingElement { + // [CEReactions] // attribute DOMString align; }; diff --git a/components/script/dom/webidls/HTMLHtmlElement.webidl b/components/script/dom/webidls/HTMLHtmlElement.webidl index ed409b1b84c..6b25a41ca8d 100644 --- a/components/script/dom/webidls/HTMLHtmlElement.webidl +++ b/components/script/dom/webidls/HTMLHtmlElement.webidl @@ -1,13 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlhtmlelement +[Exposed=Window] interface HTMLHtmlElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLHtmlElement-partial partial interface HTMLHtmlElement { + // [CEReactions] // attribute DOMString version; }; diff --git a/components/script/dom/webidls/HTMLHyperlinkElementUtils.webidl b/components/script/dom/webidls/HTMLHyperlinkElementUtils.webidl index a75ecb970a0..2f0f0ae68c0 100644 --- a/components/script/dom/webidls/HTMLHyperlinkElementUtils.webidl +++ b/components/script/dom/webidls/HTMLHyperlinkElementUtils.webidl @@ -1,25 +1,28 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlhyperlinkelementutils -[NoInterfaceObject] -interface HTMLHyperlinkElementUtils { -// stringifier attribute USVString href; - attribute USVString href; - readonly attribute USVString origin; - attribute USVString protocol; - attribute USVString username; - attribute USVString password; - attribute USVString host; - attribute USVString hostname; - attribute USVString port; - attribute USVString pathname; - attribute USVString search; - attribute USVString hash; - - // Adding a separate stringifier method until - // https://github.com/servo/servo/issues/7590 adds attribute stringifier - // support. - stringifier; +interface mixin HTMLHyperlinkElementUtils { + [CEReactions] + stringifier attribute USVString href; + readonly attribute USVString origin; + [CEReactions] + attribute USVString protocol; + [CEReactions] + attribute USVString username; + [CEReactions] + attribute USVString password; + [CEReactions] + attribute USVString host; + [CEReactions] + attribute USVString hostname; + [CEReactions] + attribute USVString port; + [CEReactions] + attribute USVString pathname; + [CEReactions] + attribute USVString search; + [CEReactions] + attribute USVString hash; }; diff --git a/components/script/dom/webidls/HTMLIFrameElement.webidl b/components/script/dom/webidls/HTMLIFrameElement.webidl index f02c7c35a1b..b9dd97bdea6 100644 --- a/components/script/dom/webidls/HTMLIFrameElement.webidl +++ b/components/script/dom/webidls/HTMLIFrameElement.webidl @@ -1,20 +1,29 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmliframeelement +[Exposed=Window] interface HTMLIFrameElement : HTMLElement { - attribute DOMString src; - // attribute DOMString srcdoc; + [HTMLConstructor] constructor(); - // https://github.com/servo/servo/issues/14453 - // attribute DOMString name; + [CEReactions] + attribute USVString src; + [CEReactions] + attribute DOMString srcdoc; - [SameObject, PutForwards=value] + [CEReactions] + attribute DOMString name; + + [SameObject, PutForwards=value] readonly attribute DOMTokenList sandbox; + // [CEReactions] // attribute boolean seamless; + [CEReactions] attribute boolean allowFullscreen; + [CEReactions] attribute DOMString width; + [CEReactions] attribute DOMString height; readonly attribute Document? contentDocument; readonly attribute WindowProxy? contentWindow; @@ -24,21 +33,17 @@ interface HTMLIFrameElement : HTMLElement { // https://html.spec.whatwg.org/multipage/#HTMLIFrameElement-partial partial interface HTMLIFrameElement { + // [CEReactions] // attribute DOMString align; + // [CEReactions] // attribute DOMString scrolling; + [CEReactions] attribute DOMString frameBorder; + // [CEReactions] // attribute DOMString longDesc; - //[TreatNullAs=EmptyString] attribute DOMString marginHeight; - //[TreatNullAs=EmptyString] attribute DOMString marginWidth; -}; - -partial interface HTMLIFrameElement { - [Func="::dom::window::Window::global_is_mozbrowser"] - attribute boolean mozbrowser; - - [Func="::dom::window::Window::global_is_mozbrowser"] - attribute boolean mozprivatebrowsing; + // [CEReactions, TreatNullAs=EmptyString] + // attribute DOMString marginHeight; + // [CEReactions, TreatNullAs=EmptyString] + // attribute DOMString marginWidth; }; - -HTMLIFrameElement implements BrowserElement; diff --git a/components/script/dom/webidls/HTMLImageElement.webidl b/components/script/dom/webidls/HTMLImageElement.webidl index 7bd69124d87..81f6b4674f1 100644 --- a/components/script/dom/webidls/HTMLImageElement.webidl +++ b/components/script/dom/webidls/HTMLImageElement.webidl @@ -1,35 +1,54 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlimageelement -[NamedConstructor=Image(optional unsigned long width, optional unsigned long height)] +[Exposed=Window, NamedConstructor=Image(optional unsigned long width, optional unsigned long height)] interface HTMLImageElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute DOMString alt; - attribute DOMString src; - // attribute DOMString srcset; + [CEReactions] + attribute USVString src; + [CEReactions] + attribute USVString srcset; + [CEReactions] attribute DOMString? crossOrigin; + [CEReactions] attribute DOMString useMap; + [CEReactions] attribute boolean isMap; + [CEReactions] attribute unsigned long width; + [CEReactions] attribute unsigned long height; readonly attribute unsigned long naturalWidth; readonly attribute unsigned long naturalHeight; readonly attribute boolean complete; - readonly attribute DOMString currentSrc; + readonly attribute USVString currentSrc; + [CEReactions] + attribute DOMString referrerPolicy; // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLImageElement-partial partial interface HTMLImageElement { + [CEReactions] attribute DOMString name; - // attribute DOMString lowsrc; + // [CEReactions] + // attribute DOMString lowsrc; + [CEReactions] attribute DOMString align; + [CEReactions] attribute unsigned long hspace; + [CEReactions] attribute unsigned long vspace; + [CEReactions] attribute DOMString longDesc; - [TreatNullAs=EmptyString] attribute DOMString border; + [CEReactions] + attribute [TreatNullAs=EmptyString] DOMString border; }; // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlimageelement-interface diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl index 5c644894bbf..6b8c557e83d 100644 --- a/components/script/dom/webidls/HTMLInputElement.webidl +++ b/components/script/dom/webidls/HTMLInputElement.webidl @@ -1,81 +1,116 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlinputelement +[Exposed=Window] interface HTMLInputElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute DOMString accept; + [CEReactions] attribute DOMString alt; + // [CEReactions] // attribute DOMString autocomplete; + // [CEReactions] // attribute boolean autofocus; + [CEReactions] attribute boolean defaultChecked; attribute boolean checked; + [CEReactions] attribute DOMString dirName; + [CEReactions] attribute boolean disabled; readonly attribute HTMLFormElement? form; readonly attribute FileList? files; - attribute DOMString formAction; - attribute DOMString formEnctype; - attribute DOMString formMethod; - attribute boolean formNoValidate; - attribute DOMString formTarget; - // attribute unsigned long height; - attribute boolean indeterminate; - // attribute DOMString inputMode; - //readonly attribute HTMLElement? list; + [CEReactions] + attribute DOMString formAction; + [CEReactions] + attribute DOMString formEnctype; + [CEReactions] + attribute DOMString formMethod; + [CEReactions] + attribute boolean formNoValidate; + [CEReactions] + attribute DOMString formTarget; + // [CEReactions] + // attribute unsigned long height; + attribute boolean indeterminate; + // [CEReactions] + // attribute DOMString inputMode; + readonly attribute HTMLElement? list; + [CEReactions] attribute DOMString max; - [SetterThrows] - attribute long maxLength; + [CEReactions, SetterThrows] + attribute long maxLength; + [CEReactions] attribute DOMString min; - [SetterThrows] - attribute long minLength; + [CEReactions, SetterThrows] + attribute long minLength; + [CEReactions] attribute boolean multiple; + [CEReactions] attribute DOMString name; + [CEReactions] attribute DOMString pattern; + [CEReactions] attribute DOMString placeholder; + [CEReactions] attribute boolean readOnly; + [CEReactions] attribute boolean required; - [SetterThrows] - attribute unsigned long size; - attribute DOMString src; + [CEReactions, SetterThrows] + attribute unsigned long size; + [CEReactions] + attribute USVString src; + [CEReactions] attribute DOMString step; + [CEReactions] attribute DOMString type; + [CEReactions] attribute DOMString defaultValue; -[TreatNullAs=EmptyString, SetterThrows] - attribute DOMString value; - // attribute Date? valueAsDate; - // attribute unrestricted double valueAsNumber; - // attribute double valueLow; - // attribute double valueHigh; - // attribute unsigned long width; + [CEReactions, SetterThrows] + attribute [TreatNullAs=EmptyString] DOMString value; + [SetterThrows] + attribute object? valueAsDate; + [SetterThrows] + attribute unrestricted double valueAsNumber; + // [CEReactions] + // attribute unsigned long width; - //void stepUp(optional long n = 1); - //void stepDown(optional long n = 1); + [Throws] void stepUp(optional long n = 1); + [Throws] void stepDown(optional long n = 1); - //readonly attribute boolean willValidate; - //readonly attribute ValidityState validity; - //readonly attribute DOMString validationMessage; - //boolean checkValidity(); - //boolean reportValidity(); - //void setCustomValidity(DOMString error); + readonly attribute boolean willValidate; + readonly attribute ValidityState validity; + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); - readonly attribute NodeList labels; + readonly attribute NodeList? labels; - //void select(); - attribute unsigned long selectionStart; - attribute unsigned long selectionEnd; - attribute DOMString selectionDirection; - //void setRangeText(DOMString replacement); - //void setRangeText(DOMString replacement, unsigned long start, unsigned long end, - // optional SelectionMode selectionMode = "preserve"); - void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); + void select(); + [SetterThrows] + attribute unsigned long? selectionStart; + [SetterThrows] + attribute unsigned long? selectionEnd; + [SetterThrows] + attribute DOMString? selectionDirection; + [Throws] + void setRangeText(DOMString replacement); + [Throws] + void setRangeText(DOMString replacement, unsigned long start, unsigned long end, + optional SelectionMode selectionMode = "preserve"); + [Throws] + void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); // also has obsolete members // Select with file-system paths for testing purpose [Pref="dom.testing.htmlinputelement.select_files.enabled"] void selectFiles(sequence<DOMString> path); - }; // https://html.spec.whatwg.org/multipage/#HTMLInputElement-partial diff --git a/components/script/dom/webidls/HTMLLIElement.webidl b/components/script/dom/webidls/HTMLLIElement.webidl index ea7d574eba4..e5c7e68c874 100644 --- a/components/script/dom/webidls/HTMLLIElement.webidl +++ b/components/script/dom/webidls/HTMLLIElement.webidl @@ -1,9 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmllielement +[Exposed=Window] interface HTMLLIElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute long value; // also has obsolete members @@ -11,5 +15,6 @@ interface HTMLLIElement : HTMLElement { // https://html.spec.whatwg.org/multipage/#HTMLLIElement-partial partial interface HTMLLIElement { + // [CEReactions] // attribute DOMString type; }; diff --git a/components/script/dom/webidls/HTMLLabelElement.webidl b/components/script/dom/webidls/HTMLLabelElement.webidl index 8acb1f312c8..228e45fc7c4 100644 --- a/components/script/dom/webidls/HTMLLabelElement.webidl +++ b/components/script/dom/webidls/HTMLLabelElement.webidl @@ -1,10 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmllabelelement +[Exposed=Window] interface HTMLLabelElement : HTMLElement { + [HTMLConstructor] constructor(); + readonly attribute HTMLFormElement? form; + [CEReactions] attribute DOMString htmlFor; readonly attribute HTMLElement? control; }; diff --git a/components/script/dom/webidls/HTMLLegendElement.webidl b/components/script/dom/webidls/HTMLLegendElement.webidl index c137d6db66a..1ef2a8fd701 100644 --- a/components/script/dom/webidls/HTMLLegendElement.webidl +++ b/components/script/dom/webidls/HTMLLegendElement.webidl @@ -1,9 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmllegendelement +[Exposed=Window] interface HTMLLegendElement : HTMLElement { + [HTMLConstructor] constructor(); + readonly attribute HTMLFormElement? form; // also has obsolete members @@ -11,5 +14,6 @@ interface HTMLLegendElement : HTMLElement { // https://html.spec.whatwg.org/multipage/#HTMLLegendElement-partial partial interface HTMLLegendElement { + // [CEReactions] // attribute DOMString align; }; diff --git a/components/script/dom/webidls/HTMLLinkElement.webidl b/components/script/dom/webidls/HTMLLinkElement.webidl index bfab6270092..31590a8dc04 100644 --- a/components/script/dom/webidls/HTMLLinkElement.webidl +++ b/components/script/dom/webidls/HTMLLinkElement.webidl @@ -1,26 +1,40 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmllinkelement +[Exposed=Window] interface HTMLLinkElement : HTMLElement { - attribute DOMString href; + [HTMLConstructor] constructor(); + + [CEReactions] + attribute USVString href; + [CEReactions] attribute DOMString? crossOrigin; + [CEReactions] attribute DOMString rel; - readonly attribute DOMTokenList relList; + [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; + [CEReactions] attribute DOMString media; + [CEReactions] attribute DOMString hreflang; + [CEReactions] attribute DOMString type; + [CEReactions] attribute DOMString integrity; - // [SameObject, PutForwards=value] readonly attribute DOMTokenList sizes; + [CEReactions] + attribute DOMString referrerPolicy; // also has obsolete members }; -HTMLLinkElement implements LinkStyle; +HTMLLinkElement includes LinkStyle; // https://html.spec.whatwg.org/multipage/#HTMLLinkElement-partial partial interface HTMLLinkElement { + [CEReactions] attribute DOMString charset; + [CEReactions] attribute DOMString rev; + [CEReactions] attribute DOMString target; }; diff --git a/components/script/dom/webidls/HTMLMapElement.webidl b/components/script/dom/webidls/HTMLMapElement.webidl index 5e21b52916e..44c397948fd 100644 --- a/components/script/dom/webidls/HTMLMapElement.webidl +++ b/components/script/dom/webidls/HTMLMapElement.webidl @@ -1,10 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlmapelement +[Exposed=Window] interface HTMLMapElement : HTMLElement { - // attribute DOMString name; - //readonly attribute HTMLCollection areas; - //readonly attribute HTMLCollection images; + [HTMLConstructor] constructor(); + + // [CEReactions] + // attribute DOMString name; + // readonly attribute HTMLCollection areas; + // readonly attribute HTMLCollection images; }; diff --git a/components/script/dom/webidls/HTMLMediaElement.webidl b/components/script/dom/webidls/HTMLMediaElement.webidl index ce952c9b539..fd658913ca5 100644 --- a/components/script/dom/webidls/HTMLMediaElement.webidl +++ b/components/script/dom/webidls/HTMLMediaElement.webidl @@ -1,67 +1,66 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlmediaelement + enum CanPlayTypeResult { "" /* empty string */, "maybe", "probably" }; -[Abstract] -interface HTMLMediaElement : HTMLElement { +typedef (MediaStream /*or MediaSource */ or Blob) MediaProvider; +[Exposed=Window, Abstract] +interface HTMLMediaElement : HTMLElement { // error state - readonly attribute MediaError? error; + readonly attribute MediaError? error; // network state - attribute DOMString src; - readonly attribute DOMString currentSrc; - // attribute DOMString crossOrigin; - const unsigned short NETWORK_EMPTY = 0; - const unsigned short NETWORK_IDLE = 1; - const unsigned short NETWORK_LOADING = 2; - const unsigned short NETWORK_NO_SOURCE = 3; - readonly attribute unsigned short networkState; - attribute DOMString preload; - //readonly attribute TimeRanges buffered; - void load(); - CanPlayTypeResult canPlayType(DOMString type); + [CEReactions] attribute USVString src; + attribute MediaProvider? srcObject; + readonly attribute USVString currentSrc; + [CEReactions] attribute DOMString? crossOrigin; + const unsigned short NETWORK_EMPTY = 0; + const unsigned short NETWORK_IDLE = 1; + const unsigned short NETWORK_LOADING = 2; + const unsigned short NETWORK_NO_SOURCE = 3; + readonly attribute unsigned short networkState; + [CEReactions] attribute DOMString preload; + readonly attribute TimeRanges buffered; + void load(); + CanPlayTypeResult canPlayType(DOMString type); // ready state - const unsigned short HAVE_NOTHING = 0; - const unsigned short HAVE_METADATA = 1; - const unsigned short HAVE_CURRENT_DATA = 2; - const unsigned short HAVE_FUTURE_DATA = 3; - const unsigned short HAVE_ENOUGH_DATA = 4; - readonly attribute unsigned short readyState; - //readonly attribute boolean seeking; + const unsigned short HAVE_NOTHING = 0; + const unsigned short HAVE_METADATA = 1; + const unsigned short HAVE_CURRENT_DATA = 2; + const unsigned short HAVE_FUTURE_DATA = 3; + const unsigned short HAVE_ENOUGH_DATA = 4; + readonly attribute unsigned short readyState; + readonly attribute boolean seeking; // playback state - // attribute double currentTime; - //void fastSeek(double time); - //readonly attribute unrestricted double duration; - //Date getStartDate(); - readonly attribute boolean paused; - // attribute double defaultPlaybackRate; - // attribute double playbackRate; - //readonly attribute TimeRanges played; - //readonly attribute TimeRanges seekable; - //readonly attribute boolean ended; - attribute boolean autoplay; - // attribute boolean loop; - void play(); - void pause(); - - // media controller - // attribute DOMString mediaGroup; - // attribute MediaController? controller; + attribute double currentTime; + void fastSeek(double time); + readonly attribute unrestricted double duration; + // Date getStartDate(); + readonly attribute boolean paused; + [Throws] attribute double defaultPlaybackRate; + [Throws] attribute double playbackRate; + readonly attribute TimeRanges played; + // readonly attribute TimeRanges seekable; + readonly attribute boolean ended; + [CEReactions] attribute boolean autoplay; + [CEReactions] attribute boolean loop; + Promise<void> play(); + void pause(); // controls - // attribute boolean controls; - // attribute double volume; - // attribute boolean muted; - // attribute boolean defaultMuted; + [CEReactions] attribute boolean controls; + [Throws] attribute double volume; + attribute boolean muted; + [CEReactions] attribute boolean defaultMuted; // tracks - //readonly attribute AudioTrackList audioTracks; - //readonly attribute VideoTrackList videoTracks; - //readonly attribute TextTrackList textTracks; - //TextTrack addTextTrack(TextTrackKind kind, optional DOMString label = "", optional DOMString language = ""); + readonly attribute AudioTrackList audioTracks; + readonly attribute VideoTrackList videoTracks; + readonly attribute TextTrackList textTracks; + TextTrack addTextTrack(TextTrackKind kind, optional DOMString label = "", optional DOMString language = ""); }; diff --git a/components/script/dom/webidls/HTMLMenuElement.webidl b/components/script/dom/webidls/HTMLMenuElement.webidl new file mode 100644 index 00000000000..be638bd4d88 --- /dev/null +++ b/components/script/dom/webidls/HTMLMenuElement.webidl @@ -0,0 +1,17 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#htmlmenuelement +[Exposed=Window] +interface HTMLMenuElement : HTMLElement { + [HTMLConstructor] constructor(); + + // also has obsolete members +}; + +// https://html.spec.whatwg.org/multipage/#HTMLMenuElement-partial +partial interface HTMLMenuElement { + [CEReactions] + attribute boolean compact; +}; diff --git a/components/script/dom/webidls/HTMLMetaElement.webidl b/components/script/dom/webidls/HTMLMetaElement.webidl index 20afc297a20..c8d4faeba5f 100644 --- a/components/script/dom/webidls/HTMLMetaElement.webidl +++ b/components/script/dom/webidls/HTMLMetaElement.webidl @@ -1,11 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlmetaelement +[Exposed=Window] interface HTMLMetaElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute DOMString name; + // [CEReactions] // attribute DOMString httpEquiv; + [CEReactions] attribute DOMString content; // also has obsolete members @@ -13,5 +19,6 @@ interface HTMLMetaElement : HTMLElement { // https://html.spec.whatwg.org/multipage/#HTMLMetaElement-partial partial interface HTMLMetaElement { + // [CEReactions] // attribute DOMString scheme; }; diff --git a/components/script/dom/webidls/HTMLMeterElement.webidl b/components/script/dom/webidls/HTMLMeterElement.webidl index c6abe4aef46..4021d2ec185 100644 --- a/components/script/dom/webidls/HTMLMeterElement.webidl +++ b/components/script/dom/webidls/HTMLMeterElement.webidl @@ -1,14 +1,23 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlmeterelement +[Exposed=Window] interface HTMLMeterElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute double value; + // [CEReactions] // attribute double min; + // [CEReactions] // attribute double max; + // [CEReactions] // attribute double low; + // [CEReactions] // attribute double high; + // [CEReactions] // attribute double optimum; readonly attribute NodeList labels; }; diff --git a/components/script/dom/webidls/HTMLModElement.webidl b/components/script/dom/webidls/HTMLModElement.webidl index beda6f97dcc..6d26249c447 100644 --- a/components/script/dom/webidls/HTMLModElement.webidl +++ b/components/script/dom/webidls/HTMLModElement.webidl @@ -1,9 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlmodelement +[Exposed=Window] interface HTMLModElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute DOMString cite; + // [CEReactions] // attribute DOMString dateTime; }; diff --git a/components/script/dom/webidls/HTMLOListElement.webidl b/components/script/dom/webidls/HTMLOListElement.webidl index 02dc3d1146f..3739d6d98af 100644 --- a/components/script/dom/webidls/HTMLOListElement.webidl +++ b/components/script/dom/webidls/HTMLOListElement.webidl @@ -1,11 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlolistelement +[Exposed=Window] interface HTMLOListElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute boolean reversed; + // [CEReactions] // attribute long start; + // [CEReactions] // attribute DOMString type; // also has obsolete members @@ -13,5 +19,6 @@ interface HTMLOListElement : HTMLElement { // https://html.spec.whatwg.org/multipage/#HTMLOListElement-partial partial interface HTMLOListElement { + // [CEReactions] // attribute boolean compact; }; diff --git a/components/script/dom/webidls/HTMLObjectElement.webidl b/components/script/dom/webidls/HTMLObjectElement.webidl index ce1d0ff1f8c..9d60c4a4f92 100644 --- a/components/script/dom/webidls/HTMLObjectElement.webidl +++ b/components/script/dom/webidls/HTMLObjectElement.webidl @@ -1,26 +1,36 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlobjectelement +[Exposed=Window] interface HTMLObjectElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute DOMString data; + [CEReactions] attribute DOMString type; + // [CEReactions] // attribute boolean typeMustMatch; + // [CEReactions] // attribute DOMString name; + // [CEReactions] // attribute DOMString useMap; readonly attribute HTMLFormElement? form; + // [CEReactions] // attribute DOMString width; + // [CEReactions] // attribute DOMString height; //readonly attribute Document? contentDocument; //readonly attribute WindowProxy? contentWindow; - //readonly attribute boolean willValidate; + readonly attribute boolean willValidate; readonly attribute ValidityState validity; - //readonly attribute DOMString validationMessage; - //boolean checkValidity(); - //boolean reportValidity(); - //void setCustomValidity(DOMString error); + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); //legacycaller any (any... arguments); diff --git a/components/script/dom/webidls/HTMLOptGroupElement.webidl b/components/script/dom/webidls/HTMLOptGroupElement.webidl index a81df036a4d..afa11148cd5 100644 --- a/components/script/dom/webidls/HTMLOptGroupElement.webidl +++ b/components/script/dom/webidls/HTMLOptGroupElement.webidl @@ -1,9 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmloptgroupelement +[Exposed=Window] interface HTMLOptGroupElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute boolean disabled; - // attribute DOMString label; + // [CEReactions] + // attribute DOMString label; }; diff --git a/components/script/dom/webidls/HTMLOptionElement.webidl b/components/script/dom/webidls/HTMLOptionElement.webidl index 713ff85a483..c995070d0e7 100644 --- a/components/script/dom/webidls/HTMLOptionElement.webidl +++ b/components/script/dom/webidls/HTMLOptionElement.webidl @@ -1,19 +1,26 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmloptionelement -//[NamedConstructor=Option(optional DOMString text = "", optional DOMString value, -// optional boolean defaultSelected = false, -// optional boolean selected = false)] +[Exposed=Window, NamedConstructor=Option(optional DOMString text = "", optional DOMString value, + optional boolean defaultSelected = false, + optional boolean selected = false)] interface HTMLOptionElement : HTMLElement { - attribute boolean disabled; - readonly attribute HTMLFormElement? form; - attribute DOMString label; - attribute boolean defaultSelected; - attribute boolean selected; - attribute DOMString value; + [HTMLConstructor] constructor(); - attribute DOMString text; - //readonly attribute long index; + [CEReactions] + attribute boolean disabled; + readonly attribute HTMLFormElement? form; + [CEReactions] + attribute DOMString label; + [CEReactions] + attribute boolean defaultSelected; + attribute boolean selected; + [CEReactions] + attribute DOMString value; + + [CEReactions] + attribute DOMString text; + readonly attribute long index; }; diff --git a/components/script/dom/webidls/HTMLOptionsCollection.webidl b/components/script/dom/webidls/HTMLOptionsCollection.webidl index 73b11779159..91906b785fb 100644 --- a/components/script/dom/webidls/HTMLOptionsCollection.webidl +++ b/components/script/dom/webidls/HTMLOptionsCollection.webidl @@ -1,18 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmloptionscollection +[Exposed=Window] interface HTMLOptionsCollection : HTMLCollection { // inherits item(), namedItem() + [CEReactions] attribute unsigned long length; // shadows inherited length - //[CEReactions] - [Throws] + [CEReactions, Throws] setter void (unsigned long index, HTMLOptionElement? option); - //[CEReactions] - [Throws] + [CEReactions, Throws] void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); - //[CEReactions] + [CEReactions] void remove(long index); attribute long selectedIndex; }; diff --git a/components/script/dom/webidls/HTMLOutputElement.webidl b/components/script/dom/webidls/HTMLOutputElement.webidl index f0baeb8fa03..c44f2a7f44c 100644 --- a/components/script/dom/webidls/HTMLOutputElement.webidl +++ b/components/script/dom/webidls/HTMLOutputElement.webidl @@ -1,23 +1,29 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmloutputelement +[Exposed=Window] interface HTMLOutputElement : HTMLElement { + [HTMLConstructor] constructor(); + // [SameObject, PutForwards=value] readonly attribute DOMTokenList htmlFor; readonly attribute HTMLFormElement? form; - // attribute DOMString name; + [CEReactions] + attribute DOMString name; - //readonly attribute DOMString type; - // attribute DOMString defaultValue; - // attribute DOMString value; + [Pure] readonly attribute DOMString type; + [CEReactions] + attribute DOMString defaultValue; + [CEReactions] + attribute DOMString value; - //readonly attribute boolean willValidate; + readonly attribute boolean willValidate; readonly attribute ValidityState validity; - //readonly attribute DOMString validationMessage; - //boolean checkValidity(); - //boolean reportValidity(); - //void setCustomValidity(DOMString error); + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); readonly attribute NodeList labels; }; diff --git a/components/script/dom/webidls/HTMLParagraphElement.webidl b/components/script/dom/webidls/HTMLParagraphElement.webidl index a96c6dc6f81..d42533b9ef9 100644 --- a/components/script/dom/webidls/HTMLParagraphElement.webidl +++ b/components/script/dom/webidls/HTMLParagraphElement.webidl @@ -1,13 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlparagraphelement +[Exposed=Window] interface HTMLParagraphElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLParagraphElement-partial partial interface HTMLParagraphElement { + // [CEReactions] // attribute DOMString align; }; diff --git a/components/script/dom/webidls/HTMLParamElement.webidl b/components/script/dom/webidls/HTMLParamElement.webidl index 9648c9f87ce..4539b3b8474 100644 --- a/components/script/dom/webidls/HTMLParamElement.webidl +++ b/components/script/dom/webidls/HTMLParamElement.webidl @@ -1,10 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlparamelement +[Exposed=Window] interface HTMLParamElement : HTMLElement { + [HTMLConstructor] constructor(); + + // [CEReactions] // attribute DOMString name; + // [CEReactions] // attribute DOMString value; // also has obsolete members @@ -12,6 +17,8 @@ interface HTMLParamElement : HTMLElement { // https://html.spec.whatwg.org/multipage/#HTMLParamElement-partial partial interface HTMLParamElement { + // [CEReactions] // attribute DOMString type; + // [CEReactions] // attribute DOMString valueType; }; diff --git a/components/script/dom/webidls/HTMLPictureElement.webidl b/components/script/dom/webidls/HTMLPictureElement.webidl new file mode 100644 index 00000000000..d03377ee541 --- /dev/null +++ b/components/script/dom/webidls/HTMLPictureElement.webidl @@ -0,0 +1,9 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#htmlpictureelement +[Exposed=Window] +interface HTMLPictureElement : HTMLElement { + [HTMLConstructor] constructor(); +}; diff --git a/components/script/dom/webidls/HTMLPreElement.webidl b/components/script/dom/webidls/HTMLPreElement.webidl index ea0df151020..ecbebb71e38 100644 --- a/components/script/dom/webidls/HTMLPreElement.webidl +++ b/components/script/dom/webidls/HTMLPreElement.webidl @@ -1,13 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlpreelement +[Exposed=Window] interface HTMLPreElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLPreElement-partial partial interface HTMLPreElement { + // [CEReactions] // attribute long width; }; diff --git a/components/script/dom/webidls/HTMLProgressElement.webidl b/components/script/dom/webidls/HTMLProgressElement.webidl index cf69566ecdd..c901e379659 100644 --- a/components/script/dom/webidls/HTMLProgressElement.webidl +++ b/components/script/dom/webidls/HTMLProgressElement.webidl @@ -1,11 +1,16 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlprogresselement +[Exposed=Window] interface HTMLProgressElement : HTMLElement { - // attribute double value; - // attribute double max; - //readonly attribute double position; + [HTMLConstructor] constructor(); + + // [CEReactions] + // attribute double value; + // [CEReactions] + // attribute double max; + // readonly attribute double position; readonly attribute NodeList labels; }; diff --git a/components/script/dom/webidls/HTMLQuoteElement.webidl b/components/script/dom/webidls/HTMLQuoteElement.webidl index e546f151d49..d7623fb47b9 100644 --- a/components/script/dom/webidls/HTMLQuoteElement.webidl +++ b/components/script/dom/webidls/HTMLQuoteElement.webidl @@ -1,8 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlquoteelement +[Exposed=Window] interface HTMLQuoteElement : HTMLElement { - // attribute DOMString cite; + [HTMLConstructor] constructor(); + + // [CEReactions] + // attribute DOMString cite; }; diff --git a/components/script/dom/webidls/HTMLScriptElement.webidl b/components/script/dom/webidls/HTMLScriptElement.webidl index 8a1dbc3fc35..b79382dbbb8 100644 --- a/components/script/dom/webidls/HTMLScriptElement.webidl +++ b/components/script/dom/webidls/HTMLScriptElement.webidl @@ -1,24 +1,40 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlscriptelement +[Exposed=Window] interface HTMLScriptElement : HTMLElement { - attribute DOMString src; + [HTMLConstructor] constructor(); + + [CEReactions] + attribute USVString src; + [CEReactions] attribute DOMString type; + [CEReactions] + attribute boolean noModule; + [CEReactions] attribute DOMString charset; + [CEReactions] attribute boolean async; + [CEReactions] attribute boolean defer; + [CEReactions] attribute DOMString? crossOrigin; - [Pure] + [CEReactions, Pure] attribute DOMString text; + [CEReactions] attribute DOMString integrity; + [CEReactions] + attribute DOMString referrerPolicy; // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLScriptElement-partial partial interface HTMLScriptElement { + [CEReactions] attribute DOMString event; + [CEReactions] attribute DOMString htmlFor; }; diff --git a/components/script/dom/webidls/HTMLSelectElement.webidl b/components/script/dom/webidls/HTMLSelectElement.webidl index 543c3267c64..91258f88ee8 100644 --- a/components/script/dom/webidls/HTMLSelectElement.webidl +++ b/components/script/dom/webidls/HTMLSelectElement.webidl @@ -1,39 +1,52 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlselectelement +[Exposed=Window] interface HTMLSelectElement : HTMLElement { - // attribute boolean autofocus; + [HTMLConstructor] constructor(); + + // [CEReactions] + // attribute boolean autofocus; + [CEReactions] attribute boolean disabled; readonly attribute HTMLFormElement? form; + [CEReactions] attribute boolean multiple; + [CEReactions] attribute DOMString name; - // attribute boolean required; + [CEReactions] + attribute boolean required; + [CEReactions] attribute unsigned long size; readonly attribute DOMString type; readonly attribute HTMLOptionsCollection options; + [CEReactions] attribute unsigned long length; getter Element? item(unsigned long index); HTMLOptionElement? namedItem(DOMString name); - // Note: this function currently only exists for union.html. + + [CEReactions, Throws] void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); + [CEReactions] void remove(); // ChildNode overload + [CEReactions] void remove(long index); - //setter void (unsigned long index, HTMLOptionElement? option); + [CEReactions, Throws] setter void (unsigned long index, HTMLOptionElement? option); - //readonly attribute HTMLCollection selectedOptions; + // readonly attribute HTMLCollection selectedOptions; attribute long selectedIndex; attribute DOMString value; - //readonly attribute boolean willValidate; + readonly attribute boolean willValidate; readonly attribute ValidityState validity; - //readonly attribute DOMString validationMessage; - //boolean checkValidity(); - //boolean reportValidity(); - //void setCustomValidity(DOMString error); + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); readonly attribute NodeList labels; }; diff --git a/components/script/dom/webidls/HTMLSourceElement.webidl b/components/script/dom/webidls/HTMLSourceElement.webidl index 738a545713a..92f75ff5995 100644 --- a/components/script/dom/webidls/HTMLSourceElement.webidl +++ b/components/script/dom/webidls/HTMLSourceElement.webidl @@ -1,9 +1,20 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlsourceelement +[Exposed=Window] interface HTMLSourceElement : HTMLElement { - // attribute DOMString src; - // attribute DOMString type; + [HTMLConstructor] constructor(); + + [CEReactions] + attribute DOMString src; + [CEReactions] + attribute DOMString type; + [CEReactions] + attribute DOMString srcset; + [CEReactions] + attribute DOMString sizes; + [CEReactions] + attribute DOMString media; }; diff --git a/components/script/dom/webidls/HTMLSpanElement.webidl b/components/script/dom/webidls/HTMLSpanElement.webidl index a74967536a1..2645cd7678b 100644 --- a/components/script/dom/webidls/HTMLSpanElement.webidl +++ b/components/script/dom/webidls/HTMLSpanElement.webidl @@ -1,6 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlspanelement -interface HTMLSpanElement : HTMLElement {}; +[Exposed=Window] +interface HTMLSpanElement : HTMLElement { + [HTMLConstructor] constructor(); +}; diff --git a/components/script/dom/webidls/HTMLStyleElement.webidl b/components/script/dom/webidls/HTMLStyleElement.webidl index 78926c2c1a8..dd68cb6270a 100644 --- a/components/script/dom/webidls/HTMLStyleElement.webidl +++ b/components/script/dom/webidls/HTMLStyleElement.webidl @@ -1,11 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlstyleelement +[Exposed=Window] interface HTMLStyleElement : HTMLElement { - // attribute DOMString media; - // attribute DOMString type; - // attribute boolean scoped; + [HTMLConstructor] constructor(); + + // [CEReactions] + // attribute DOMString media; + // [CEReactions] + // attribute DOMString type; + // [CEReactions] + // attribute boolean scoped; }; -HTMLStyleElement implements LinkStyle; +HTMLStyleElement includes LinkStyle; diff --git a/components/script/dom/webidls/HTMLTableCaptionElement.webidl b/components/script/dom/webidls/HTMLTableCaptionElement.webidl index b405d23ed40..e834be183c8 100644 --- a/components/script/dom/webidls/HTMLTableCaptionElement.webidl +++ b/components/script/dom/webidls/HTMLTableCaptionElement.webidl @@ -1,13 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltablecaptionelement +[Exposed=Window] interface HTMLTableCaptionElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLTableCaptionElement-partial partial interface HTMLTableCaptionElement { - // attribute DOMString align; + // [CEReactions] + // attribute DOMString align; }; diff --git a/components/script/dom/webidls/HTMLTableCellElement.webidl b/components/script/dom/webidls/HTMLTableCellElement.webidl index 8ac135170ec..3c955de66b9 100644 --- a/components/script/dom/webidls/HTMLTableCellElement.webidl +++ b/components/script/dom/webidls/HTMLTableCellElement.webidl @@ -1,29 +1,47 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltablecellelement -[Abstract] +[Exposed=Window] interface HTMLTableCellElement : HTMLElement { - attribute unsigned long colSpan; - attribute unsigned long rowSpan; - // attribute DOMString headers; + [HTMLConstructor] constructor(); + + [CEReactions] + attribute unsigned long colSpan; + [CEReactions] + attribute unsigned long rowSpan; + // [CEReactions] + // attribute DOMString headers; readonly attribute long cellIndex; + // [CEReactions] + // attribute DOMString scope; // only conforming for th elements + // [CEReactions] + // attribute DOMString abbr; // only conforming for th elements + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLTableCellElement-partial partial interface HTMLTableCellElement { - // attribute DOMString align; - // attribute DOMString axis; - // attribute DOMString height; + // [CEReactions] + // attribute DOMString align; + // [CEReactions] + // attribute DOMString axis; + // [CEReactions] + // attribute DOMString height; + [CEReactions] attribute DOMString width; - // attribute DOMString ch; - // attribute DOMString chOff; - // attribute boolean noWrap; - // attribute DOMString vAlign; + // attribute DOMString ch; + // [CEReactions] + // attribute DOMString chOff; + // [CEReactions] + // attribute boolean noWrap; + // [CEReactions] + // attribute DOMString vAlign; - [TreatNullAs=EmptyString] attribute DOMString bgColor; + [CEReactions] + attribute [TreatNullAs=EmptyString] DOMString bgColor; }; diff --git a/components/script/dom/webidls/HTMLTableColElement.webidl b/components/script/dom/webidls/HTMLTableColElement.webidl index 69188251443..1d853643827 100644 --- a/components/script/dom/webidls/HTMLTableColElement.webidl +++ b/components/script/dom/webidls/HTMLTableColElement.webidl @@ -1,19 +1,28 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltablecolelement +[Exposed=Window] interface HTMLTableColElement : HTMLElement { - // attribute unsigned long span; + [HTMLConstructor] constructor(); + + // [CEReactions] + // attribute unsigned long span; // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLTableColElement-partial partial interface HTMLTableColElement { - // attribute DOMString align; - // attribute DOMString ch; - // attribute DOMString chOff; - // attribute DOMString vAlign; - // attribute DOMString width; + // [CEReactions] + // attribute DOMString align; + // [CEReactions] + // attribute DOMString ch; + // [CEReactions] + // attribute DOMString chOff; + // [CEReactions] + // attribute DOMString vAlign; + // [CEReactions] + // attribute DOMString width; }; diff --git a/components/script/dom/webidls/HTMLTableDataCellElement.webidl b/components/script/dom/webidls/HTMLTableDataCellElement.webidl deleted file mode 100644 index 208ed76d692..00000000000 --- a/components/script/dom/webidls/HTMLTableDataCellElement.webidl +++ /dev/null @@ -1,13 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://html.spec.whatwg.org/multipage/#htmltabledatacellelement -interface HTMLTableDataCellElement : HTMLTableCellElement { - // also has obsolete members -}; - -// https://html.spec.whatwg.org/multipage/#HTMLTableDataCellElement-partial -partial interface HTMLTableDataCellElement { - // attribute DOMString abbr; -}; diff --git a/components/script/dom/webidls/HTMLTableElement.webidl b/components/script/dom/webidls/HTMLTableElement.webidl index f0d8e19d0eb..05acced2e57 100644 --- a/components/script/dom/webidls/HTMLTableElement.webidl +++ b/components/script/dom/webidls/HTMLTableElement.webidl @@ -1,39 +1,59 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltableelement +[Exposed=Window] interface HTMLTableElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute HTMLTableCaptionElement? caption; HTMLTableCaptionElement createCaption(); + [CEReactions] void deleteCaption(); - [SetterThrows] + + [CEReactions, SetterThrows] attribute HTMLTableSectionElement? tHead; HTMLTableSectionElement createTHead(); + [CEReactions] void deleteTHead(); - [SetterThrows] + + [CEReactions, SetterThrows] attribute HTMLTableSectionElement? tFoot; HTMLTableSectionElement createTFoot(); + [CEReactions] void deleteTFoot(); + readonly attribute HTMLCollection tBodies; HTMLTableSectionElement createTBody(); + readonly attribute HTMLCollection rows; [Throws] HTMLTableRowElement insertRow(optional long index = -1); - [Throws] void deleteRow(long index); + [CEReactions, Throws] void deleteRow(long index); // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLTableElement-partial partial interface HTMLTableElement { - // attribute DOMString align; - // attribute DOMString border; - // attribute DOMString frame; - // attribute DOMString rules; - // attribute DOMString summary; + // [CEReactions] + // attribute DOMString align; + // [CEReactions] + // attribute DOMString border; + // [CEReactions] + // attribute DOMString frame; + // [CEReactions] + // attribute DOMString rules; + // [CEReactions] + // attribute DOMString summary; + [CEReactions] attribute DOMString width; - [TreatNullAs=EmptyString] attribute DOMString bgColor; - //[TreatNullAs=EmptyString] attribute DOMString cellPadding; - //[TreatNullAs=EmptyString] attribute DOMString cellSpacing; + [CEReactions] + attribute [TreatNullAs=EmptyString] DOMString bgColor; + // [CEReactions, TreatNullAs=EmptyString] + // attribute DOMString cellPadding; + // [CEReactions, TreatNullAs=EmptyString] + // attribute DOMString cellSpacing; }; diff --git a/components/script/dom/webidls/HTMLTableHeaderCellElement.webidl b/components/script/dom/webidls/HTMLTableHeaderCellElement.webidl deleted file mode 100644 index 9bf8f1fc950..00000000000 --- a/components/script/dom/webidls/HTMLTableHeaderCellElement.webidl +++ /dev/null @@ -1,11 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://html.spec.whatwg.org/multipage/#htmltableheadercellelement -interface HTMLTableHeaderCellElement : HTMLTableCellElement { - // attribute DOMString scope; - // attribute DOMString abbr; - // attribute DOMString sorted; - //void sort(); -}; diff --git a/components/script/dom/webidls/HTMLTableRowElement.webidl b/components/script/dom/webidls/HTMLTableRowElement.webidl index 9d4b0655cad..c51e06e6fdc 100644 --- a/components/script/dom/webidls/HTMLTableRowElement.webidl +++ b/components/script/dom/webidls/HTMLTableRowElement.webidl @@ -1,15 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltablerowelement +[Exposed=Window] interface HTMLTableRowElement : HTMLElement { + [HTMLConstructor] constructor(); + readonly attribute long rowIndex; readonly attribute long sectionRowIndex; readonly attribute HTMLCollection cells; [Throws] HTMLElement insertCell(optional long index = -1); - [Throws] + [CEReactions, Throws] void deleteCell(long index); // also has obsolete members @@ -17,10 +20,15 @@ interface HTMLTableRowElement : HTMLElement { // https://html.spec.whatwg.org/multipage/#HTMLTableRowElement-partial partial interface HTMLTableRowElement { - // attribute DOMString align; - // attribute DOMString ch; - // attribute DOMString chOff; - // attribute DOMString vAlign; + // [CEReactions] + // attribute DOMString align; + // [CEReactions] + // attribute DOMString ch; + // [CEReactions] + // attribute DOMString chOff; + // [CEReactions] + // attribute DOMString vAlign; - [TreatNullAs=EmptyString] attribute DOMString bgColor; + [CEReactions] + attribute [TreatNullAs=EmptyString] DOMString bgColor; }; diff --git a/components/script/dom/webidls/HTMLTableSectionElement.webidl b/components/script/dom/webidls/HTMLTableSectionElement.webidl index 979d8030ffd..e73d16020dc 100644 --- a/components/script/dom/webidls/HTMLTableSectionElement.webidl +++ b/components/script/dom/webidls/HTMLTableSectionElement.webidl @@ -1,13 +1,16 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltablesectionelement +[Exposed=Window] interface HTMLTableSectionElement : HTMLElement { + [HTMLConstructor] constructor(); + readonly attribute HTMLCollection rows; [Throws] HTMLElement insertRow(optional long index = -1); - [Throws] + [CEReactions, Throws] void deleteRow(long index); // also has obsolete members @@ -15,8 +18,12 @@ interface HTMLTableSectionElement : HTMLElement { // https://html.spec.whatwg.org/multipage/#HTMLTableSectionElement-partial partial interface HTMLTableSectionElement { - // attribute DOMString align; - // attribute DOMString ch; - // attribute DOMString chOff; - // attribute DOMString vAlign; + // [CEReactions] + // attribute DOMString align; + // [CEReactions] + // attribute DOMString ch; + // [CEReactions] + // attribute DOMString chOff; + // [CEReactions] + // attribute DOMString vAlign; }; diff --git a/components/script/dom/webidls/HTMLTemplateElement.webidl b/components/script/dom/webidls/HTMLTemplateElement.webidl index b3383de69d2..b71d37f4914 100644 --- a/components/script/dom/webidls/HTMLTemplateElement.webidl +++ b/components/script/dom/webidls/HTMLTemplateElement.webidl @@ -1,8 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltemplateelement +[Exposed=Window] interface HTMLTemplateElement : HTMLElement { + [HTMLConstructor] constructor(); + readonly attribute DocumentFragment content; }; diff --git a/components/script/dom/webidls/HTMLTextAreaElement.webidl b/components/script/dom/webidls/HTMLTextAreaElement.webidl index f92e662c354..b726946bc48 100644 --- a/components/script/dom/webidls/HTMLTextAreaElement.webidl +++ b/components/script/dom/webidls/HTMLTextAreaElement.webidl @@ -1,47 +1,69 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltextareaelement +[Exposed=Window] interface HTMLTextAreaElement : HTMLElement { - // attribute DOMString autocomplete; - // attribute boolean autofocus; - [SetterThrows] - attribute unsigned long cols; - // attribute DOMString dirName; + [HTMLConstructor] constructor(); + + // [CEReactions] + // attribute DOMString autocomplete; + // [CEReactions] + // attribute boolean autofocus; + [CEReactions, SetterThrows] + attribute unsigned long cols; + [CEReactions] + attribute DOMString dirName; + [CEReactions] attribute boolean disabled; readonly attribute HTMLFormElement? form; - // attribute DOMString inputMode; - // attribute long maxLength; - // attribute long minLength; + // [CEReactions] + // attribute DOMString inputMode; + [CEReactions, SetterThrows] + attribute long maxLength; + [CEReactions, SetterThrows] + attribute long minLength; + [CEReactions] attribute DOMString name; + [CEReactions] attribute DOMString placeholder; + [CEReactions] attribute boolean readOnly; + [CEReactions] attribute boolean required; - [SetterThrows] + [CEReactions, SetterThrows] attribute unsigned long rows; + [CEReactions] attribute DOMString wrap; readonly attribute DOMString type; + [CEReactions] attribute DOMString defaultValue; - [TreatNullAs=EmptyString] attribute DOMString value; - //readonly attribute unsigned long textLength; + attribute [TreatNullAs=EmptyString] DOMString value; + readonly attribute unsigned long textLength; - //readonly attribute boolean willValidate; - //readonly attribute ValidityState validity; - //readonly attribute DOMString validationMessage; - //boolean checkValidity(); - //boolean reportValidity(); - //void setCustomValidity(DOMString error); + readonly attribute boolean willValidate; + readonly attribute ValidityState validity; + readonly attribute DOMString validationMessage; + boolean checkValidity(); + boolean reportValidity(); + void setCustomValidity(DOMString error); readonly attribute NodeList labels; - //void select(); - attribute unsigned long selectionStart; - attribute unsigned long selectionEnd; - attribute DOMString selectionDirection; - //void setRangeText(DOMString replacement); - //void setRangeText(DOMString replacement, unsigned long start, unsigned long end, - // optional SelectionMode selectionMode = "preserve"); - void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); + void select(); + [SetterThrows] + attribute unsigned long? selectionStart; + [SetterThrows] + attribute unsigned long? selectionEnd; + [SetterThrows] + attribute DOMString? selectionDirection; + [Throws] + void setRangeText(DOMString replacement); + [Throws] + void setRangeText(DOMString replacement, unsigned long start, unsigned long end, + optional SelectionMode selectionMode = "preserve"); + [Throws] + void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); }; diff --git a/components/script/dom/webidls/HTMLTimeElement.webidl b/components/script/dom/webidls/HTMLTimeElement.webidl index 5f2ac73d4cb..27dbf26cb88 100644 --- a/components/script/dom/webidls/HTMLTimeElement.webidl +++ b/components/script/dom/webidls/HTMLTimeElement.webidl @@ -1,8 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltimeelement +[Exposed=Window] interface HTMLTimeElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute DOMString dateTime; }; diff --git a/components/script/dom/webidls/HTMLTitleElement.webidl b/components/script/dom/webidls/HTMLTitleElement.webidl index 10373be7e4b..49fc9e0daf4 100644 --- a/components/script/dom/webidls/HTMLTitleElement.webidl +++ b/components/script/dom/webidls/HTMLTitleElement.webidl @@ -1,9 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltitleelement +[Exposed=Window] interface HTMLTitleElement : HTMLElement { - [Pure] - attribute DOMString text; + [HTMLConstructor] constructor(); + + [CEReactions, Pure] + attribute DOMString text; }; diff --git a/components/script/dom/webidls/HTMLTrackElement.webidl b/components/script/dom/webidls/HTMLTrackElement.webidl index 55733235321..350901cf2e9 100644 --- a/components/script/dom/webidls/HTMLTrackElement.webidl +++ b/components/script/dom/webidls/HTMLTrackElement.webidl @@ -1,20 +1,28 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmltrackelement +[Exposed=Window] interface HTMLTrackElement : HTMLElement { - // attribute DOMString kind; - // attribute DOMString src; - // attribute DOMString srclang; - // attribute DOMString label; - // attribute boolean default; + [HTMLConstructor] constructor(); - //const unsigned short NONE = 0; - //const unsigned short LOADING = 1; - //const unsigned short LOADED = 2; - //const unsigned short ERROR = 3; - //readonly attribute unsigned short readyState; + [CEReactions] + attribute DOMString kind; + [CEReactions] + attribute USVString src; + [CEReactions] + attribute DOMString srclang; + [CEReactions] + attribute DOMString label; + [CEReactions] + attribute boolean default; - //readonly attribute TextTrack track; + const unsigned short NONE = 0; + const unsigned short LOADING = 1; + const unsigned short LOADED = 2; + const unsigned short ERROR = 3; + readonly attribute unsigned short readyState; + + readonly attribute TextTrack track; }; diff --git a/components/script/dom/webidls/HTMLUListElement.webidl b/components/script/dom/webidls/HTMLUListElement.webidl index 91a79c7f925..479e46961c9 100644 --- a/components/script/dom/webidls/HTMLUListElement.webidl +++ b/components/script/dom/webidls/HTMLUListElement.webidl @@ -1,14 +1,19 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlulistelement +[Exposed=Window] interface HTMLUListElement : HTMLElement { + [HTMLConstructor] constructor(); + // also has obsolete members }; // https://html.spec.whatwg.org/multipage/#HTMLUListElement-partial partial interface HTMLUListElement { - // attribute boolean compact; - // attribute DOMString type; + // [CEReactions] + // attribute boolean compact; + // [CEReactions] + // attribute DOMString type; }; diff --git a/components/script/dom/webidls/HTMLUnknownElement.webidl b/components/script/dom/webidls/HTMLUnknownElement.webidl index acf5a47a996..5e26b34468f 100644 --- a/components/script/dom/webidls/HTMLUnknownElement.webidl +++ b/components/script/dom/webidls/HTMLUnknownElement.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://html.spec.whatwg.org/multipage/#htmlunknownelement and @@ -11,5 +11,6 @@ * and create derivative works of this document. */ +[Exposed=Window] interface HTMLUnknownElement : HTMLElement { }; diff --git a/components/script/dom/webidls/HTMLVideoElement.webidl b/components/script/dom/webidls/HTMLVideoElement.webidl index 5e7c9cb9fce..d9e6b86e4bb 100644 --- a/components/script/dom/webidls/HTMLVideoElement.webidl +++ b/components/script/dom/webidls/HTMLVideoElement.webidl @@ -1,12 +1,22 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#htmlvideoelement +[Exposed=Window] interface HTMLVideoElement : HTMLMediaElement { - // attribute unsigned long width; - // attribute unsigned long height; - //readonly attribute unsigned long videoWidth; - //readonly attribute unsigned long videoHeight; - // attribute DOMString poster; + [HTMLConstructor] constructor(); + + // [CEReactions] + // attribute unsigned long width; + // [CEReactions] + // attribute unsigned long height; + readonly attribute unsigned long videoWidth; + readonly attribute unsigned long videoHeight; + [CEReactions] attribute DOMString poster; +}; + +partial interface HTMLVideoElement { + [Pref="media.testing.enabled"] + attribute EventHandler onpostershown; }; diff --git a/components/script/dom/webidls/HashChangeEvent.webidl b/components/script/dom/webidls/HashChangeEvent.webidl index 3d81e8ad383..f225967a138 100644 --- a/components/script/dom/webidls/HashChangeEvent.webidl +++ b/components/script/dom/webidls/HashChangeEvent.webidl @@ -1,11 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#hashchangeevent -[Constructor(DOMString type, optional HashChangeEventInit eventInitDict), - Exposed=Window] +[Exposed=Window] interface HashChangeEvent : Event { + [Throws] constructor(DOMString type, optional HashChangeEventInit eventInitDict = {}); readonly attribute USVString oldURL; readonly attribute USVString newURL; }; diff --git a/components/script/dom/webidls/Headers.webidl b/components/script/dom/webidls/Headers.webidl index 5ae08ad2bd7..dac3d860154 100644 --- a/components/script/dom/webidls/Headers.webidl +++ b/components/script/dom/webidls/Headers.webidl @@ -1,14 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://fetch.spec.whatwg.org/#headers-class -typedef (Headers or sequence<sequence<ByteString>> or MozMap<ByteString>) HeadersInit; +typedef (sequence<sequence<ByteString>> or record<ByteString, ByteString>) HeadersInit; -[Constructor(optional HeadersInit init), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface Headers { + [Throws] constructor(optional HeadersInit init); [Throws] void append(ByteString name, ByteString value); [Throws] diff --git a/components/script/dom/webidls/History.webidl b/components/script/dom/webidls/History.webidl index 56171470877..95248cbcd9b 100644 --- a/components/script/dom/webidls/History.webidl +++ b/components/script/dom/webidls/History.webidl @@ -1,18 +1,26 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // enum ScrollRestoration { "auto", "manual" }; // https://html.spec.whatwg.org/multipage/#the-history-interface [Exposed=(Window,Worker)] interface History { + [Throws] readonly attribute unsigned long length; + // [Throws] // attribute ScrollRestoration scrollRestoration; - // readonly attribute any state; - [Throws] void go(optional long delta = 0); + [Throws] + readonly attribute any state; + [Throws] + void go(optional long delta = 0); + [Throws] void back(); + [Throws] void forward(); - // void pushState(any data, DOMString title, optional USVString? url = null); - // void replaceState(any data, DOMString title, optional USVString? url = null); + [Throws] + void pushState(any data, DOMString title, optional USVString? url = null); + [Throws] + void replaceState(any data, DOMString title, optional USVString? url = null); }; diff --git a/components/script/dom/webidls/ImageBitmap.webidl b/components/script/dom/webidls/ImageBitmap.webidl new file mode 100644 index 00000000000..51c923bfaf4 --- /dev/null +++ b/components/script/dom/webidls/ImageBitmap.webidl @@ -0,0 +1,36 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://html.spec.whatwg.org/multipage/#imagebitmap + * + * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and Opera Software ASA. + * You are granted a license to use, reproduce and create derivative works of this document. + */ + +//[Exposed=(Window,Worker), Serializable, Transferable] +[Exposed=(Window,Worker), Pref="dom.imagebitmap.enabled"] +interface ImageBitmap { + readonly attribute unsigned long width; + readonly attribute unsigned long height; + //void close(); +}; + +typedef (CanvasImageSource or + Blob or + ImageData) ImageBitmapSource; + +enum ImageOrientation { "none", "flipY" }; +enum PremultiplyAlpha { "none", "premultiply", "default" }; +enum ColorSpaceConversion { "none", "default" }; +enum ResizeQuality { "pixelated", "low", "medium", "high" }; + +dictionary ImageBitmapOptions { + ImageOrientation imageOrientation = "none"; + PremultiplyAlpha premultiplyAlpha = "default"; + ColorSpaceConversion colorSpaceConversion = "default"; + [EnforceRange] unsigned long resizeWidth; + [EnforceRange] unsigned long resizeHeight; + ResizeQuality resizeQuality = "low"; +}; diff --git a/components/script/dom/webidls/ImageData.webidl b/components/script/dom/webidls/ImageData.webidl index d327facd5c9..b05e201f42a 100644 --- a/components/script/dom/webidls/ImageData.webidl +++ b/components/script/dom/webidls/ImageData.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://html.spec.whatwg.org/multipage/#imagedata @@ -9,10 +9,10 @@ * You are granted a license to use, reproduce and create derivative works of this document. */ -[Constructor(unsigned long sw, unsigned long sh), - Constructor(/* Uint8ClampedArray */ object data, unsigned long sw, optional unsigned long sh), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface ImageData { + [Throws] constructor(unsigned long sw, unsigned long sh); + [Throws] constructor(/* Uint8ClampedArray */ object data, unsigned long sw, optional unsigned long sh); //[Constant] readonly attribute unsigned long width; //[Constant] diff --git a/components/script/dom/webidls/InputEvent.webidl b/components/script/dom/webidls/InputEvent.webidl new file mode 100644 index 00000000000..58699b643fa --- /dev/null +++ b/components/script/dom/webidls/InputEvent.webidl @@ -0,0 +1,22 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/uievents/#idl-inputevent + * + */ + +// https://w3c.github.io/uievents/#idl-inputevent +[Exposed=Window] +interface InputEvent : UIEvent { + [Throws] constructor(DOMString type, optional InputEventInit eventInitDict = {}); + readonly attribute DOMString? data; + readonly attribute boolean isComposing; +}; + +// https://w3c.github.io/uievents/#idl-inputeventinit +dictionary InputEventInit : UIEventInit { + DOMString? data = null; + boolean isComposing = false; +}; diff --git a/components/script/dom/webidls/IterableIterator.webidl b/components/script/dom/webidls/IterableIterator.webidl index d975aa5645d..25b8aae881e 100644 --- a/components/script/dom/webidls/IterableIterator.webidl +++ b/components/script/dom/webidls/IterableIterator.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This interface is entirely internal to Servo, and should not be accessible to // web pages. diff --git a/components/script/dom/webidls/KeyboardEvent.webidl b/components/script/dom/webidls/KeyboardEvent.webidl index 9426b0da30a..6933a0105ed 100644 --- a/components/script/dom/webidls/KeyboardEvent.webidl +++ b/components/script/dom/webidls/KeyboardEvent.webidl @@ -1,14 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://w3c.github.io/uievents/#interface-keyboardevent * */ -[Constructor(DOMString typeArg, optional KeyboardEventInit keyboardEventInitDict)] +[Exposed=Window] interface KeyboardEvent : UIEvent { + [Throws] constructor(DOMString typeArg, optional KeyboardEventInit keyboardEventInitDict = {}); // KeyLocationCode const unsigned long DOM_KEY_LOCATION_STANDARD = 0x00; const unsigned long DOM_KEY_LOCATION_LEFT = 0x01; diff --git a/components/script/dom/webidls/Location.webidl b/components/script/dom/webidls/Location.webidl index 6413bd3846a..4120fc2731d 100644 --- a/components/script/dom/webidls/Location.webidl +++ b/components/script/dom/webidls/Location.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#location [Exposed=Window, Unforgeable] interface Location { - /*stringifier*/ [Throws] attribute USVString href; + [Throws] stringifier attribute USVString href; [Throws] readonly attribute USVString origin; [Throws] attribute USVString protocol; [Throws] attribute USVString host; @@ -19,9 +19,4 @@ [Throws] void reload(); //[SameObject] readonly attribute USVString[] ancestorOrigins; - - // This is only doing as well as gecko right now. - // https://github.com/servo/servo/issues/7590 is on file for - // adding attribute stringifier support. - [Throws] stringifier; }; diff --git a/components/script/dom/webidls/MediaDeviceInfo.webidl b/components/script/dom/webidls/MediaDeviceInfo.webidl new file mode 100644 index 00000000000..ab24f8046f9 --- /dev/null +++ b/components/script/dom/webidls/MediaDeviceInfo.webidl @@ -0,0 +1,21 @@ +/* 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/. */ + + // https://w3c.github.io/mediacapture-main/#device-info + +[Exposed=Window, +SecureContext, Pref="dom.webrtc.enabled"] +interface MediaDeviceInfo { + readonly attribute DOMString deviceId; + readonly attribute MediaDeviceKind kind; + readonly attribute DOMString label; + readonly attribute DOMString groupId; + [Default] object toJSON(); +}; + +enum MediaDeviceKind { + "audioinput", + "audiooutput", + "videoinput" +}; diff --git a/components/script/dom/webidls/MediaDevices.webidl b/components/script/dom/webidls/MediaDevices.webidl new file mode 100644 index 00000000000..53e6c3599a4 --- /dev/null +++ b/components/script/dom/webidls/MediaDevices.webidl @@ -0,0 +1,86 @@ +/* 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/. */ + +// https://w3c.github.io/mediacapture-main/#dom-mediadevices + +[Exposed=Window, +SecureContext, Pref="dom.webrtc.enabled"] +interface MediaDevices : EventTarget { + // attribute EventHandler ondevicechange; + Promise<sequence<MediaDeviceInfo>> enumerateDevices(); +}; + +partial interface Navigator { + // [SameObject, SecureContext] + [Pref="dom.webrtc.enabled"] readonly attribute MediaDevices mediaDevices; +}; + +partial interface MediaDevices { + // MediaTrackSupportedConstraints getSupportedConstraints(); + Promise<MediaStream> getUserMedia(optional MediaStreamConstraints constraints = {}); +}; + + +dictionary MediaStreamConstraints { + (boolean or MediaTrackConstraints) video = false; + (boolean or MediaTrackConstraints) audio = false; +}; + +dictionary DoubleRange { + double max; + double min; +}; + +dictionary ConstrainDoubleRange : DoubleRange { + double exact; + double ideal; +}; + +dictionary ULongRange { + [Clamp] unsigned long max; + [Clamp] unsigned long min; +}; + +dictionary ConstrainULongRange : ULongRange { + [Clamp] unsigned long exact; + [Clamp] unsigned long ideal; +}; + +// dictionary ConstrainBooleanParameters { +// boolean exact; +// boolean ideal; +// }; + +// dictionary ConstrainDOMStringParameters { +// (DOMString or sequence<DOMString>) exact; +// (DOMString or sequence<DOMString>) ideal; +// }; + +dictionary MediaTrackConstraints : MediaTrackConstraintSet { + sequence<MediaTrackConstraintSet> advanced; +}; + +typedef ([Clamp] unsigned long or ConstrainULongRange) ConstrainULong; +typedef (double or ConstrainDoubleRange) ConstrainDouble; +// typedef (boolean or ConstrainBooleanParameters) ConstrainBoolean; +// typedef (DOMString or sequence<DOMString> or ConstrainDOMStringParameters) ConstrainDOMString; + +dictionary MediaTrackConstraintSet { + ConstrainULong width; + ConstrainULong height; + ConstrainDouble aspectRatio; + ConstrainDouble frameRate; + // ConstrainDOMString facingMode; + // ConstrainDOMString resizeMode; + // ConstrainDouble volume; + ConstrainULong sampleRate; + // ConstrainULong sampleSize; + // ConstrainBoolean echoCancellation; + // ConstrainBoolean autoGainControl; + // ConstrainBoolean noiseSuppression; + // ConstrainDouble latency; + // ConstrainULong channelCount; + // ConstrainDOMString deviceId; + // ConstrainDOMString groupId; +}; diff --git a/components/script/dom/webidls/MediaElementAudioSourceNode.webidl b/components/script/dom/webidls/MediaElementAudioSourceNode.webidl new file mode 100644 index 00000000000..5afe7775caa --- /dev/null +++ b/components/script/dom/webidls/MediaElementAudioSourceNode.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#mediaelementaudiosourcenode + */ + +dictionary MediaElementAudioSourceOptions { + required HTMLMediaElement mediaElement; +}; + +[Exposed=Window] +interface MediaElementAudioSourceNode : AudioNode { + [Throws] constructor (AudioContext context, MediaElementAudioSourceOptions options); + [SameObject] readonly attribute HTMLMediaElement mediaElement; +}; diff --git a/components/script/dom/webidls/MediaError.webidl b/components/script/dom/webidls/MediaError.webidl index ab1966d1095..bff99c786f4 100644 --- a/components/script/dom/webidls/MediaError.webidl +++ b/components/script/dom/webidls/MediaError.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#mediaerror @@ -11,4 +11,5 @@ interface MediaError { const unsigned short MEDIA_ERR_DECODE = 3; const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4; readonly attribute unsigned short code; + readonly attribute DOMString message; }; diff --git a/components/script/dom/webidls/MediaList.webidl b/components/script/dom/webidls/MediaList.webidl index f397badfd52..b2ba10f0d7a 100644 --- a/components/script/dom/webidls/MediaList.webidl +++ b/components/script/dom/webidls/MediaList.webidl @@ -1,11 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#the-medialist-interface -// [LegacyArrayClass] +[Exposed=Window] interface MediaList { - [TreatNullAs=EmptyString] /* stringifier */ attribute DOMString mediaText; + stringifier attribute [TreatNullAs=EmptyString] DOMString mediaText; readonly attribute unsigned long length; getter DOMString? item(unsigned long index); void appendMedium(DOMString medium); diff --git a/components/script/dom/webidls/MediaMetadata.webidl b/components/script/dom/webidls/MediaMetadata.webidl new file mode 100644 index 00000000000..495aeef8e35 --- /dev/null +++ b/components/script/dom/webidls/MediaMetadata.webidl @@ -0,0 +1,30 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/mediasession/#mediametadata + */ + +dictionary MediaImage { + required USVString src; + DOMString sizes = ""; + DOMString type = ""; +}; + +[Exposed=Window] +interface MediaMetadata { + [Throws] constructor(optional MediaMetadataInit init = {}); + attribute DOMString title; + attribute DOMString artist; + attribute DOMString album; + // TODO: https://github.com/servo/servo/issues/10072 + // attribute FrozenArray<MediaImage> artwork; +}; + +dictionary MediaMetadataInit { + DOMString title = ""; + DOMString artist = ""; + DOMString album = ""; + sequence<MediaImage> artwork = []; +}; diff --git a/components/script/dom/webidls/MediaQueryList.webidl b/components/script/dom/webidls/MediaQueryList.webidl index 2991efa89a8..3818382645a 100644 --- a/components/script/dom/webidls/MediaQueryList.webidl +++ b/components/script/dom/webidls/MediaQueryList.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom-view/#mediaquerylist diff --git a/components/script/dom/webidls/MediaQueryListEvent.webidl b/components/script/dom/webidls/MediaQueryListEvent.webidl index 877ca17b60b..f1837ee2750 100644 --- a/components/script/dom/webidls/MediaQueryListEvent.webidl +++ b/components/script/dom/webidls/MediaQueryListEvent.webidl @@ -1,10 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom-view/#dom-mediaquerylistevent-mediaquerylistevent -[Constructor(DOMString type, optional MediaQueryListEventInit eventInitDict), Exposed=(Window)] +[Exposed=(Window)] interface MediaQueryListEvent : Event { + [Throws] constructor(DOMString type, optional MediaQueryListEventInit eventInitDict = {}); readonly attribute DOMString media; readonly attribute boolean matches; }; diff --git a/components/script/dom/webidls/MediaSession.webidl b/components/script/dom/webidls/MediaSession.webidl new file mode 100644 index 00000000000..2680ea0c40b --- /dev/null +++ b/components/script/dom/webidls/MediaSession.webidl @@ -0,0 +1,62 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/mediasession/#mediasession + */ + +[Exposed=Window] +partial interface Navigator { + [SameObject] readonly attribute MediaSession mediaSession; +}; + +enum MediaSessionPlaybackState { + "none", + "paused", + "playing" +}; + +enum MediaSessionAction { + "play", + "pause", + "seekbackward", + "seekforward", + "previoustrack", + "nexttrack", + "skipad", + "stop", + "seekto" +}; + +dictionary MediaSessionActionDetails { + required MediaSessionAction action; +}; + +dictionary MediaSessionSeekActionDetails : MediaSessionActionDetails { + double? seekOffset; +}; + +dictionary MediaSessionSeekToActionDetails : MediaSessionActionDetails { + required double seekTime; + boolean? fastSeek; +}; + +dictionary MediaPositionState { + double duration; + double playbackRate; + double position; +}; + +callback MediaSessionActionHandler = void(/*MediaSessionActionDetails details*/); + +[Exposed=Window] +interface MediaSession { + attribute MediaMetadata? metadata; + + attribute MediaSessionPlaybackState playbackState; + + void setActionHandler(MediaSessionAction action, MediaSessionActionHandler? handler); + + [Throws] void setPositionState(optional MediaPositionState state = {}); +}; diff --git a/components/script/dom/webidls/MediaStream.webidl b/components/script/dom/webidls/MediaStream.webidl new file mode 100644 index 00000000000..3df07a22454 --- /dev/null +++ b/components/script/dom/webidls/MediaStream.webidl @@ -0,0 +1,23 @@ +/* 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/. */ + +// https://w3c.github.io/mediacapture-main/#dom-mediastream + +[Exposed=Window] +interface MediaStream : EventTarget { + [Throws] constructor(); + [Throws] constructor(MediaStream stream); + [Throws] constructor(sequence<MediaStreamTrack> tracks); + // readonly attribute DOMString id; + sequence<MediaStreamTrack> getAudioTracks(); + sequence<MediaStreamTrack> getVideoTracks(); + sequence<MediaStreamTrack> getTracks(); + MediaStreamTrack? getTrackById(DOMString trackId); + void addTrack(MediaStreamTrack track); + void removeTrack(MediaStreamTrack track); + MediaStream clone(); + // readonly attribute boolean active; + // attribute EventHandler onaddtrack; + // attribute EventHandler onremovetrack; +}; diff --git a/components/script/dom/webidls/MediaStreamAudioDestinationNode.webidl b/components/script/dom/webidls/MediaStreamAudioDestinationNode.webidl new file mode 100644 index 00000000000..93d5f86ebc5 --- /dev/null +++ b/components/script/dom/webidls/MediaStreamAudioDestinationNode.webidl @@ -0,0 +1,13 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#mediastreamaudiodestinationnode + */ + +[Exposed=Window] +interface MediaStreamAudioDestinationNode : AudioNode { + [Throws] constructor (AudioContext context, optional AudioNodeOptions options = {}); + readonly attribute MediaStream stream; +}; diff --git a/components/script/dom/webidls/MediaStreamAudioSourceNode.webidl b/components/script/dom/webidls/MediaStreamAudioSourceNode.webidl new file mode 100644 index 00000000000..655e2443081 --- /dev/null +++ b/components/script/dom/webidls/MediaStreamAudioSourceNode.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#mediastreamaudiosourcenode + */ + +dictionary MediaStreamAudioSourceOptions { + required MediaStream mediaStream; +}; + +[Exposed=Window] +interface MediaStreamAudioSourceNode : AudioNode { + [Throws] constructor (AudioContext context, MediaStreamAudioSourceOptions options); + [SameObject] readonly attribute MediaStream mediaStream; +}; diff --git a/components/script/dom/webidls/MediaStreamTrack.webidl b/components/script/dom/webidls/MediaStreamTrack.webidl new file mode 100644 index 00000000000..b514360622a --- /dev/null +++ b/components/script/dom/webidls/MediaStreamTrack.webidl @@ -0,0 +1,24 @@ +/* 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/. */ + +// https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack + +[Exposed=Window] +interface MediaStreamTrack : EventTarget { + readonly attribute DOMString kind; + readonly attribute DOMString id; + // readonly attribute DOMString label; + // attribute boolean enabled; + // readonly attribute boolean muted; + // attribute EventHandler onmute; + // attribute EventHandler onunmute; + // readonly attribute MediaStreamTrackState readyState; + // attribute EventHandler onended; + MediaStreamTrack clone(); + // void stop(); + // MediaTrackCapabilities getCapabilities(); + // MediaTrackConstraints getConstraints(); + // MediaTrackSettings getSettings(); + // Promise<void> applyConstraints(optional MediaTrackConstraints constraints); +}; diff --git a/components/script/dom/webidls/MediaStreamTrackAudioSourceNode.webidl b/components/script/dom/webidls/MediaStreamTrackAudioSourceNode.webidl new file mode 100644 index 00000000000..354b8fa8c85 --- /dev/null +++ b/components/script/dom/webidls/MediaStreamTrackAudioSourceNode.webidl @@ -0,0 +1,16 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#mediastreamtrackaudiosourcenode + */ + +dictionary MediaStreamTrackAudioSourceOptions { + required MediaStreamTrack mediaStreamTrack; +}; + +[Exposed=Window] +interface MediaStreamTrackAudioSourceNode : AudioNode { + [Throws] constructor (AudioContext context, MediaStreamTrackAudioSourceOptions options); +}; diff --git a/components/script/dom/webidls/MessageChannel.webidl b/components/script/dom/webidls/MessageChannel.webidl new file mode 100644 index 00000000000..f48fe643353 --- /dev/null +++ b/components/script/dom/webidls/MessageChannel.webidl @@ -0,0 +1,14 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is: + * https://html.spec.whatwg.org/multipage/#messagechannel + */ + +[Exposed=(Window,Worker)] +interface MessageChannel { + constructor(); + readonly attribute MessagePort port1; + readonly attribute MessagePort port2; +}; diff --git a/components/script/dom/webidls/MessageEvent.webidl b/components/script/dom/webidls/MessageEvent.webidl index 0870f979dac..b37d725b793 100644 --- a/components/script/dom/webidls/MessageEvent.webidl +++ b/components/script/dom/webidls/MessageEvent.webidl @@ -1,15 +1,27 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#messageevent -[Constructor(DOMString type, optional MessageEventInit eventInitDict), Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface MessageEvent : Event { + [Throws] constructor(DOMString type, optional MessageEventInit eventInitDict = {}); readonly attribute any data; readonly attribute DOMString origin; readonly attribute DOMString lastEventId; - //readonly attribute (WindowProxy or MessagePort)? source; - //readonly attribute MessagePort[]? ports; + readonly attribute MessageEventSource? source; + readonly attribute /*FrozenArray<MessagePort>*/any ports; + + void initMessageEvent( + DOMString type, + optional boolean bubbles = false, + optional boolean cancelable = false, + optional any data = null, + optional DOMString origin = "", + optional DOMString lastEventId = "", + optional MessageEventSource? source = null, + optional sequence<MessagePort> ports = [] + ); }; dictionary MessageEventInit : EventInit { @@ -17,6 +29,8 @@ dictionary MessageEventInit : EventInit { DOMString origin = ""; DOMString lastEventId = ""; //DOMString channel; - //(WindowProxy or MessagePort)? source; - //sequence<MessagePort> ports; + MessageEventSource? source = null; + sequence<MessagePort> ports = []; }; + +typedef (WindowProxy or MessagePort or ServiceWorker) MessageEventSource; diff --git a/components/script/dom/webidls/MessagePort.webidl b/components/script/dom/webidls/MessagePort.webidl new file mode 100644 index 00000000000..58a3f06a960 --- /dev/null +++ b/components/script/dom/webidls/MessagePort.webidl @@ -0,0 +1,23 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is: + * https://html.spec.whatwg.org/multipage/#messageport + */ + +[Exposed=(Window,Worker)] +interface MessagePort : EventTarget { + [Throws] void postMessage(any message, sequence<object> transfer); + [Throws] void postMessage(any message, optional PostMessageOptions options = {}); + void start(); + void close(); + + // event handlers + attribute EventHandler onmessage; + attribute EventHandler onmessageerror; +}; + +dictionary PostMessageOptions { + sequence<object> transfer = []; +}; diff --git a/components/script/dom/webidls/MimeType.webidl b/components/script/dom/webidls/MimeType.webidl index 2f9ef726994..0f971a858cc 100644 --- a/components/script/dom/webidls/MimeType.webidl +++ b/components/script/dom/webidls/MimeType.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#mimetype [Exposed=Window] diff --git a/components/script/dom/webidls/MimeTypeArray.webidl b/components/script/dom/webidls/MimeTypeArray.webidl index b66cef1b594..c7f724c5fbf 100644 --- a/components/script/dom/webidls/MimeTypeArray.webidl +++ b/components/script/dom/webidls/MimeTypeArray.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#mimetypearray [LegacyUnenumerableNamedProperties, Exposed=Window] diff --git a/components/script/dom/webidls/MouseEvent.webidl b/components/script/dom/webidls/MouseEvent.webidl index e9f7a31db48..0b7cb644368 100644 --- a/components/script/dom/webidls/MouseEvent.webidl +++ b/components/script/dom/webidls/MouseEvent.webidl @@ -1,15 +1,21 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/uievents/#interface-mouseevent -[Constructor(DOMString typeArg, optional MouseEventInit mouseEventInitDict), - Exposed=Window] +[Exposed=Window] interface MouseEvent : UIEvent { + [Throws] constructor(DOMString typeArg, optional MouseEventInit mouseEventInitDict = {}); readonly attribute long screenX; readonly attribute long screenY; readonly attribute long clientX; readonly attribute long clientY; + readonly attribute long pageX; + readonly attribute long pageY; + readonly attribute long x; + readonly attribute long y; + readonly attribute long offsetX; + readonly attribute long offsetY; readonly attribute boolean ctrlKey; readonly attribute boolean shiftKey; readonly attribute boolean altKey; @@ -17,7 +23,7 @@ interface MouseEvent : UIEvent { readonly attribute short button; readonly attribute EventTarget? relatedTarget; // Introduced in DOM Level 3 - //readonly attribute unsigned short buttons; + readonly attribute unsigned short buttons; //boolean getModifierState (DOMString keyArg); [Pref="dom.mouseevent.which.enabled"] @@ -31,7 +37,7 @@ dictionary MouseEventInit : EventModifierInit { long clientX = 0; long clientY = 0; short button = 0; - //unsigned short buttons = 0; + unsigned short buttons = 0; EventTarget? relatedTarget = null; }; diff --git a/components/script/dom/webidls/MutationObserver.webidl b/components/script/dom/webidls/MutationObserver.webidl index dbcfa945d4a..7d75c5ed0c4 100644 --- a/components/script/dom/webidls/MutationObserver.webidl +++ b/components/script/dom/webidls/MutationObserver.webidl @@ -1,17 +1,19 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is: * https://dom.spec.whatwg.org/#mutationobserver */ // https://dom.spec.whatwg.org/#mutationobserver -[Pref="dom.mutation_observer.enabled", Constructor(MutationCallback callback)] +[Exposed=Window, Pref="dom.mutation_observer.enabled"] interface MutationObserver { - //void observe(Node target, optional MutationObserverInit options); - //void disconnect(); - //sequence<MutationRecord> takeRecords(); + [Throws] constructor(MutationCallback callback); + [Throws] + void observe(Node target, optional MutationObserverInit options = {}); + void disconnect(); + sequence<MutationRecord> takeRecords(); }; callback MutationCallback = void (sequence<MutationRecord> mutations, MutationObserver observer); diff --git a/components/script/dom/webidls/MutationRecord.webidl b/components/script/dom/webidls/MutationRecord.webidl index 286801bf2ab..2c08fcea71f 100644 --- a/components/script/dom/webidls/MutationRecord.webidl +++ b/components/script/dom/webidls/MutationRecord.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is: * https://dom.spec.whatwg.org/#mutationrecord @@ -12,13 +12,13 @@ interface MutationRecord { readonly attribute DOMString type; [SameObject] readonly attribute Node target; - //[SameObject] - //readonly attribute NodeList addedNodes; - //[SameObject] - //readonly attribute NodeList removedNodes; - //readonly attribute Node? previousSibling; - //readonly attribute Node? nextSibling; - //readonly attribute DOMString? attributeName; - //readonly attribute DOMString? attributeNamespace; - //readonly attribute DOMString? oldValue; + [SameObject] + readonly attribute NodeList addedNodes; + [SameObject] + readonly attribute NodeList removedNodes; + readonly attribute Node? previousSibling; + readonly attribute Node? nextSibling; + readonly attribute DOMString? attributeName; + readonly attribute DOMString? attributeNamespace; + readonly attribute DOMString? oldValue; }; diff --git a/components/script/dom/webidls/NamedNodeMap.webidl b/components/script/dom/webidls/NamedNodeMap.webidl index 66156943b08..15adeac6852 100644 --- a/components/script/dom/webidls/NamedNodeMap.webidl +++ b/components/script/dom/webidls/NamedNodeMap.webidl @@ -1,10 +1,10 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://dom.spec.whatwg.org/#interface-namednodemap -[LegacyUnenumerableNamedProperties] +[Exposed=Window, LegacyUnenumerableNamedProperties] interface NamedNodeMap { [Pure] readonly attribute unsigned long length; @@ -14,12 +14,12 @@ interface NamedNodeMap { getter Attr? getNamedItem(DOMString qualifiedName); [Pure] Attr? getNamedItemNS(DOMString? namespace, DOMString localName); - [Throws] + [CEReactions, Throws] Attr? setNamedItem(Attr attr); - [Throws] + [CEReactions, Throws] Attr? setNamedItemNS(Attr attr); - [Throws] + [CEReactions, Throws] Attr removeNamedItem(DOMString qualifiedName); - [Throws] + [CEReactions, Throws] Attr removeNamedItemNS(DOMString? namespace, DOMString localName); }; diff --git a/components/script/dom/webidls/NavigationPreloadManager.webidl b/components/script/dom/webidls/NavigationPreloadManager.webidl new file mode 100644 index 00000000000..ba8d329769e --- /dev/null +++ b/components/script/dom/webidls/NavigationPreloadManager.webidl @@ -0,0 +1,17 @@ +/* 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/. */ + +// https://w3c.github.io/ServiceWorker/#navigation-preload-manager +[Pref="dom.serviceworker.enabled", SecureContext, Exposed=(Window,Worker)] +interface NavigationPreloadManager { + Promise<void> enable(); + Promise<void> disable(); + Promise<void> setHeaderValue(ByteString value); + Promise<NavigationPreloadState> getState(); +}; + +dictionary NavigationPreloadState { + boolean enabled = false; + ByteString headerValue; +}; diff --git a/components/script/dom/webidls/Navigator.webidl b/components/script/dom/webidls/Navigator.webidl index 338753d6261..4ebb5ad20be 100644 --- a/components/script/dom/webidls/Navigator.webidl +++ b/components/script/dom/webidls/Navigator.webidl @@ -1,29 +1,33 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#navigator +[Exposed=Window] interface Navigator { // objects implementing this interface also implement the interfaces given below }; -Navigator implements NavigatorID; -Navigator implements NavigatorLanguage; -//Navigator implements NavigatorOnLine; -//Navigator implements NavigatorContentUtils; -//Navigator implements NavigatorStorageUtils; -Navigator implements NavigatorPlugins; -Navigator implements NavigatorCookies; +Navigator includes NavigatorID; +Navigator includes NavigatorLanguage; +//Navigator includes NavigatorOnLine; +//Navigator includes NavigatorContentUtils; +//Navigator includes NavigatorStorageUtils; +Navigator includes NavigatorPlugins; +Navigator includes NavigatorCookies; // https://html.spec.whatwg.org/multipage/#navigatorid -[NoInterfaceObject, Exposed=(Window,Worker)] -interface NavigatorID { +[Exposed=(Window,Worker)] +interface mixin NavigatorID { readonly attribute DOMString appCodeName; // constant "Mozilla" readonly attribute DOMString appName; readonly attribute DOMString appVersion; readonly attribute DOMString platform; readonly attribute DOMString product; // constant "Gecko" + [Exposed=Window] readonly attribute DOMString productSub; boolean taintEnabled(); // constant false readonly attribute DOMString userAgent; + [Exposed=Window] readonly attribute DOMString vendor; + [Exposed=Window] readonly attribute DOMString vendorSub; // constant "" }; // https://webbluetoothcg.github.io/web-bluetooth/#navigator-extensions @@ -37,32 +41,24 @@ partial interface Navigator { }; // https://html.spec.whatwg.org/multipage/#navigatorlanguage -[NoInterfaceObject, Exposed=(Window,Worker)] -interface NavigatorLanguage { +[Exposed=(Window,Worker)] +interface mixin NavigatorLanguage { readonly attribute DOMString language; - // https://github.com/servo/servo/issues/10073 - //readonly attribute DOMString[] languages; + readonly attribute any languages; }; // https://html.spec.whatwg.org/multipage/#navigatorplugins -[NoInterfaceObject] -interface NavigatorPlugins { +interface mixin NavigatorPlugins { [SameObject] readonly attribute PluginArray plugins; [SameObject] readonly attribute MimeTypeArray mimeTypes; boolean javaEnabled(); }; // https://html.spec.whatwg.org/multipage/#navigatorcookies -[NoInterfaceObject] -interface NavigatorCookies { +interface mixin NavigatorCookies { readonly attribute boolean cookieEnabled; }; -// https://w3c.github.io/webvr/#interface-navigator -partial interface Navigator { - [SameObject, Pref="dom.webvr.enabled"] readonly attribute VR vr; -}; - // https://w3c.github.io/permissions/#navigator-and-workernavigator-extension [Exposed=(Window)] partial interface Navigator { @@ -73,3 +69,8 @@ partial interface Navigator { partial interface Navigator { [Pref="dom.gamepad.enabled"] GamepadList getGamepads(); }; + +[Exposed=Window] +partial interface Navigator { + [SameObject, Pref="dom.webgpu.enabled"] readonly attribute GPU gpu; +}; diff --git a/components/script/dom/webidls/Node.webidl b/components/script/dom/webidls/Node.webidl index 6108de01d90..c8a71dbebb7 100644 --- a/components/script/dom/webidls/Node.webidl +++ b/components/script/dom/webidls/Node.webidl @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is: * https://dom.spec.whatwg.org/#interface-node */ -[Abstract] +[Exposed=Window, Abstract] interface Node : EventTarget { const unsigned short ELEMENT_NODE = 1; const unsigned short ATTRIBUTE_NODE = 2; // historical @@ -29,10 +29,13 @@ interface Node : EventTarget { readonly attribute USVString baseURI; [Pure] + readonly attribute boolean isConnected; + + [Pure] readonly attribute Document? ownerDocument; [Pure] - Node getRootNode(); + Node getRootNode(optional GetRootNodeOptions options = {}); [Pure] readonly attribute Node? parentNode; @@ -51,12 +54,14 @@ interface Node : EventTarget { [Pure] readonly attribute Node? nextSibling; - [Pure] + [CEReactions, Pure] attribute DOMString? nodeValue; - [Pure] + [CEReactions, Pure] attribute DOMString? textContent; + [CEReactions] void normalize(); + [CEReactions, Throws] Node cloneNode(optional boolean deep = false); [Pure] boolean isEqualNode(Node? node); @@ -81,12 +86,16 @@ interface Node : EventTarget { [Pure] boolean isDefaultNamespace(DOMString? namespace); - [Throws] + [CEReactions, Throws] Node insertBefore(Node node, Node? child); - [Throws] + [CEReactions, Throws] Node appendChild(Node node); - [Throws] + [CEReactions, Throws] Node replaceChild(Node node, Node child); - [Throws] + [CEReactions, Throws] Node removeChild(Node child); }; + +dictionary GetRootNodeOptions { + boolean composed = false; +}; diff --git a/components/script/dom/webidls/NodeFilter.webidl b/components/script/dom/webidls/NodeFilter.webidl index 79d059e393e..95e718ff771 100644 --- a/components/script/dom/webidls/NodeFilter.webidl +++ b/components/script/dom/webidls/NodeFilter.webidl @@ -1,12 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#interface-nodefilter */ // Import from http://hg.mozilla.org/mozilla-central/file/a5a720259d79/dom/webidl/NodeFilter.webidl +[Exposed=Window] callback interface NodeFilter { // Constants for acceptNode() const unsigned short FILTER_ACCEPT = 1; diff --git a/components/script/dom/webidls/NodeIterator.webidl b/components/script/dom/webidls/NodeIterator.webidl index 636e7ed2943..9c9e1f41e80 100644 --- a/components/script/dom/webidls/NodeIterator.webidl +++ b/components/script/dom/webidls/NodeIterator.webidl @@ -1,11 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * https://dom.spec.whatwg.org/#nodeiterator */ // Import from http://hg.mozilla.org/mozilla-central/raw-file/a5a720259d79/dom/webidl/NodeIterator.webidl +[Exposed=Window] interface NodeIterator { [SameObject] readonly attribute Node root; diff --git a/components/script/dom/webidls/NodeList.webidl b/components/script/dom/webidls/NodeList.webidl index 2f959aed6e3..8ce65efb802 100644 --- a/components/script/dom/webidls/NodeList.webidl +++ b/components/script/dom/webidls/NodeList.webidl @@ -1,11 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is: * https://dom.spec.whatwg.org/#interface-nodelist */ +[Exposed=Window] interface NodeList { [Pure] getter Node? item(unsigned long index); diff --git a/components/script/dom/webidls/NonElementParentNode.webidl b/components/script/dom/webidls/NonElementParentNode.webidl index 02cc35cbe18..2d2c18c368e 100644 --- a/components/script/dom/webidls/NonElementParentNode.webidl +++ b/components/script/dom/webidls/NonElementParentNode.webidl @@ -1,10 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://dom.spec.whatwg.org/#nonelementparentnode -[NoInterfaceObject] -interface NonElementParentNode { +interface mixin NonElementParentNode { [Pure] Element? getElementById(DOMString elementId); }; diff --git a/components/script/dom/webidls/OESElementIndexUint.webidl b/components/script/dom/webidls/OESElementIndexUint.webidl new file mode 100644 index 00000000000..01a441e9946 --- /dev/null +++ b/components/script/dom/webidls/OESElementIndexUint.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/OES_element_index_uint/ + */ + +[NoInterfaceObject, Exposed=Window] +interface OESElementIndexUint { +}; diff --git a/components/script/dom/webidls/OESStandardDerivatives.webidl b/components/script/dom/webidls/OESStandardDerivatives.webidl new file mode 100644 index 00000000000..0e4c51e4df3 --- /dev/null +++ b/components/script/dom/webidls/OESStandardDerivatives.webidl @@ -0,0 +1,12 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/OES_standard_derivatives/ + */ + +[NoInterfaceObject, Exposed=Window] +interface OESStandardDerivatives { + const GLenum FRAGMENT_SHADER_DERIVATIVE_HINT_OES = 0x8B8B; +}; diff --git a/components/script/dom/webidls/OESTextureFloat.webidl b/components/script/dom/webidls/OESTextureFloat.webidl new file mode 100644 index 00000000000..f053a405977 --- /dev/null +++ b/components/script/dom/webidls/OESTextureFloat.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/OES_texture_float/ + */ + +[NoInterfaceObject, Exposed=Window] +interface OESTextureFloat { +}; diff --git a/components/script/dom/webidls/OESTextureFloatLinear.webidl b/components/script/dom/webidls/OESTextureFloatLinear.webidl new file mode 100644 index 00000000000..f0abf5a1aac --- /dev/null +++ b/components/script/dom/webidls/OESTextureFloatLinear.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/ + */ + +[NoInterfaceObject, Exposed=Window] +interface OESTextureFloatLinear { +}; diff --git a/components/script/dom/webidls/OESTextureHalfFloat.webidl b/components/script/dom/webidls/OESTextureHalfFloat.webidl new file mode 100644 index 00000000000..cba71c8cb56 --- /dev/null +++ b/components/script/dom/webidls/OESTextureHalfFloat.webidl @@ -0,0 +1,12 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/OES_texture_half_float/ + */ + +[NoInterfaceObject, Exposed=Window] +interface OESTextureHalfFloat { + const GLenum HALF_FLOAT_OES = 0x8D61; +}; diff --git a/components/script/dom/webidls/OESTextureHalfFloatLinear.webidl b/components/script/dom/webidls/OESTextureHalfFloatLinear.webidl new file mode 100644 index 00000000000..61454dd9cdb --- /dev/null +++ b/components/script/dom/webidls/OESTextureHalfFloatLinear.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/OES_texture_half_float_linear/ + */ + +[NoInterfaceObject, Exposed=Window] +interface OESTextureHalfFloatLinear { +}; diff --git a/components/script/dom/webidls/OESVertexArrayObject.webidl b/components/script/dom/webidls/OESVertexArrayObject.webidl new file mode 100644 index 00000000000..21c59fd7a88 --- /dev/null +++ b/components/script/dom/webidls/OESVertexArrayObject.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/ + */ + +[NoInterfaceObject, Exposed=Window] +interface OESVertexArrayObject { + const unsigned long VERTEX_ARRAY_BINDING_OES = 0x85B5; + + WebGLVertexArrayObjectOES? createVertexArrayOES(); + void deleteVertexArrayOES(WebGLVertexArrayObjectOES? arrayObject); + boolean isVertexArrayOES(WebGLVertexArrayObjectOES? arrayObject); + void bindVertexArrayOES(WebGLVertexArrayObjectOES? arrayObject); +}; diff --git a/components/script/dom/webidls/OfflineAudioCompletionEvent.webidl b/components/script/dom/webidls/OfflineAudioCompletionEvent.webidl new file mode 100644 index 00000000000..aa63fe22156 --- /dev/null +++ b/components/script/dom/webidls/OfflineAudioCompletionEvent.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * For more information on this interface please see + * https://webaudio.github.io/web-audio-api/#offlineaudiocompletionevent + */ + +dictionary OfflineAudioCompletionEventInit : EventInit { + required AudioBuffer renderedBuffer; +}; + +[Exposed=Window] +interface OfflineAudioCompletionEvent : Event { + [Throws] constructor(DOMString type, OfflineAudioCompletionEventInit eventInitDict); + readonly attribute AudioBuffer renderedBuffer; +}; diff --git a/components/script/dom/webidls/OfflineAudioContext.webidl b/components/script/dom/webidls/OfflineAudioContext.webidl new file mode 100644 index 00000000000..ff2d923a096 --- /dev/null +++ b/components/script/dom/webidls/OfflineAudioContext.webidl @@ -0,0 +1,24 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#OfflineAudioContext + */ + +dictionary OfflineAudioContextOptions { + unsigned long numberOfChannels = 1; + required unsigned long length; + required float sampleRate; +}; + +[Exposed=Window] +interface OfflineAudioContext : BaseAudioContext { + [Throws] constructor(OfflineAudioContextOptions contextOptions); + [Throws] constructor(unsigned long numberOfChannels, unsigned long length, float sampleRate); + readonly attribute unsigned long length; + attribute EventHandler oncomplete; + + Promise<AudioBuffer> startRendering(); +// Promise<void> suspend(double suspendTime); +}; diff --git a/components/script/dom/webidls/OffscreenCanvas.webidl b/components/script/dom/webidls/OffscreenCanvas.webidl new file mode 100644 index 00000000000..8bb87b178f9 --- /dev/null +++ b/components/script/dom/webidls/OffscreenCanvas.webidl @@ -0,0 +1,25 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#the-offscreencanvas-interface +typedef (OffscreenCanvasRenderingContext2D or WebGLRenderingContext or WebGL2RenderingContext) +OffscreenRenderingContext; + +dictionary ImageEncodeOptions { + DOMString type = "image/png"; + unrestricted double quality = 1.0; +}; + +//enum OffscreenRenderingContextId { "2d", "webgl", "webgl2" }; + +[Exposed=(Window,Worker)/*, Transferable*/, Pref="dom.offscreen_canvas.enabled"] +interface OffscreenCanvas : EventTarget { + [Throws] constructor([EnforceRange] unsigned long long width, [EnforceRange] unsigned long long height); + attribute /*[EnforceRange]*/ unsigned long long width; + attribute /*[EnforceRange]*/ unsigned long long height; + + OffscreenRenderingContext? getContext(DOMString contextId, optional any options = null); + //ImageBitmap transferToImageBitmap(); + //Promise<Blob> convertToBlob(optional ImageEncodeOptions options); +}; diff --git a/components/script/dom/webidls/OffscreenCanvasRenderingContext2D.webidl b/components/script/dom/webidls/OffscreenCanvasRenderingContext2D.webidl new file mode 100644 index 00000000000..c129bfa8e47 --- /dev/null +++ b/components/script/dom/webidls/OffscreenCanvasRenderingContext2D.webidl @@ -0,0 +1,30 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#the-offscreen-2d-rendering-context +[Exposed=(Window,Worker), Pref="dom.offscreen_canvas.enabled"] +interface OffscreenCanvasRenderingContext2D { + //void commit(); + readonly attribute OffscreenCanvas canvas; +}; +OffscreenCanvasRenderingContext2D includes CanvasState; +OffscreenCanvasRenderingContext2D includes CanvasCompositing; +OffscreenCanvasRenderingContext2D includes CanvasImageSmoothing; +OffscreenCanvasRenderingContext2D includes CanvasFillStrokeStyles; +OffscreenCanvasRenderingContext2D includes CanvasShadowStyles; +OffscreenCanvasRenderingContext2D includes CanvasFilters; +OffscreenCanvasRenderingContext2D includes CanvasRect; + +OffscreenCanvasRenderingContext2D includes CanvasTransform; +OffscreenCanvasRenderingContext2D includes CanvasDrawPath; +OffscreenCanvasRenderingContext2D includes CanvasText; +OffscreenCanvasRenderingContext2D includes CanvasDrawImage; +OffscreenCanvasRenderingContext2D includes CanvasImageData; +OffscreenCanvasRenderingContext2D includes CanvasPathDrawingStyles; +OffscreenCanvasRenderingContext2D includes CanvasTextDrawingStyles; +OffscreenCanvasRenderingContext2D includes CanvasPath; + + + + diff --git a/components/script/dom/webidls/OscillatorNode.webidl b/components/script/dom/webidls/OscillatorNode.webidl new file mode 100644 index 00000000000..f2b4c215561 --- /dev/null +++ b/components/script/dom/webidls/OscillatorNode.webidl @@ -0,0 +1,34 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#oscillatornode + */ + +enum OscillatorType { + "sine", + "square", + "sawtooth", + "triangle", + "custom" +}; + +dictionary OscillatorOptions : AudioNodeOptions { + OscillatorType type = "sine"; + float frequency = 440; + float detune = 0; + // PeriodicWave periodicWave; +}; + +[Exposed=Window] +interface OscillatorNode : AudioScheduledSourceNode { + [Throws] constructor(BaseAudioContext context, optional OscillatorOptions options = {}); + [SetterThrows] + attribute OscillatorType type; + + readonly attribute AudioParam frequency; + readonly attribute AudioParam detune; + +// void setPeriodicWave (PeriodicWave periodicWave); +}; diff --git a/components/script/dom/webidls/PageTransitionEvent.webidl b/components/script/dom/webidls/PageTransitionEvent.webidl index a23f3099bad..000818f1a30 100644 --- a/components/script/dom/webidls/PageTransitionEvent.webidl +++ b/components/script/dom/webidls/PageTransitionEvent.webidl @@ -1,11 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#the-pagetransitionevent-interface -[Constructor(DOMString type, optional PageTransitionEventInit eventInitDict), - Exposed=Window] +[Exposed=Window] interface PageTransitionEvent : Event { + [Throws] constructor(DOMString type, optional PageTransitionEventInit eventInitDict = {}); readonly attribute boolean persisted; }; diff --git a/components/script/dom/webidls/PaintRenderingContext2D.webidl b/components/script/dom/webidls/PaintRenderingContext2D.webidl new file mode 100644 index 00000000000..bc415450358 --- /dev/null +++ b/components/script/dom/webidls/PaintRenderingContext2D.webidl @@ -0,0 +1,19 @@ +/* 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/. */ + +// https://drafts.css-houdini.org/css-paint-api/#paintrenderingcontext2d +[Pref="dom.worklet.enabled", Exposed=PaintWorklet] +interface PaintRenderingContext2D { +}; +PaintRenderingContext2D includes CanvasState; +PaintRenderingContext2D includes CanvasTransform; +PaintRenderingContext2D includes CanvasCompositing; +PaintRenderingContext2D includes CanvasImageSmoothing; +PaintRenderingContext2D includes CanvasFillStrokeStyles; +PaintRenderingContext2D includes CanvasShadowStyles; +PaintRenderingContext2D includes CanvasRect; +PaintRenderingContext2D includes CanvasDrawPath; +PaintRenderingContext2D includes CanvasDrawImage; +PaintRenderingContext2D includes CanvasPathDrawingStyles; +PaintRenderingContext2D includes CanvasPath; diff --git a/components/script/dom/webidls/PaintSize.webidl b/components/script/dom/webidls/PaintSize.webidl new file mode 100644 index 00000000000..ed1b46d29f4 --- /dev/null +++ b/components/script/dom/webidls/PaintSize.webidl @@ -0,0 +1,10 @@ +/* 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/. */ + +// https://drafts.css-houdini.org/css-paint-api/#paintsize +[Pref="dom.worklet.enabled", Exposed=PaintWorklet] +interface PaintSize { + readonly attribute double width; + readonly attribute double height; +}; diff --git a/components/script/dom/webidls/PaintWorkletGlobalScope.webidl b/components/script/dom/webidls/PaintWorkletGlobalScope.webidl new file mode 100644 index 00000000000..b80bf92dce5 --- /dev/null +++ b/components/script/dom/webidls/PaintWorkletGlobalScope.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope +[Global=(Worklet,PaintWorklet), Pref="dom.worklet.enabled", Exposed=PaintWorklet] +interface PaintWorkletGlobalScope : WorkletGlobalScope { + [Throws] void registerPaint(DOMString name, VoidFunction paintCtor); + // This function is to be used only for testing, and should not be + // accessible outside of that use. + [Pref="dom.worklet.blockingsleep.enabled"] + void sleep(unsigned long long ms); +}; diff --git a/components/script/dom/webidls/PannerNode.webidl b/components/script/dom/webidls/PannerNode.webidl new file mode 100644 index 00000000000..c692a97e278 --- /dev/null +++ b/components/script/dom/webidls/PannerNode.webidl @@ -0,0 +1,56 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#pannernode + */ + +dictionary PannerOptions : AudioNodeOptions { + PanningModelType panningModel = "equalpower"; + DistanceModelType distanceModel = "inverse"; + float positionX = 0; + float positionY = 0; + float positionZ = 0; + float orientationX = 1; + float orientationY = 0; + float orientationZ = 0; + double refDistance = 1; + double maxDistance = 10000; + double rolloffFactor = 1; + double coneInnerAngle = 360; + double coneOuterAngle = 360; + double coneOuterGain = 0; +}; + +enum DistanceModelType { + "linear", + "inverse", + "exponential" +}; + +enum PanningModelType { + "equalpower", + "HRTF" +}; + +[Exposed=Window] +interface PannerNode : AudioNode { + [Throws] constructor(BaseAudioContext context, optional PannerOptions options = {}); + attribute PanningModelType panningModel; + readonly attribute AudioParam positionX; + readonly attribute AudioParam positionY; + readonly attribute AudioParam positionZ; + readonly attribute AudioParam orientationX; + readonly attribute AudioParam orientationY; + readonly attribute AudioParam orientationZ; + attribute DistanceModelType distanceModel; + [SetterThrows] attribute double refDistance; + [SetterThrows] attribute double maxDistance; + [SetterThrows] attribute double rolloffFactor; + attribute double coneInnerAngle; + attribute double coneOuterAngle; + [SetterThrows] attribute double coneOuterGain; + void setPosition (float x, float y, float z); + void setOrientation (float x, float y, float z); +}; diff --git a/components/script/dom/webidls/ParentNode.webidl b/components/script/dom/webidls/ParentNode.webidl index 84da03e3643..a7bcc64cd23 100644 --- a/components/script/dom/webidls/ParentNode.webidl +++ b/components/script/dom/webidls/ParentNode.webidl @@ -1,13 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#interface-parentnode */ -[NoInterfaceObject] -interface ParentNode { +interface mixin ParentNode { [SameObject] readonly attribute HTMLCollection children; [Pure] @@ -17,10 +16,12 @@ interface ParentNode { [Pure] readonly attribute unsigned long childElementCount; - [Throws, Unscopable] + [CEReactions, Throws, Unscopable] void prepend((Node or DOMString)... nodes); - [Throws, Unscopable] + [CEReactions, Throws, Unscopable] void append((Node or DOMString)... nodes); + [CEReactions, Throws, Unscopable] + void replaceChildren((Node or DOMString)... nodes); [Pure, Throws] Element? querySelector(DOMString selectors); diff --git a/components/script/dom/webidls/Performance.webidl b/components/script/dom/webidls/Performance.webidl index 6aeaa6d11aa..5db6551ff96 100644 --- a/components/script/dom/webidls/Performance.webidl +++ b/components/script/dom/webidls/Performance.webidl @@ -1,19 +1,53 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is - * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-window.performance-attribute + * https://w3c.github.io/hr-time/#sec-performance */ typedef double DOMHighResTimeStamp; +typedef sequence<PerformanceEntry> PerformanceEntryList; +[Exposed=(Window, Worker)] +interface Performance : EventTarget { + DOMHighResTimeStamp now(); + readonly attribute DOMHighResTimeStamp timeOrigin; + [Default] object toJSON(); +}; + +// https://w3c.github.io/performance-timeline/#extensions-to-the-performance-interface +[Exposed=(Window, Worker)] +partial interface Performance { + PerformanceEntryList getEntries(); + PerformanceEntryList getEntriesByType(DOMString type); + PerformanceEntryList getEntriesByName(DOMString name, + optional DOMString type); +}; + +// https://w3c.github.io/user-timing/#extensions-performance-interface [Exposed=(Window,Worker)] -interface Performance { - readonly attribute PerformanceTiming timing; - /* readonly attribute PerformanceNavigation navigation; */ +partial interface Performance { + [Throws] + void mark(DOMString markName); + void clearMarks(optional DOMString markName); + [Throws] + void measure(DOMString measureName, optional DOMString startMark, optional DOMString endMark); + void clearMeasures(optional DOMString measureName); }; +//https://w3c.github.io/resource-timing/#sec-extensions-performance-interface partial interface Performance { - DOMHighResTimeStamp now(); + void clearResourceTimings (); + void setResourceTimingBufferSize (unsigned long maxSize); + attribute EventHandler onresourcetimingbufferfull; +}; + +// https://w3c.github.io/navigation-timing/#extensions-to-the-performance-interface +[Exposed=Window] +partial interface Performance { + [SameObject] + readonly attribute PerformanceNavigationTiming timing; + [SameObject] + readonly attribute PerformanceNavigation navigation; }; diff --git a/components/script/dom/webidls/PerformanceEntry.webidl b/components/script/dom/webidls/PerformanceEntry.webidl new file mode 100644 index 00000000000..5f6fee1cabd --- /dev/null +++ b/components/script/dom/webidls/PerformanceEntry.webidl @@ -0,0 +1,16 @@ +/* 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/. + * + * The origin of this IDL file is + * https://w3c.github.io/performance-timeline/#the-performanceentry-interface + */ + +[Exposed=(Window,Worker)] +interface PerformanceEntry { + readonly attribute DOMString name; + readonly attribute DOMString entryType; + readonly attribute DOMHighResTimeStamp startTime; + readonly attribute DOMHighResTimeStamp duration; + [Default] object toJSON(); +}; diff --git a/components/script/dom/webidls/PerformanceMark.webidl b/components/script/dom/webidls/PerformanceMark.webidl new file mode 100644 index 00000000000..43783454865 --- /dev/null +++ b/components/script/dom/webidls/PerformanceMark.webidl @@ -0,0 +1,11 @@ +/* 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/. + * + * The origin of this IDL file is + * https://w3c.github.io/user-timing/#performancemark + */ + +[Exposed=(Window,Worker)] +interface PerformanceMark : PerformanceEntry { +}; diff --git a/components/script/dom/webidls/PerformanceMeasure.webidl b/components/script/dom/webidls/PerformanceMeasure.webidl new file mode 100644 index 00000000000..8a1469fa68d --- /dev/null +++ b/components/script/dom/webidls/PerformanceMeasure.webidl @@ -0,0 +1,11 @@ +/* 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/. + * + * The origin of this IDL file is + * https://w3c.github.io/user-timing/#performancemeasure + */ + +[Exposed=(Window,Worker)] +interface PerformanceMeasure : PerformanceEntry { +}; diff --git a/components/script/dom/webidls/PerformanceNavigation.webidl b/components/script/dom/webidls/PerformanceNavigation.webidl new file mode 100644 index 00000000000..3e5cba196f6 --- /dev/null +++ b/components/script/dom/webidls/PerformanceNavigation.webidl @@ -0,0 +1,18 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/navigation-timing/#the-performancenavigation-interface + */ + +[Exposed=Window] +interface PerformanceNavigation { + const unsigned short TYPE_NAVIGATE = 0; + const unsigned short TYPE_RELOAD = 1; + const unsigned short TYPE_BACK_FORWARD = 2; + const unsigned short TYPE_RESERVED = 255; + readonly attribute unsigned short type; + readonly attribute unsigned short redirectCount; + [Default] object toJSON(); +}; diff --git a/components/script/dom/webidls/PerformanceNavigationTiming.webidl b/components/script/dom/webidls/PerformanceNavigationTiming.webidl new file mode 100644 index 00000000000..5e369b4eb91 --- /dev/null +++ b/components/script/dom/webidls/PerformanceNavigationTiming.webidl @@ -0,0 +1,32 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/navigation-timing/#dom-performancenavigationtiming + */ + +enum NavigationType { + "navigate", + "reload", + "back_forward", + "prerender" +}; + +[Exposed=Window] +interface PerformanceNavigationTiming : PerformanceResourceTiming { + readonly attribute DOMHighResTimeStamp unloadEventStart; + readonly attribute DOMHighResTimeStamp unloadEventEnd; + readonly attribute DOMHighResTimeStamp domInteractive; + readonly attribute DOMHighResTimeStamp domContentLoadedEventStart; + readonly attribute DOMHighResTimeStamp domContentLoadedEventEnd; + readonly attribute DOMHighResTimeStamp domComplete; + readonly attribute DOMHighResTimeStamp loadEventStart; + readonly attribute DOMHighResTimeStamp loadEventEnd; + readonly attribute NavigationType type; + readonly attribute unsigned short redirectCount; + [Default] object toJSON(); + /* Servo-only attribute for measuring when the top-level document (not iframes) is complete. */ + [Pref="dom.testperf.enabled"] + readonly attribute DOMHighResTimeStamp topLevelDomComplete; +}; diff --git a/components/script/dom/webidls/PerformanceObserver.webidl b/components/script/dom/webidls/PerformanceObserver.webidl new file mode 100644 index 00000000000..db511a7b4b7 --- /dev/null +++ b/components/script/dom/webidls/PerformanceObserver.webidl @@ -0,0 +1,26 @@ +/* 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/. + * + * The origin of this IDL file is + * https://w3c.github.io/performance-timeline/#the-performanceobserver-interface + */ + +dictionary PerformanceObserverInit { + sequence<DOMString> entryTypes; + DOMString type; + boolean buffered; +}; + +callback PerformanceObserverCallback = void (PerformanceObserverEntryList entries, PerformanceObserver observer); + +[Exposed=(Window,Worker)] +interface PerformanceObserver { + [Throws] constructor(PerformanceObserverCallback callback); + [Throws] + void observe(optional PerformanceObserverInit options = {}); + void disconnect(); + PerformanceEntryList takeRecords(); + // codegen doesn't like SameObject+static and doesn't know FrozenArray + /*[SameObject]*/ static readonly attribute /*FrozenArray<DOMString>*/ any supportedEntryTypes; +}; diff --git a/components/script/dom/webidls/PerformanceObserverEntryList.webidl b/components/script/dom/webidls/PerformanceObserverEntryList.webidl new file mode 100644 index 00000000000..fc29226714c --- /dev/null +++ b/components/script/dom/webidls/PerformanceObserverEntryList.webidl @@ -0,0 +1,15 @@ +/* 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/. + * + * The origin of this IDL file is + * https://w3c.github.io/performance-timeline/#performanceobserverentrylist-interface + */ + +[Exposed=(Window,Worker)] +interface PerformanceObserverEntryList { + PerformanceEntryList getEntries(); + PerformanceEntryList getEntriesByType(DOMString entryType); + PerformanceEntryList getEntriesByName(DOMString name, + optional DOMString entryType); +}; diff --git a/components/script/dom/webidls/PerformancePaintTiming.webidl b/components/script/dom/webidls/PerformancePaintTiming.webidl new file mode 100644 index 00000000000..207e5c405ad --- /dev/null +++ b/components/script/dom/webidls/PerformancePaintTiming.webidl @@ -0,0 +1,11 @@ +/* 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/. + * + * The origin of this IDL file is + * https://wicg.github.io/paint-timing/#sec-PerformancePaintTiming + */ + +[Exposed=(Window,Worker)] +interface PerformancePaintTiming : PerformanceEntry { +}; diff --git a/components/script/dom/webidls/PerformanceResourceTiming.webidl b/components/script/dom/webidls/PerformanceResourceTiming.webidl new file mode 100644 index 00000000000..50a6dba1416 --- /dev/null +++ b/components/script/dom/webidls/PerformanceResourceTiming.webidl @@ -0,0 +1,30 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/resource-timing/ + */ + +// https://w3c.github.io/resource-timing/#sec-performanceresourcetiming +[Exposed=(Window,Worker)] +interface PerformanceResourceTiming : PerformanceEntry { + readonly attribute DOMString initiatorType; + readonly attribute DOMString nextHopProtocol; + // readonly attribute DOMHighResTimeStamp workerStart; + readonly attribute DOMHighResTimeStamp redirectStart; + readonly attribute DOMHighResTimeStamp redirectEnd; + readonly attribute DOMHighResTimeStamp fetchStart; + readonly attribute DOMHighResTimeStamp domainLookupStart; + readonly attribute DOMHighResTimeStamp domainLookupEnd; + readonly attribute DOMHighResTimeStamp connectStart; + readonly attribute DOMHighResTimeStamp connectEnd; + readonly attribute DOMHighResTimeStamp secureConnectionStart; + readonly attribute DOMHighResTimeStamp requestStart; + readonly attribute DOMHighResTimeStamp responseStart; + readonly attribute DOMHighResTimeStamp responseEnd; + readonly attribute unsigned long long transferSize; + readonly attribute unsigned long long encodedBodySize; + readonly attribute unsigned long long decodedBodySize; + [Default] object toJSON(); +}; diff --git a/components/script/dom/webidls/PerformanceTiming.webidl b/components/script/dom/webidls/PerformanceTiming.webidl deleted file mode 100644 index 2728bcd48d5..00000000000 --- a/components/script/dom/webidls/PerformanceTiming.webidl +++ /dev/null @@ -1,32 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ -/* - * The origin of this IDL file is - * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-navigation-timing-interface - */ - -[Exposed=(Window,Worker)] -interface PerformanceTiming { - readonly attribute unsigned long long navigationStart; - /* readonly attribute unsigned long long unloadEventStart; - readonly attribute unsigned long long unloadEventEnd; - readonly attribute unsigned long long redirectStart; - readonly attribute unsigned long long redirectEnd; - readonly attribute unsigned long long fetchStart; - readonly attribute unsigned long long domainLookupStart; - readonly attribute unsigned long long domainLookupEnd; - readonly attribute unsigned long long connectStart; - readonly attribute unsigned long long connectEnd; - readonly attribute unsigned long long secureConnectionStart; - readonly attribute unsigned long long requestStart; - readonly attribute unsigned long long responseStart; - readonly attribute unsigned long long responseEnd; */ - readonly attribute unsigned long long domLoading; - readonly attribute unsigned long long domInteractive; - readonly attribute unsigned long long domContentLoadedEventStart; - readonly attribute unsigned long long domContentLoadedEventEnd; - readonly attribute unsigned long long domComplete; - readonly attribute unsigned long long loadEventStart; - readonly attribute unsigned long long loadEventEnd; -}; diff --git a/components/script/dom/webidls/PermissionStatus.webidl b/components/script/dom/webidls/PermissionStatus.webidl index 9fb1a5ce966..83c0371e0de 100644 --- a/components/script/dom/webidls/PermissionStatus.webidl +++ b/components/script/dom/webidls/PermissionStatus.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/permissions/#permissionstatus diff --git a/components/script/dom/webidls/Permissions.webidl b/components/script/dom/webidls/Permissions.webidl index 56841956e8d..5b4092cc645 100644 --- a/components/script/dom/webidls/Permissions.webidl +++ b/components/script/dom/webidls/Permissions.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/permissions/#permissions-interface diff --git a/components/script/dom/webidls/Plugin.webidl b/components/script/dom/webidls/Plugin.webidl index accf5de1ce3..4c03636fabe 100644 --- a/components/script/dom/webidls/Plugin.webidl +++ b/components/script/dom/webidls/Plugin.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#dom-plugin [LegacyUnenumerableNamedProperties, Exposed=Window] diff --git a/components/script/dom/webidls/PluginArray.webidl b/components/script/dom/webidls/PluginArray.webidl index bc2e963ee24..09883cbfad4 100644 --- a/components/script/dom/webidls/PluginArray.webidl +++ b/components/script/dom/webidls/PluginArray.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#pluginarray [LegacyUnenumerableNamedProperties, Exposed=Window] diff --git a/components/script/dom/webidls/PopStateEvent.webidl b/components/script/dom/webidls/PopStateEvent.webidl index 4508cac6288..deb398abb3d 100644 --- a/components/script/dom/webidls/PopStateEvent.webidl +++ b/components/script/dom/webidls/PopStateEvent.webidl @@ -1,11 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#the-popstateevent-interface -[Constructor(DOMString type, optional PopStateEventInit eventInitDict), - Exposed=Window] +[Exposed=Window] interface PopStateEvent : Event { + [Throws] constructor(DOMString type, optional PopStateEventInit eventInitDict = {}); readonly attribute any state; }; diff --git a/components/script/dom/webidls/ProcessingInstruction.webidl b/components/script/dom/webidls/ProcessingInstruction.webidl index 734d43ebe5a..b3641badf1f 100644 --- a/components/script/dom/webidls/ProcessingInstruction.webidl +++ b/components/script/dom/webidls/ProcessingInstruction.webidl @@ -1,11 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#interface-processinginstruction */ +[Exposed=Window] interface ProcessingInstruction : CharacterData { [Constant] readonly attribute DOMString target; diff --git a/components/script/dom/webidls/ProgressEvent.webidl b/components/script/dom/webidls/ProgressEvent.webidl index 0c518822ef4..17d066ef187 100644 --- a/components/script/dom/webidls/ProgressEvent.webidl +++ b/components/script/dom/webidls/ProgressEvent.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://xhr.spec.whatwg.org/#interface-progressevent @@ -12,9 +12,9 @@ * http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0. */ -[Constructor(DOMString type, optional ProgressEventInit eventInitDict), - Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface ProgressEvent : Event { + [Throws] constructor(DOMString type, optional ProgressEventInit eventInitDict = {}); readonly attribute boolean lengthComputable; readonly attribute unsigned long long loaded; readonly attribute unsigned long long total; diff --git a/components/script/dom/webidls/Promise.webidl b/components/script/dom/webidls/Promise.webidl index 5897ff422f9..6cfbfb68bb0 100644 --- a/components/script/dom/webidls/Promise.webidl +++ b/components/script/dom/webidls/Promise.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This interface is entirely internal to Servo, and should not be accessible to // web pages. diff --git a/components/script/dom/webidls/PromiseNativeHandler.webidl b/components/script/dom/webidls/PromiseNativeHandler.webidl index caa1692dfd3..7d5f35e3223 100644 --- a/components/script/dom/webidls/PromiseNativeHandler.webidl +++ b/components/script/dom/webidls/PromiseNativeHandler.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This interface is entirely internal to Servo, and should not be accessible to // web pages. diff --git a/components/script/dom/webidls/PromiseRejectionEvent.webidl b/components/script/dom/webidls/PromiseRejectionEvent.webidl new file mode 100644 index 00000000000..70d11b1ff33 --- /dev/null +++ b/components/script/dom/webidls/PromiseRejectionEvent.webidl @@ -0,0 +1,17 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#the-promiserejectionevent-interface + +[Exposed=(Window,Worker)] +interface PromiseRejectionEvent : Event { + [Throws] constructor(DOMString type, PromiseRejectionEventInit eventInitDict); + readonly attribute Promise<any> promise; + readonly attribute any reason; +}; + +dictionary PromiseRejectionEventInit : EventInit { + required Promise<any> promise; + any reason; +}; diff --git a/components/script/dom/webidls/RTCDataChannel.webidl b/components/script/dom/webidls/RTCDataChannel.webidl new file mode 100644 index 00000000000..cdc3b9fdd48 --- /dev/null +++ b/components/script/dom/webidls/RTCDataChannel.webidl @@ -0,0 +1,49 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#dom-rtcdatachannel + +[Exposed=Window, Pref="dom.webrtc.enabled"] +interface RTCDataChannel : EventTarget { + readonly attribute USVString label; + readonly attribute boolean ordered; + readonly attribute unsigned short? maxPacketLifeTime; + readonly attribute unsigned short? maxRetransmits; + readonly attribute USVString protocol; + readonly attribute boolean negotiated; + readonly attribute unsigned short? id; + readonly attribute RTCDataChannelState readyState; + //readonly attribute unsigned long bufferedAmount; + //attribute unsigned long bufferedAmountLowThreshold; + attribute EventHandler onopen; + attribute EventHandler onbufferedamountlow; + attribute EventHandler onerror; + attribute EventHandler onclosing; + attribute EventHandler onclose; + void close(); + attribute EventHandler onmessage; + [SetterThrows] attribute DOMString binaryType; + [Throws] void send(USVString data); + [Throws] void send(Blob data); + [Throws] void send(ArrayBuffer data); + [Throws] void send(ArrayBufferView data); +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit +dictionary RTCDataChannelInit { + boolean ordered = true; + unsigned short maxPacketLifeTime; + unsigned short maxRetransmits; + USVString protocol = ""; + boolean negotiated = false; + unsigned short id; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcdatachannelstate +enum RTCDataChannelState { + "connecting", + "open", + "closing", + "closed" +}; diff --git a/components/script/dom/webidls/RTCDataChannelEvent.webidl b/components/script/dom/webidls/RTCDataChannelEvent.webidl new file mode 100644 index 00000000000..bc564a12138 --- /dev/null +++ b/components/script/dom/webidls/RTCDataChannelEvent.webidl @@ -0,0 +1,15 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#dom-rtcdatachannelevent + +[Exposed=Window, Pref="dom.webrtc.enabled"] +interface RTCDataChannelEvent : Event { + constructor(DOMString type, RTCDataChannelEventInit eventInitDict); + readonly attribute RTCDataChannel channel; +}; + +dictionary RTCDataChannelEventInit : EventInit { + required RTCDataChannel channel; +}; diff --git a/components/script/dom/webidls/RTCError.webidl b/components/script/dom/webidls/RTCError.webidl new file mode 100644 index 00000000000..f7ffa3d2d57 --- /dev/null +++ b/components/script/dom/webidls/RTCError.webidl @@ -0,0 +1,35 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#dom-rtcerror + +[Exposed=Window, Pref="dom.webrtc.enabled"] +interface RTCError : DOMException { + constructor(RTCErrorInit init, optional DOMString message = ""); + readonly attribute RTCErrorDetailType errorDetail; + readonly attribute long? sdpLineNumber; + readonly attribute long? httpRequestStatusCode; + readonly attribute long? sctpCauseCode; + readonly attribute unsigned long? receivedAlert; + readonly attribute unsigned long? sentAlert; +}; + +dictionary RTCErrorInit { + required RTCErrorDetailType errorDetail; + long sdpLineNumber; + long httpRequestStatusCode; + long sctpCauseCode; + unsigned long receivedAlert; + unsigned long sentAlert; +}; + +enum RTCErrorDetailType { + "data-channel-failure", + "dtls-failure", + "fingerprint-failure", + "sctp-failure", + "sdp-syntax-error", + "hardware-encoder-not-available", + "hardware-encoder-error" +}; diff --git a/components/script/dom/webidls/RTCErrorEvent.webidl b/components/script/dom/webidls/RTCErrorEvent.webidl new file mode 100644 index 00000000000..d3ac256744c --- /dev/null +++ b/components/script/dom/webidls/RTCErrorEvent.webidl @@ -0,0 +1,15 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#dom-rtcerrorevent + +[Exposed=Window, Pref="dom.webrtc.enabled"] +interface RTCErrorEvent : Event { + constructor(DOMString type, RTCErrorEventInit eventInitDict); + [SameObject] readonly attribute RTCError error; +}; + +dictionary RTCErrorEventInit : EventInit { + required RTCError error; +}; diff --git a/components/script/dom/webidls/RTCIceCandidate.webidl b/components/script/dom/webidls/RTCIceCandidate.webidl new file mode 100644 index 00000000000..fde0421c8c0 --- /dev/null +++ b/components/script/dom/webidls/RTCIceCandidate.webidl @@ -0,0 +1,33 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface + + +[Exposed=Window, Pref="dom.webrtc.enabled"] +interface RTCIceCandidate { + [Throws] constructor(optional RTCIceCandidateInit candidateInitDict = {}); + readonly attribute DOMString candidate; + readonly attribute DOMString? sdpMid; + readonly attribute unsigned short? sdpMLineIndex; + // readonly attribute DOMString? foundation; + // readonly attribute RTCIceComponent? component; + // readonly attribute unsigned long? priority; + // readonly attribute DOMString? address; + // readonly attribute RTCIceProtocol? protocol; + // readonly attribute unsigned short? port; + // readonly attribute RTCIceCandidateType? type; + // readonly attribute RTCIceTcpCandidateType? tcpType; + // readonly attribute DOMString? relatedAddress; + // readonly attribute unsigned short? relatedPort; + readonly attribute DOMString? usernameFragment; + RTCIceCandidateInit toJSON(); +}; + +dictionary RTCIceCandidateInit { + DOMString candidate = ""; + DOMString? sdpMid = null; + unsigned short? sdpMLineIndex = null; + DOMString usernameFragment; +}; diff --git a/components/script/dom/webidls/RTCPeerConnection.webidl b/components/script/dom/webidls/RTCPeerConnection.webidl new file mode 100644 index 00000000000..d75f2f9c14c --- /dev/null +++ b/components/script/dom/webidls/RTCPeerConnection.webidl @@ -0,0 +1,153 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#interface-definition + +[Exposed=Window, Pref="dom.webrtc.enabled"] +interface RTCPeerConnection : EventTarget { + [Throws] constructor(optional RTCConfiguration configuration = {}); + Promise<RTCSessionDescriptionInit> createOffer(optional RTCOfferOptions options = {}); + Promise<RTCSessionDescriptionInit> createAnswer(optional RTCAnswerOptions options = {}); + Promise<void> setLocalDescription(RTCSessionDescriptionInit description); + readonly attribute RTCSessionDescription? localDescription; + // readonly attribute RTCSessionDescription? currentLocalDescription; + // readonly attribute RTCSessionDescription? pendingLocalDescription; + Promise<void> setRemoteDescription(RTCSessionDescriptionInit description); + readonly attribute RTCSessionDescription? remoteDescription; + // readonly attribute RTCSessionDescription? currentRemoteDescription; + // readonly attribute RTCSessionDescription? pendingRemoteDescription; + Promise<void> addIceCandidate(optional RTCIceCandidateInit candidate = {}); + readonly attribute RTCSignalingState signalingState; + readonly attribute RTCIceGatheringState iceGatheringState; + readonly attribute RTCIceConnectionState iceConnectionState; + // readonly attribute RTCPeerConnectionState connectionState; + // readonly attribute boolean? canTrickleIceCandidates; + // static sequence<RTCIceServer> getDefaultIceServers(); + // RTCConfiguration getConfiguration(); + // void setConfiguration(RTCConfiguration configuration); + void close(); + attribute EventHandler onnegotiationneeded; + attribute EventHandler onicecandidate; + // attribute EventHandler onicecandidateerror; + attribute EventHandler onsignalingstatechange; + attribute EventHandler oniceconnectionstatechange; + attribute EventHandler onicegatheringstatechange; + // attribute EventHandler onconnectionstatechange; + + // removed from spec, but still shipped by browsers + void addStream (MediaStream stream); +}; + +dictionary RTCConfiguration { + sequence<RTCIceServer> iceServers; + RTCIceTransportPolicy iceTransportPolicy = "all"; + RTCBundlePolicy bundlePolicy = "balanced"; + RTCRtcpMuxPolicy rtcpMuxPolicy = "require"; + DOMString peerIdentity; + // sequence<RTCCertificate> certificates; + [EnforceRange] + octet iceCandidatePoolSize = 0; +}; + +enum RTCIceTransportPolicy { + "relay", + "all" +}; + +enum RTCBundlePolicy { + "balanced", + "max-compat", + "max-bundle" +}; + +enum RTCRtcpMuxPolicy { + // At risk due to lack of implementers' interest. + "negotiate", + "require" +}; + +dictionary RTCIceServer { + required (DOMString or sequence<DOMString>) urls; + DOMString username; + DOMString /*(DOMString or RTCOAuthCredential)*/ credential; + RTCIceCredentialType credentialType = "password"; +}; + +enum RTCIceCredentialType { + "password", + "oauth" +}; + +dictionary RTCOfferAnswerOptions { + boolean voiceActivityDetection = true; +}; + +dictionary RTCOfferOptions : RTCOfferAnswerOptions { + boolean iceRestart = false; +}; + +dictionary RTCAnswerOptions : RTCOfferAnswerOptions { +}; + +enum RTCIceGatheringState { + "new", + "gathering", + "complete" +}; + +enum RTCIceConnectionState { + "new", + "checking", + "connected", + "completed", + "disconnected", + "failed", + "closed" +}; + +enum RTCSignalingState { + "stable", + "have-local-offer", + "have-remote-offer", + "have-local-pranswer", + "have-remote-pranswer", + "closed" +}; + +dictionary RTCRtpCodingParameters { + DOMString rid; +}; + +dictionary RTCRtpEncodingParameters : RTCRtpCodingParameters { + boolean active = true; + unsigned long maxBitrate; + double scaleResolutionDownBy; +}; + +dictionary RTCRtpTransceiverInit { + RTCRtpTransceiverDirection direction = "sendrecv"; + sequence<MediaStream> streams = []; + sequence<RTCRtpEncodingParameters> sendEncodings = []; +}; + +partial interface RTCPeerConnection { + // sequence<RTCRtpSender> getSenders(); + // sequence<RTCRtpReceiver> getReceivers(); + // sequence<RTCRtpTransceiver> getTransceivers(); + // RTCRtpSender addTrack(MediaStreamTrack track, + // MediaStream... streams); + // void removeTrack(RTCRtpSender sender); + [Pref="dom.webrtc.transceiver.enabled"] + RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind, + optional RTCRtpTransceiverInit init = {}); + attribute EventHandler ontrack; +}; + +// https://www.w3.org/TR/webrtc/#rtcpeerconnection-interface-extensions-0 +partial interface RTCPeerConnection { + // readonly attribute RTCSctpTransport? sctp; + RTCDataChannel createDataChannel(USVString label, + optional RTCDataChannelInit dataChannelDict = {}); + attribute EventHandler ondatachannel; +}; diff --git a/components/script/dom/webidls/RTCPeerConnectionIceEvent.webidl b/components/script/dom/webidls/RTCPeerConnectionIceEvent.webidl new file mode 100644 index 00000000000..2ffd69cea50 --- /dev/null +++ b/components/script/dom/webidls/RTCPeerConnectionIceEvent.webidl @@ -0,0 +1,17 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#rtcpeerconnectioniceevent + +[Exposed=Window, Pref="dom.webrtc.enabled"] +interface RTCPeerConnectionIceEvent : Event { + [Throws] constructor(DOMString type, optional RTCPeerConnectionIceEventInit eventInitDict = {}); + readonly attribute RTCIceCandidate? candidate; + readonly attribute DOMString? url; +}; + +dictionary RTCPeerConnectionIceEventInit : EventInit { + RTCIceCandidate? candidate; + DOMString? url; +}; diff --git a/components/script/dom/webidls/RTCRtpSender.webidl b/components/script/dom/webidls/RTCRtpSender.webidl new file mode 100644 index 00000000000..d804b1bb3b7 --- /dev/null +++ b/components/script/dom/webidls/RTCRtpSender.webidl @@ -0,0 +1,47 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#dom-rtcrtpsender + +dictionary RTCRtpHeaderExtensionParameters { + required DOMString uri; + required unsigned short id; + boolean encrypted = false; +}; + +dictionary RTCRtcpParameters { + DOMString cname; + boolean reducedSize; +}; + +dictionary RTCRtpCodecParameters { + required octet payloadType; + required DOMString mimeType; + required unsigned long clockRate; + unsigned short channels; + DOMString sdpFmtpLine; +}; + +dictionary RTCRtpParameters { + required sequence<RTCRtpHeaderExtensionParameters> headerExtensions; + required RTCRtcpParameters rtcp; + required sequence<RTCRtpCodecParameters> codecs; +}; + +dictionary RTCRtpSendParameters : RTCRtpParameters { + required DOMString transactionId; + required sequence<RTCRtpEncodingParameters> encodings; +}; + +[Exposed=Window, Pref="dom.webrtc.transceiver.enabled"] +interface RTCRtpSender { + //readonly attribute MediaStreamTrack? track; + //readonly attribute RTCDtlsTransport? transport; + //static RTCRtpCapabilities? getCapabilities(DOMString kind); + Promise<void> setParameters(RTCRtpSendParameters parameters); + RTCRtpSendParameters getParameters(); + //Promise<void> replaceTrack(MediaStreamTrack? withTrack); + //void setStreams(MediaStream... streams); + //Promise<RTCStatsReport> getStats(); +}; diff --git a/components/script/dom/webidls/RTCRtpTransceiver.webidl b/components/script/dom/webidls/RTCRtpTransceiver.webidl new file mode 100644 index 00000000000..408c2708709 --- /dev/null +++ b/components/script/dom/webidls/RTCRtpTransceiver.webidl @@ -0,0 +1,24 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#rtcrtptransceiver-interface + +[Exposed=Window, Pref="dom.webrtc.transceiver.enabled"] +interface RTCRtpTransceiver { + //readonly attribute DOMString? mid; + [SameObject] readonly attribute RTCRtpSender sender; + //[SameObject] readonly attribute RTCRtpReceiver receiver; + attribute RTCRtpTransceiverDirection direction; + //readonly attribute RTCRtpTransceiverDirection? currentDirection; + //void stop(); + //void setCodecPreferences(sequence<RTCRtpCodecCapability> codecs); +}; + +enum RTCRtpTransceiverDirection { + "sendrecv", + "sendonly", + "recvonly", + "inactive", + "stopped" +}; diff --git a/components/script/dom/webidls/RTCSessionDescription.webidl b/components/script/dom/webidls/RTCSessionDescription.webidl new file mode 100644 index 00000000000..f13d0fd0d9f --- /dev/null +++ b/components/script/dom/webidls/RTCSessionDescription.webidl @@ -0,0 +1,25 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#rtcsessiondescription-class + +[Exposed=Window, Pref="dom.webrtc.enabled"] +interface RTCSessionDescription { + [Throws] constructor(RTCSessionDescriptionInit descriptionInitDict); + readonly attribute RTCSdpType type; + readonly attribute DOMString sdp; + [Default] object toJSON(); +}; + +dictionary RTCSessionDescriptionInit { + required RTCSdpType type; + DOMString sdp = ""; +}; + +enum RTCSdpType { + "offer", + "pranswer", + "answer", + "rollback" +}; diff --git a/components/script/dom/webidls/RTCTrackEvent.webidl b/components/script/dom/webidls/RTCTrackEvent.webidl new file mode 100644 index 00000000000..c2283e5a803 --- /dev/null +++ b/components/script/dom/webidls/RTCTrackEvent.webidl @@ -0,0 +1,23 @@ +/* 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/. */ + +// https://w3c.github.io/webrtc-pc/#dom-rtctrackevent + +[Exposed=Window, Pref="dom.webrtc.enabled"] +interface RTCTrackEvent : Event { + [Throws] constructor(DOMString type, RTCTrackEventInit eventInitDict); + // readonly attribute RTCRtpReceiver receiver; + readonly attribute MediaStreamTrack track; + // [SameObject] + // readonly attribute FrozenArray<MediaStream> streams; + // readonly attribute RTCRtpTransceiver transceiver; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtctrackeventinit +dictionary RTCTrackEventInit : EventInit { + // required RTCRtpReceiver receiver; + required MediaStreamTrack track; + // sequence<MediaStream> streams = []; + // required RTCRtpTransceiver transceiver; +}; diff --git a/components/script/dom/webidls/RadioNodeList.webidl b/components/script/dom/webidls/RadioNodeList.webidl index 7168ea34551..6db8d2af353 100644 --- a/components/script/dom/webidls/RadioNodeList.webidl +++ b/components/script/dom/webidls/RadioNodeList.webidl @@ -1,8 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#radionodelist +[Exposed=Window] interface RadioNodeList : NodeList { attribute DOMString value; }; diff --git a/components/script/dom/webidls/Range.webidl b/components/script/dom/webidls/Range.webidl index 1daf2d8182f..84cd80da0ef 100644 --- a/components/script/dom/webidls/Range.webidl +++ b/components/script/dom/webidls/Range.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#range @@ -8,8 +8,9 @@ * http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#extensions-to-the-range-interface */ -[Constructor] +[Exposed=Window] interface Range { + [Throws] constructor(); [Pure] readonly attribute Node startContainer; [Pure] @@ -47,15 +48,15 @@ interface Range { const unsigned short END_TO_START = 3; [Pure, Throws] short compareBoundaryPoints(unsigned short how, Range sourceRange); - [Throws] + [CEReactions, Throws] void deleteContents(); - [NewObject, Throws] + [CEReactions, NewObject, Throws] DocumentFragment extractContents(); - [NewObject, Throws] + [CEReactions, NewObject, Throws] DocumentFragment cloneContents(); - [Throws] + [CEReactions, Throws] void insertNode(Node node); - [Throws] + [CEReactions, Throws] void surroundContents(Node newParent); [NewObject] @@ -76,7 +77,7 @@ interface Range { // https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#extensions-to-the-range-interface partial interface Range { - [NewObject, Throws] + [CEReactions, NewObject, Throws] DocumentFragment createContextualFragment(DOMString fragment); }; diff --git a/components/script/dom/webidls/ReadableStream.webidl b/components/script/dom/webidls/ReadableStream.webidl new file mode 100644 index 00000000000..3662ca75ab8 --- /dev/null +++ b/components/script/dom/webidls/ReadableStream.webidl @@ -0,0 +1,11 @@ +/* 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/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +[NoInterfaceObject, Exposed=(Window,Worker)] +// Need to escape "ReadableStream" so it's treated as an identifier. +interface _ReadableStream { +}; diff --git a/components/script/dom/webidls/Request.webidl b/components/script/dom/webidls/Request.webidl index 95efc97937a..048636c1e8b 100644 --- a/components/script/dom/webidls/Request.webidl +++ b/components/script/dom/webidls/Request.webidl @@ -1,19 +1,18 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://fetch.spec.whatwg.org/#request-class typedef (Request or USVString) RequestInfo; -[Constructor(RequestInfo input, optional RequestInit init), - Exposed=(Window,Worker)] - +[Exposed=(Window,Worker)] interface Request { + [Throws] constructor(RequestInfo input, optional RequestInit init = {}); readonly attribute ByteString method; readonly attribute USVString url; [SameObject] readonly attribute Headers headers; - readonly attribute RequestType type; + readonly attribute RequestDestination destination; readonly attribute USVString referrer; readonly attribute ReferrerPolicy referrerPolicy; @@ -22,10 +21,11 @@ interface Request { readonly attribute RequestCache cache; readonly attribute RequestRedirect redirect; readonly attribute DOMString integrity; + [NewObject, Throws] Request clone(); }; -Request implements Body; +Request includes Body; dictionary RequestInit { ByteString method; @@ -41,31 +41,21 @@ dictionary RequestInit { any window; // can only be set to null }; -enum RequestType { - "", - "audio", - "font", - "image", - "script", - "style", - "track", - "video" -}; - enum RequestDestination { "", + "audio", "document", "embed", "font", "image", "manifest", - "media", "object", "report", "script", - "serviceworker", "sharedworker", "style", + "track", + "video", "worker", "xslt" }; @@ -105,6 +95,7 @@ enum ReferrerPolicy { "origin", "origin-when-cross-origin", "unsafe-url", + "same-origin", "strict-origin", "strict-origin-when-cross-origin" }; diff --git a/components/script/dom/webidls/Response.webidl b/components/script/dom/webidls/Response.webidl index a1b3cf7dd9a..0ced0c13794 100644 --- a/components/script/dom/webidls/Response.webidl +++ b/components/script/dom/webidls/Response.webidl @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://fetch.spec.whatwg.org/#response-class - [Constructor(optional BodyInit? body = null, optional ResponseInit init), - Exposed=(Window,Worker)] + [Exposed=(Window,Worker)] interface Response { + [Throws] constructor(optional BodyInit? body = null, optional ResponseInit init = {}); [NewObject] static Response error(); [NewObject, Throws] static Response redirect(USVString url, optional unsigned short status = 302); @@ -23,11 +23,11 @@ interface Response { [NewObject, Throws] Response clone(); }; -Response implements Body; +Response includes Body; dictionary ResponseInit { unsigned short status = 200; - ByteString statusText = "OK"; + ByteString statusText = ""; HeadersInit headers; }; diff --git a/components/script/dom/webidls/SVGElement.webidl b/components/script/dom/webidls/SVGElement.webidl index 529e9e67f06..2da7f062b89 100644 --- a/components/script/dom/webidls/SVGElement.webidl +++ b/components/script/dom/webidls/SVGElement.webidl @@ -1,9 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://svgwg.org/svg2-draft/types.html#InterfaceSVGElement -[Abstract, Pref="dom.svg.enabled"] +[Exposed=Window, Pref="dom.svg.enabled"] interface SVGElement : Element { //[SameObject] readonly attribute SVGAnimatedString className; @@ -18,5 +18,6 @@ interface SVGElement : Element { //void blur(); }; -//SVGElement implements GlobalEventHandlers; -//SVGElement implements SVGElementInstance; +//SVGElement includes GlobalEventHandlers; +//SVGElement includes SVGElementInstance; +SVGElement includes ElementCSSInlineStyle; diff --git a/components/script/dom/webidls/SVGGraphicsElement.webidl b/components/script/dom/webidls/SVGGraphicsElement.webidl index cf6c315d917..052182e374d 100644 --- a/components/script/dom/webidls/SVGGraphicsElement.webidl +++ b/components/script/dom/webidls/SVGGraphicsElement.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://svgwg.org/svg2-draft/types.html#InterfaceSVGGraphicsElement //dictionary SVGBoundingBoxOptions { @@ -10,7 +10,7 @@ // boolean clipped = false; //}; -[Abstract, Pref="dom.svg.enabled"] +[Exposed=Window, Abstract, Pref="dom.svg.enabled"] interface SVGGraphicsElement : SVGElement { //[SameObject] readonly attribute SVGAnimatedTransformList transform; @@ -19,4 +19,4 @@ interface SVGGraphicsElement : SVGElement { //DOMMatrix? getScreenCTM(); }; -//SVGGraphicsElement implements SVGTests; +//SVGGraphicsElement includes SVGTests; diff --git a/components/script/dom/webidls/SVGSVGElement.webidl b/components/script/dom/webidls/SVGSVGElement.webidl index bed2c03a74b..0e61776a40e 100644 --- a/components/script/dom/webidls/SVGSVGElement.webidl +++ b/components/script/dom/webidls/SVGSVGElement.webidl @@ -1,9 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://svgwg.org/svg2-draft/struct.html#InterfaceSVGSVGElement -[Pref="dom.svg.enabled"] +[Exposed=Window, Pref="dom.svg.enabled"] interface SVGSVGElement : SVGGraphicsElement { //[SameObject] readonly attribute SVGAnimatedLength x; @@ -40,6 +40,6 @@ interface SVGSVGElement : SVGGraphicsElement { //void forceRedraw(); }; -//SVGSVGElement implements SVGFitToViewBox; -//SVGSVGElement implements SVGZoomAndPan; -//SVGSVGElement implements WindowEventHandlers; +//SVGSVGElement includes SVGFitToViewBox; +//SVGSVGElement includes SVGZoomAndPan; +//SVGSVGElement includes WindowEventHandlers; diff --git a/components/script/dom/webidls/Screen.webidl b/components/script/dom/webidls/Screen.webidl index dfc81221d76..9160560b8c1 100644 --- a/components/script/dom/webidls/Screen.webidl +++ b/components/script/dom/webidls/Screen.webidl @@ -1,14 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // http://dev.w3.org/csswg/cssom-view/#the-screen-interface [Exposed=Window] interface Screen { - //readonly attribute double availWidth; - //readonly attribute double availHeight; - //readonly attribute double width; - //readonly attribute double height; + readonly attribute double availWidth; + readonly attribute double availHeight; + readonly attribute double width; + readonly attribute double height; readonly attribute unsigned long colorDepth; readonly attribute unsigned long pixelDepth; }; diff --git a/components/script/dom/webidls/Selection.webidl b/components/script/dom/webidls/Selection.webidl new file mode 100644 index 00000000000..38c874e3bf7 --- /dev/null +++ b/components/script/dom/webidls/Selection.webidl @@ -0,0 +1,32 @@ +/* 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/. */ + +// https://w3c.github.io/selection-api/#selection-interface +[Exposed=Window] +interface Selection { +readonly attribute Node? anchorNode; + readonly attribute unsigned long anchorOffset; + readonly attribute Node? focusNode; + readonly attribute unsigned long focusOffset; + readonly attribute boolean isCollapsed; + readonly attribute unsigned long rangeCount; + readonly attribute DOMString type; + [Throws] Range getRangeAt(unsigned long index); + void addRange(Range range); + [Throws] void removeRange(Range range); + void removeAllRanges(); + void empty(); + [Throws] void collapse(Node? node, optional unsigned long offset = 0); + [Throws] void setPosition(Node? node, optional unsigned long offset = 0); + [Throws] void collapseToStart(); + [Throws] void collapseToEnd(); + [Throws] void extend(Node node, optional unsigned long offset = 0); + [Throws] + void setBaseAndExtent(Node anchorNode, unsigned long anchorOffset, Node focusNode, unsigned long focusOffset); + [Throws] void selectAllChildren(Node node); + [CEReactions, Throws] + void deleteFromDocument(); + boolean containsNode(Node node, optional boolean allowPartialContainment = false); + stringifier DOMString (); +}; diff --git a/components/script/dom/webidls/ServiceWorker.webidl b/components/script/dom/webidls/ServiceWorker.webidl index 219c25bcd52..60bb6dfc4b9 100644 --- a/components/script/dom/webidls/ServiceWorker.webidl +++ b/components/script/dom/webidls/ServiceWorker.webidl @@ -1,19 +1,21 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -// http://w3c.github.io/ServiceWorker/#service-worker-obj -[Pref="dom.serviceworker.enabled", Exposed=(Window,Worker)] +// https://w3c.github.io/ServiceWorker/#serviceworker-interface +[Pref="dom.serviceworker.enabled", SecureContext, Exposed=(Window,Worker)] interface ServiceWorker : EventTarget { readonly attribute USVString scriptURL; readonly attribute ServiceWorkerState state; - [Throws] void postMessage(any message/*, optional sequence<Transferable> transfer*/); + [Throws] void postMessage(any message, sequence<object> transfer); + [Throws] void postMessage(any message, optional PostMessageOptions options = {}); // event attribute EventHandler onstatechange; }; -ServiceWorker implements AbstractWorker; +// FIXME: use `includes` instead of `implements` after #22539 is fixed. +ServiceWorker includes AbstractWorker; enum ServiceWorkerState { "installing", diff --git a/components/script/dom/webidls/ServiceWorkerContainer.webidl b/components/script/dom/webidls/ServiceWorkerContainer.webidl index 6d183619af8..18deb5e58f4 100644 --- a/components/script/dom/webidls/ServiceWorkerContainer.webidl +++ b/components/script/dom/webidls/ServiceWorkerContainer.webidl @@ -1,26 +1,30 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -// https://w3c.github.io/ServiceWorker/#service-worker-container +// https://w3c.github.io/ServiceWorker/#serviceworkercontainer-interface [Pref="dom.serviceworker.enabled", Exposed=(Window,Worker)] interface ServiceWorkerContainer : EventTarget { - [Unforgeable] readonly attribute ServiceWorker? controller; - //[SameObject] readonly attribute Promise<ServiceWorkerRegistration> ready; + readonly attribute ServiceWorker? controller; + //readonly attribute Promise<ServiceWorkerRegistration> ready; - [NewObject] Promise<ServiceWorkerRegistration> register(USVString scriptURL, optional RegistrationOptions options); + [NewObject] Promise<ServiceWorkerRegistration> register(USVString scriptURL, + optional RegistrationOptions options = {}); - //[NewObject] /*Promise<any>*/ any getRegistration(optional USVString clientURL = ""); - //[NewObject] /* Promise */<sequence<ServiceWorkerRegistration>> getRegistrations(); + //[NewObject] Promise<any> getRegistration(optional USVString clientURL = ""); + //[NewObject] Promise<FrozenArray<ServiceWorkerRegistration>> getRegistrations(); + //void startMessages(); // events //attribute EventHandler oncontrollerchange; //attribute EventHandler onerror; //attribute EventHandler onmessage; // event.source of message events is ServiceWorker object + //attribute EventHandler onmessageerror; }; dictionary RegistrationOptions { USVString scope; - //WorkerType type = "classic"; + WorkerType type = "classic"; + ServiceWorkerUpdateViaCache updateViaCache = "imports"; }; diff --git a/components/script/dom/webidls/ServiceWorkerGlobalScope.webidl b/components/script/dom/webidls/ServiceWorkerGlobalScope.webidl index 9259c3d64c4..257f3ff3252 100644 --- a/components/script/dom/webidls/ServiceWorkerGlobalScope.webidl +++ b/components/script/dom/webidls/ServiceWorkerGlobalScope.webidl @@ -1,8 +1,8 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -// https://w3c.github.io/ServiceWorker/#service-worker-global-scope +// https://w3c.github.io/ServiceWorker/#serviceworkerglobalscope [Global=(Worker,ServiceWorker), Exposed=ServiceWorker, Pref="dom.serviceworker.enabled"] @@ -17,8 +17,8 @@ interface ServiceWorkerGlobalScope : WorkerGlobalScope { //attribute EventHandler oninstall; //attribute EventHandler onactivate; //attribute EventHandler onfetch; - //attribute EventHandler onforeignfetch; // event attribute EventHandler onmessage; // event.source of the message events is Client object + attribute EventHandler onmessageerror; }; diff --git a/components/script/dom/webidls/ServiceWorkerRegistration.webidl b/components/script/dom/webidls/ServiceWorkerRegistration.webidl index 27c27d7cc41..f7a10659513 100644 --- a/components/script/dom/webidls/ServiceWorkerRegistration.webidl +++ b/components/script/dom/webidls/ServiceWorkerRegistration.webidl @@ -1,15 +1,17 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -// https://w3c.github.io/ServiceWorker/#service-worker-registration-obj -[Pref="dom.serviceworker.enabled", Exposed=(Window,Worker)] +// https://w3c.github.io/ServiceWorker/#serviceworkerregistration-interface +[Pref="dom.serviceworker.enabled", SecureContext, Exposed=(Window,Worker)] interface ServiceWorkerRegistration : EventTarget { - [Unforgeable] readonly attribute ServiceWorker? installing; - [Unforgeable] readonly attribute ServiceWorker? waiting; - [Unforgeable] readonly attribute ServiceWorker? active; + readonly attribute ServiceWorker? installing; + readonly attribute ServiceWorker? waiting; + readonly attribute ServiceWorker? active; + [SameObject] readonly attribute NavigationPreloadManager navigationPreload; readonly attribute USVString scope; + readonly attribute ServiceWorkerUpdateViaCache updateViaCache; // [NewObject] Promise<void> update(); // [NewObject] Promise<boolean> unregister(); @@ -17,3 +19,9 @@ interface ServiceWorkerRegistration : EventTarget { // event // attribute EventHandler onupdatefound; }; + +enum ServiceWorkerUpdateViaCache { + "imports", + "all", + "none" +}; diff --git a/components/script/dom/webidls/ServoParser.webidl b/components/script/dom/webidls/ServoParser.webidl index c3b0926d824..89350130919 100644 --- a/components/script/dom/webidls/ServoParser.webidl +++ b/components/script/dom/webidls/ServoParser.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This interface is entirely internal to Servo, and should not be accessible to // web pages. diff --git a/components/script/dom/webidls/ShadowRoot.webidl b/components/script/dom/webidls/ShadowRoot.webidl new file mode 100644 index 00000000000..b39991df4b6 --- /dev/null +++ b/components/script/dom/webidls/ShadowRoot.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * The origin of this IDL file is: + * https://dom.spec.whatwg.org/#interface-shadowroot + */ + +[Exposed=Window] +interface ShadowRoot : DocumentFragment { + readonly attribute ShadowRootMode mode; + readonly attribute Element host; +}; + +enum ShadowRootMode { "open", "closed"}; + +ShadowRoot includes DocumentOrShadowRoot; diff --git a/components/script/dom/webidls/StereoPannerNode.webidl b/components/script/dom/webidls/StereoPannerNode.webidl new file mode 100644 index 00000000000..e59f7cb9d19 --- /dev/null +++ b/components/script/dom/webidls/StereoPannerNode.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#StereoPannerNode + */ + +dictionary StereoPannerOptions: AudioNodeOptions { + float pan = 0; +}; + +[Exposed=Window] +interface StereoPannerNode : AudioScheduledSourceNode { + [Throws] constructor(BaseAudioContext context, optional StereoPannerOptions options = {}); + readonly attribute AudioParam pan; +}; diff --git a/components/script/dom/webidls/Storage.webidl b/components/script/dom/webidls/Storage.webidl index ba04dceee3e..380eb72d265 100644 --- a/components/script/dom/webidls/Storage.webidl +++ b/components/script/dom/webidls/Storage.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://html.spec.whatwg.org/multipage/#the-storage-interface diff --git a/components/script/dom/webidls/StorageEvent.webidl b/components/script/dom/webidls/StorageEvent.webidl index e838bc9480a..dbc60b23036 100644 --- a/components/script/dom/webidls/StorageEvent.webidl +++ b/components/script/dom/webidls/StorageEvent.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * Interface for a client side storage. See * https://html.spec.whatwg.org/multipage/#the-storageevent-interface @@ -9,13 +9,20 @@ * Event sent to a window when a storage area changes. */ -[Constructor(DOMString type, optional StorageEventInit eventInitDict), Exposed=Window] +[Exposed=Window] interface StorageEvent : Event { + [Throws] constructor(DOMString type, optional StorageEventInit eventInitDict = {}); readonly attribute DOMString? key; readonly attribute DOMString? oldValue; readonly attribute DOMString? newValue; readonly attribute DOMString url; readonly attribute Storage? storageArea; + + + void initStorageEvent(DOMString type, optional boolean bubbles = false, + optional boolean cancelable = false, optional DOMString? key = null, optional + DOMString? oldValue = null, optional DOMString? newValue = null, optional + USVString url = "", optional Storage? storageArea = null); }; dictionary StorageEventInit : EventInit { diff --git a/components/script/dom/webidls/StylePropertyMapReadOnly.webidl b/components/script/dom/webidls/StylePropertyMapReadOnly.webidl new file mode 100644 index 00000000000..7f3860ee701 --- /dev/null +++ b/components/script/dom/webidls/StylePropertyMapReadOnly.webidl @@ -0,0 +1,16 @@ +/* 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/. */ + +// https://drafts.css-houdini.org/css-typed-om-1/#stylepropertymapreadonly +// NOTE: should this be exposed to Window? +[Pref="dom.worklet.enabled", Exposed=(Worklet)] +interface StylePropertyMapReadOnly { + CSSStyleValue? get(DOMString property); + // sequence<CSSStyleValue> getAll(DOMString property); + boolean has(DOMString property); + // iterable<DOMString, (CSSStyleValue or sequence<CSSStyleValue>)>; + sequence<DOMString> getProperties(); + // https://github.com/w3c/css-houdini-drafts/issues/268 + // stringifier; +}; diff --git a/components/script/dom/webidls/StyleSheet.webidl b/components/script/dom/webidls/StyleSheet.webidl index 1ef0533abdc..cb8290cc30b 100644 --- a/components/script/dom/webidls/StyleSheet.webidl +++ b/components/script/dom/webidls/StyleSheet.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#the-stylesheet-interface [Exposed=Window] @@ -8,16 +8,15 @@ interface StyleSheet { readonly attribute DOMString type_; readonly attribute DOMString? href; - // readonly attribute (Element or ProcessingInstruction)? ownerNode; + readonly attribute Element? ownerNode; // readonly attribute StyleSheet? parentStyleSheet; readonly attribute DOMString? title; - // [SameObject, PutForwards=mediaText] readonly attribute MediaList media; + [SameObject, PutForwards=mediaText] readonly attribute MediaList media; attribute boolean disabled; }; // https://drafts.csswg.org/cssom/#the-linkstyle-interface -[NoInterfaceObject] -interface LinkStyle { +interface mixin LinkStyle { readonly attribute StyleSheet? sheet; }; diff --git a/components/script/dom/webidls/StyleSheetList.webidl b/components/script/dom/webidls/StyleSheetList.webidl index 20360eb4ba4..2179c284495 100644 --- a/components/script/dom/webidls/StyleSheetList.webidl +++ b/components/script/dom/webidls/StyleSheetList.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://drafts.csswg.org/cssom/#the-stylesheetlist-interface // [ArrayClass] diff --git a/components/script/dom/webidls/SubmitEvent.webidl b/components/script/dom/webidls/SubmitEvent.webidl new file mode 100644 index 00000000000..f5b2c49257d --- /dev/null +++ b/components/script/dom/webidls/SubmitEvent.webidl @@ -0,0 +1,15 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#submitevent +[Exposed=Window] +interface SubmitEvent : Event { + constructor(DOMString typeArg, optional SubmitEventInit eventInitDict = {}); + + readonly attribute HTMLElement? submitter; +}; + +dictionary SubmitEventInit : EventInit { + HTMLElement? submitter = null; +}; diff --git a/components/script/dom/webidls/TestBinding.webidl b/components/script/dom/webidls/TestBinding.webidl index 74216947917..14ab146d8be 100644 --- a/components/script/dom/webidls/TestBinding.webidl +++ b/components/script/dom/webidls/TestBinding.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This interface is entirely internal to Servo, and should not be accessible to // web pages. @@ -32,8 +32,10 @@ dictionary TestDictionary { Blob interfaceValue; any anyValue; object objectValue; - TestDictionaryDefaults dict; + TestDictionaryDefaults dict = {}; sequence<TestDictionaryDefaults> seqDict; + // Testing codegen to import Element correctly, ensure no other code references Element directly + sequence<Element> elementSequence; // Reserved rust keyword DOMString type; // These are used to test bidirectional conversion @@ -43,6 +45,14 @@ dictionary TestDictionary { DOMString? nonRequiredNullable2; }; +dictionary TestDictionaryParent { + DOMString parentStringMember; +}; + +dictionary TestDictionaryWithParent : TestDictionaryParent { + DOMString stringMember; +}; + dictionary TestDictionaryDefaults { boolean booleanValue = false; byte byteValue = 7; @@ -62,6 +72,7 @@ dictionary TestDictionaryDefaults { USVString usvstringValue = "foo"; TestEnum enumValue = "bar"; any anyValue = null; + sequence<object> arrayValue = []; boolean? nullableBooleanValue = false; byte? nullableByteValue = 7; @@ -83,13 +94,17 @@ dictionary TestDictionaryDefaults { object? nullableObjectValue = null; }; -[Constructor, - Constructor(sequence<unrestricted double> numberSequence), - Constructor(unrestricted double num), - Pref="dom.testbinding.enabled", +dictionary TestURLLike { + required DOMString href; +}; + +[Pref="dom.testbinding.enabled", Exposed=(Window,Worker) ] interface TestBinding { + [Throws] constructor(); + [Throws] constructor(sequence<unrestricted double> numberSequence); + [Throws] constructor(unrestricted double num); attribute boolean booleanAttribute; attribute byte byteAttribute; attribute octet octetAttribute; @@ -151,6 +166,13 @@ interface TestBinding { [BinaryName="BinaryRenamedAttribute2"] attribute DOMString attr-to-binary-rename; attribute DOMString attr-to-automatically-rename; + const long long constantInt64 = -1; + const unsigned long long constantUint64 = 1; + const float constantFloat32 = 1.0; + const double constantFloat64 = 1.0; + const unrestricted float constantUnrestrictedFloat32 = 1.0; + const unrestricted double constantUnrestrictedFloat64 = 1.0; + [PutForwards=booleanAttribute] readonly attribute TestBinding forwardedAttribute; @@ -219,6 +241,8 @@ interface TestBinding { TestDictionary receiveTestDictionaryWithSuccessOnKeyword(); boolean dictMatchesPassedValues(TestDictionary arg); + (DOMString or object) receiveUnionIdentity((DOMString or object) arg); + void passBoolean(boolean arg); void passByte(byte arg); void passOctet(octet arg); @@ -237,6 +261,9 @@ interface TestBinding { void passByteString(ByteString arg); void passEnum(TestEnum arg); void passInterface(Blob arg); + void passTypedArray(Int8Array arg); + void passTypedArray2(ArrayBuffer arg); + void passTypedArray3(ArrayBufferView arg); void passUnion((HTMLElement or long) arg); void passUnion2((Event or DOMString) data); void passUnion3((Blob or DOMString) data); @@ -247,6 +274,7 @@ interface TestBinding { void passUnion8((sequence<ByteString> or long) arg); void passUnion9((TestDictionary or long) arg); void passUnion10((DOMString or object) arg); + void passUnion11((ArrayBuffer or ArrayBufferView) arg); void passUnionWithTypedef((Document or TestTypedef) arg); void passUnionWithTypedef2((sequence<long> or TestTypedef) arg); void passAny(any arg); @@ -254,9 +282,19 @@ interface TestBinding { void passCallbackFunction(Function fun); void passCallbackInterface(EventListener listener); void passSequence(sequence<long> seq); + void passAnySequence(sequence<any> seq); + sequence<any> anySequencePassthrough(sequence<any> seq); + void passObjectSequence(sequence<object> seq); void passStringSequence(sequence<DOMString> seq); void passInterfaceSequence(sequence<Blob> seq); + void passOverloaded(ArrayBuffer arg); + void passOverloaded(DOMString arg); + + // https://github.com/servo/servo/pull/26154 + DOMString passOverloadedDict(Node arg); + DOMString passOverloadedDict(TestURLLike arg); + void passNullableBoolean(boolean? arg); void passNullableByte(byte? arg); void passNullableOctet(octet? arg); @@ -276,6 +314,7 @@ interface TestBinding { // void passNullableEnum(TestEnum? arg); void passNullableInterface(Blob? arg); void passNullableObject(object? arg); + void passNullableTypedArray(Int8Array? arg); void passNullableUnion((HTMLElement or long)? arg); void passNullableUnion2((Event or DOMString)? data); void passNullableUnion3((DOMString or sequence<long>)? data); @@ -358,6 +397,7 @@ interface TestBinding { void passOptionalStringWithDefault(optional DOMString arg = "x"); void passOptionalUsvstringWithDefault(optional USVString arg = "x"); void passOptionalEnumWithDefault(optional TestEnum arg = "foo"); + void passOptionalSequenceWithDefault(optional sequence<long> seq = []); // void passOptionalUnionWithDefault(optional (HTMLElement or long) arg = 9); // void passOptionalUnion2WithDefault(optional(Event or DOMString)? data = "foo"); @@ -400,6 +440,9 @@ interface TestBinding { // void passOptionalNullableEnumWithNonNullDefault(optional TestEnum? arg = "foo"); // void passOptionalNullableUnionWithNonNullDefault(optional (HTMLElement or long)? arg = 7); // void passOptionalNullableUnion2WithNonNullDefault(optional (Event or DOMString)? data = "foo"); + TestBinding passOptionalOverloaded(TestBinding arg0, optional unsigned long arg1 = 0, + optional unsigned long arg2 = 0); + void passOptionalOverloaded(Blob arg0, optional unsigned long arg1 = 0); void passVariadicBoolean(boolean... args); void passVariadicBooleanAndDefault(optional boolean arg = true, boolean... args); @@ -434,33 +477,37 @@ interface TestBinding { sequence<sequence<long>> returnSequenceSequence(); void passUnionSequenceSequence((long or sequence<sequence<long>>) seq); - void passMozMap(MozMap<long> arg); - void passNullableMozMap(MozMap<long>? arg); - void passMozMapOfNullableInts(MozMap<long?> arg); - void passOptionalMozMapOfNullableInts(optional MozMap<long?> arg); - void passOptionalNullableMozMapOfNullableInts(optional MozMap<long?>? arg); - void passCastableObjectMozMap(MozMap<TestBinding> arg); - void passNullableCastableObjectMozMap(MozMap<TestBinding?> arg); - void passCastableObjectNullableMozMap(MozMap<TestBinding>? arg); - void passNullableCastableObjectNullableMozMap(MozMap<TestBinding?>? arg); - void passOptionalMozMap(optional MozMap<long> arg); - void passOptionalNullableMozMap(optional MozMap<long>? arg); - void passOptionalNullableMozMapWithDefaultValue(optional MozMap<long>? arg = null); - void passOptionalObjectMozMap(optional MozMap<TestBinding> arg); - void passStringMozMap(MozMap<DOMString> arg); - void passByteStringMozMap(MozMap<ByteString> arg); - void passMozMapOfMozMaps(MozMap<MozMap<long>> arg); - - void passMozMapUnion((long or MozMap<ByteString>) init); - void passMozMapUnion2((TestBinding or MozMap<ByteString>) init); - void passMozMapUnion3((TestBinding or sequence<sequence<ByteString>> or MozMap<ByteString>) init); - - MozMap<long> receiveMozMap(); - MozMap<long>? receiveNullableMozMap(); - MozMap<long?> receiveMozMapOfNullableInts(); - MozMap<long?>? receiveNullableMozMapOfNullableInts(); - MozMap<MozMap<long>> receiveMozMapOfMozMaps(); - MozMap<any> receiveAnyMozMap(); + void passRecord(record<DOMString, long> arg); + void passRecordWithUSVStringKey(record<USVString, long> arg); + void passRecordWithByteStringKey(record<ByteString, long> arg); + void passNullableRecord(record<DOMString, long>? arg); + void passRecordOfNullableInts(record<DOMString, long?> arg); + void passOptionalRecordOfNullableInts(optional record<DOMString, long?> arg); + void passOptionalNullableRecordOfNullableInts(optional record<DOMString, long?>? arg); + void passCastableObjectRecord(record<DOMString, TestBinding> arg); + void passNullableCastableObjectRecord(record<DOMString, TestBinding?> arg); + void passCastableObjectNullableRecord(record<DOMString, TestBinding>? arg); + void passNullableCastableObjectNullableRecord(record<DOMString, TestBinding?>? arg); + void passOptionalRecord(optional record<DOMString, long> arg); + void passOptionalNullableRecord(optional record<DOMString, long>? arg); + void passOptionalNullableRecordWithDefaultValue(optional record<DOMString, long>? arg = null); + void passOptionalObjectRecord(optional record<DOMString, TestBinding> arg); + void passStringRecord(record<DOMString, DOMString> arg); + void passByteStringRecord(record<DOMString, ByteString> arg); + void passRecordOfRecords(record<DOMString, record<DOMString, long>> arg); + + void passRecordUnion((long or record<DOMString, ByteString>) init); + void passRecordUnion2((TestBinding or record<DOMString, ByteString>) init); + void passRecordUnion3((TestBinding or sequence<sequence<ByteString>> or record<DOMString, ByteString>) init); + + record<DOMString, long> receiveRecord(); + record<USVString, long> receiveRecordWithUSVStringKey(); + record<ByteString, long> receiveRecordWithByteStringKey(); + record<DOMString, long>? receiveNullableRecord(); + record<DOMString, long?> receiveRecordOfNullableInts(); + record<DOMString, long?>? receiveNullableRecordOfNullableInts(); + record<DOMString, record<DOMString, long>> receiveRecordOfRecords(); + record<DOMString, any> receiveAnyRecord(); static attribute boolean booleanAttributeStatic; static void receiveVoidStatic(); @@ -478,7 +525,7 @@ interface TestBinding { [Pref="dom.testbinding.prefcontrolled.enabled"] const unsigned short prefControlledConstDisabled = 0; [Pref="layout.animations.test.enabled"] - void advanceClock(long millis, optional boolean forceLayoutTick = true); + void advanceClock(long millis); [Pref="dom.testbinding.prefcontrolled2.enabled"] readonly attribute boolean prefControlledAttributeEnabled; @@ -519,7 +566,6 @@ interface TestBinding { Promise<any> returnRejectedPromise(any value); readonly attribute Promise<boolean> promiseAttribute; void acceptPromise(Promise<DOMString> string); - void acceptNullablePromise(Promise<DOMString>? string); Promise<any> promiseNativeHandler(SimpleCallback? resolve, SimpleCallback? reject); void promiseResolveNative(Promise<any> p, any value); void promiseRejectNative(Promise<any> p, any value); @@ -530,6 +576,21 @@ interface TestBinding { GlobalScope entryGlobal(); GlobalScope incumbentGlobal(); + + [Exposed=(Window)] + readonly attribute boolean semiExposedBoolFromInterface; + + TestDictionaryWithParent getDictionaryWithParent(DOMString parent, DOMString child); +}; + +[Exposed=(Window)] +partial interface TestBinding { + readonly attribute boolean boolFromSemiExposedPartialInterface; +}; + +partial interface TestBinding { + [Exposed=(Window)] + readonly attribute boolean semiExposedBoolFromPartialInterface; }; callback SimpleCallback = void(any value); diff --git a/components/script/dom/webidls/TestBindingIterable.webidl b/components/script/dom/webidls/TestBindingIterable.webidl index c9e61074eed..d74c43d8f5f 100644 --- a/components/script/dom/webidls/TestBindingIterable.webidl +++ b/components/script/dom/webidls/TestBindingIterable.webidl @@ -1,12 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This interface is entirely internal to Servo, and should not be accessible to // web pages. -[Pref="dom.testbinding.enabled", Exposed=(Window,Worker), Constructor] +[Pref="dom.testbinding.enabled", Exposed=(Window,Worker)] interface TestBindingIterable { + [Throws] constructor(); void add(DOMString arg); readonly attribute unsigned long length; getter DOMString getItem(unsigned long index); diff --git a/components/script/dom/webidls/TestBindingPairIterable.webidl b/components/script/dom/webidls/TestBindingPairIterable.webidl index a7bc66c1be3..5d19fbe9f32 100644 --- a/components/script/dom/webidls/TestBindingPairIterable.webidl +++ b/components/script/dom/webidls/TestBindingPairIterable.webidl @@ -1,12 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // This interface is entirely internal to Servo, and should not be accessible to // web pages. -[Pref="dom.testbinding.enabled", Exposed=(Window,Worker), Constructor] +[Pref="dom.testbinding.enabled", Exposed=(Window,Worker)] interface TestBindingPairIterable { + [Throws] constructor(); void add(DOMString key, unsigned long value); iterable<DOMString, unsigned long>; }; diff --git a/components/script/dom/webidls/TestBindingProxy.webidl b/components/script/dom/webidls/TestBindingProxy.webidl index f0117721d73..211a4f3a9f7 100644 --- a/components/script/dom/webidls/TestBindingProxy.webidl +++ b/components/script/dom/webidls/TestBindingProxy.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * This IDL file was created to test the special operations (see * https://heycam.github.io/webidl/#idl-special-operations) without converting diff --git a/components/script/dom/webidls/TestRunner.webidl b/components/script/dom/webidls/TestRunner.webidl index 0326c14dbec..bea35374bd6 100644 --- a/components/script/dom/webidls/TestRunner.webidl +++ b/components/script/dom/webidls/TestRunner.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://webbluetoothcg.github.io/web-bluetooth/tests#test-runner diff --git a/components/script/dom/webidls/TestWorklet.webidl b/components/script/dom/webidls/TestWorklet.webidl new file mode 100644 index 00000000000..be254a3d079 --- /dev/null +++ b/components/script/dom/webidls/TestWorklet.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +[Pref="dom.worklet.testing.enabled", Exposed=(Window)] +interface TestWorklet { + [Throws] constructor(); + [NewObject] Promise<void> addModule(USVString moduleURL, optional WorkletOptions options = {}); + DOMString? lookup(DOMString key); +}; diff --git a/components/script/dom/webidls/TestWorkletGlobalScope.webidl b/components/script/dom/webidls/TestWorkletGlobalScope.webidl new file mode 100644 index 00000000000..e3cbec7c052 --- /dev/null +++ b/components/script/dom/webidls/TestWorkletGlobalScope.webidl @@ -0,0 +1,11 @@ +/* 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/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +[Global=(Worklet,TestWorklet), Pref="dom.worklet.enabled", Exposed=TestWorklet] +interface TestWorkletGlobalScope : WorkletGlobalScope { + void registerKeyValue(DOMString key, DOMString value); +}; diff --git a/components/script/dom/webidls/Text.webidl b/components/script/dom/webidls/Text.webidl index 515d9939806..0b0a980d0a9 100644 --- a/components/script/dom/webidls/Text.webidl +++ b/components/script/dom/webidls/Text.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/ @@ -10,8 +10,9 @@ */ // https://dom.spec.whatwg.org/#text -[Constructor(optional DOMString data = "")] +[Exposed=Window] interface Text : CharacterData { + [Throws] constructor(optional DOMString data = ""); [NewObject, Throws] Text splitText(unsigned long offset); [Pure] diff --git a/components/script/dom/webidls/TextDecoder.webidl b/components/script/dom/webidls/TextDecoder.webidl index e7292ed3061..cc9cf5506db 100644 --- a/components/script/dom/webidls/TextDecoder.webidl +++ b/components/script/dom/webidls/TextDecoder.webidl @@ -1,19 +1,23 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://encoding.spec.whatwg.org/#interface-textdecoder dictionary TextDecoderOptions { boolean fatal = false; - //boolean ignoreBOM = false; + boolean ignoreBOM = false; }; -[Constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options), Exposed=(Window,Worker)] +dictionary TextDecodeOptions { + boolean stream = false; +}; + +[Exposed=(Window,Worker)] interface TextDecoder { + [Throws] constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options = {}); readonly attribute DOMString encoding; readonly attribute boolean fatal; - //readonly attribute boolean ignoreBOM; - //USVString decode(optional BufferSource input, optional TextDecodeOptions options); + readonly attribute boolean ignoreBOM; [Throws] - USVString decode(optional object input); + USVString decode(optional BufferSource input, optional TextDecodeOptions options = {}); }; diff --git a/components/script/dom/webidls/TextEncoder.webidl b/components/script/dom/webidls/TextEncoder.webidl index eef46f68edf..77e5132ff05 100644 --- a/components/script/dom/webidls/TextEncoder.webidl +++ b/components/script/dom/webidls/TextEncoder.webidl @@ -1,10 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* https://encoding.spec.whatwg.org/#interface-textencoder */ -[Constructor, Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface TextEncoder { + [Throws] constructor(); readonly attribute DOMString encoding; [NewObject] Uint8Array encode(optional USVString input = ""); diff --git a/components/script/dom/webidls/TextMetrics.webidl b/components/script/dom/webidls/TextMetrics.webidl new file mode 100644 index 00000000000..efba27450fe --- /dev/null +++ b/components/script/dom/webidls/TextMetrics.webidl @@ -0,0 +1,23 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#textmetrics +[Exposed=(PaintWorklet, Window, Worker), Pref="dom.canvas_text.enabled"] +interface TextMetrics { + // x-direction + readonly attribute double width; // advance width + readonly attribute double actualBoundingBoxLeft; + readonly attribute double actualBoundingBoxRight; + + // y-direction + readonly attribute double fontBoundingBoxAscent; + readonly attribute double fontBoundingBoxDescent; + readonly attribute double actualBoundingBoxAscent; + readonly attribute double actualBoundingBoxDescent; + readonly attribute double emHeightAscent; + readonly attribute double emHeightDescent; + readonly attribute double hangingBaseline; + readonly attribute double alphabeticBaseline; + readonly attribute double ideographicBaseline; +}; diff --git a/components/script/dom/webidls/TextTrack.webidl b/components/script/dom/webidls/TextTrack.webidl new file mode 100644 index 00000000000..d51d9b0dc67 --- /dev/null +++ b/components/script/dom/webidls/TextTrack.webidl @@ -0,0 +1,30 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#texttrack + +enum TextTrackMode { "disabled", "hidden", "showing" }; +enum TextTrackKind { "subtitles", "captions", "descriptions", "chapters", "metadata" }; + +[Exposed=Window] +interface TextTrack : EventTarget { + readonly attribute TextTrackKind kind; + readonly attribute DOMString label; + readonly attribute DOMString language; + + readonly attribute DOMString id; + // readonly attribute DOMString inBandMetadataTrackDispatchType; + + attribute TextTrackMode mode; + + readonly attribute TextTrackCueList? cues; + readonly attribute TextTrackCueList? activeCues; + + [Throws] + void addCue(TextTrackCue cue); + [Throws] + void removeCue(TextTrackCue cue); + + attribute EventHandler oncuechange; +}; diff --git a/components/script/dom/webidls/TextTrackCue.webidl b/components/script/dom/webidls/TextTrackCue.webidl new file mode 100644 index 00000000000..20f1bf3f06d --- /dev/null +++ b/components/script/dom/webidls/TextTrackCue.webidl @@ -0,0 +1,18 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#texttrackcue + +[Exposed=Window] +interface TextTrackCue : EventTarget { + readonly attribute TextTrack? track; + + attribute DOMString id; + attribute double startTime; + attribute double endTime; + attribute boolean pauseOnExit; + + attribute EventHandler onenter; + attribute EventHandler onexit; +}; diff --git a/components/script/dom/webidls/TextTrackCueList.webidl b/components/script/dom/webidls/TextTrackCueList.webidl new file mode 100644 index 00000000000..357d8751bc2 --- /dev/null +++ b/components/script/dom/webidls/TextTrackCueList.webidl @@ -0,0 +1,12 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#texttrackcuelist + +[Exposed=Window] +interface TextTrackCueList { + readonly attribute unsigned long length; + getter TextTrackCue (unsigned long index); + TextTrackCue? getCueById(DOMString id); +}; diff --git a/components/script/dom/webidls/TextTrackList.webidl b/components/script/dom/webidls/TextTrackList.webidl new file mode 100644 index 00000000000..33e1bef0ff4 --- /dev/null +++ b/components/script/dom/webidls/TextTrackList.webidl @@ -0,0 +1,16 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#texttracklist + +[Exposed=Window] +interface TextTrackList : EventTarget { + readonly attribute unsigned long length; + getter TextTrack (unsigned long index); + TextTrack? getTrackById(DOMString id); + + attribute EventHandler onchange; + attribute EventHandler onaddtrack; + attribute EventHandler onremovetrack; +}; diff --git a/components/script/dom/webidls/TimeRanges.webidl b/components/script/dom/webidls/TimeRanges.webidl new file mode 100644 index 00000000000..0163a590a91 --- /dev/null +++ b/components/script/dom/webidls/TimeRanges.webidl @@ -0,0 +1,12 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage#time-ranges + +[Exposed=Window] +interface TimeRanges { + readonly attribute unsigned long length; + [Throws] double start(unsigned long index); + [Throws] double end(unsigned long index); +}; diff --git a/components/script/dom/webidls/Touch.webidl b/components/script/dom/webidls/Touch.webidl index d2a8e0373fa..c887a0fc2a3 100644 --- a/components/script/dom/webidls/Touch.webidl +++ b/components/script/dom/webidls/Touch.webidl @@ -1,8 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // http://w3c.github.io/touch-events/#idl-def-Touch +[Exposed=Window] interface Touch { readonly attribute long identifier; readonly attribute EventTarget target; diff --git a/components/script/dom/webidls/TouchEvent.webidl b/components/script/dom/webidls/TouchEvent.webidl index 971c7216443..3779349b781 100644 --- a/components/script/dom/webidls/TouchEvent.webidl +++ b/components/script/dom/webidls/TouchEvent.webidl @@ -1,8 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // http://w3c.github.io/touch-events/#idl-def-TouchEvent +[Exposed=Window] interface TouchEvent : UIEvent { readonly attribute TouchList touches; readonly attribute TouchList targetTouches; diff --git a/components/script/dom/webidls/TouchList.webidl b/components/script/dom/webidls/TouchList.webidl index c9229b69245..bc6f7cb1304 100644 --- a/components/script/dom/webidls/TouchList.webidl +++ b/components/script/dom/webidls/TouchList.webidl @@ -1,8 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // http://w3c.github.io/touch-events/#idl-def-TouchList +[Exposed=Window] interface TouchList { readonly attribute unsigned long length; getter Touch? item (unsigned long index); diff --git a/components/script/dom/webidls/TrackEvent.webidl b/components/script/dom/webidls/TrackEvent.webidl new file mode 100644 index 00000000000..124e5cf4345 --- /dev/null +++ b/components/script/dom/webidls/TrackEvent.webidl @@ -0,0 +1,15 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#the-trackevent-interface + +[Exposed=Window] +interface TrackEvent : Event { + [Throws] constructor(DOMString type, optional TrackEventInit eventInitDict = {}); + readonly attribute (VideoTrack or AudioTrack or TextTrack)? track; +}; + +dictionary TrackEventInit : EventInit { + (VideoTrack or AudioTrack or TextTrack)? track = null; +}; diff --git a/components/script/dom/webidls/TransitionEvent.webidl b/components/script/dom/webidls/TransitionEvent.webidl index 5e7a3ff6913..eaa9f3917db 100644 --- a/components/script/dom/webidls/TransitionEvent.webidl +++ b/components/script/dom/webidls/TransitionEvent.webidl @@ -1,14 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * For more information on this interface please see * https://dom.spec.whatwg.org/#event */ -[Constructor(DOMString type, optional TransitionEventInit transitionEventInitDict), - Exposed=Window] +[Exposed=Window] interface TransitionEvent : Event { + [Throws] constructor(DOMString type, optional TransitionEventInit transitionEventInitDict = {}); readonly attribute DOMString propertyName; readonly attribute float elapsedTime; readonly attribute DOMString pseudoElement; diff --git a/components/script/dom/webidls/TreeWalker.webidl b/components/script/dom/webidls/TreeWalker.webidl index 049f79bdd11..4162855dd09 100644 --- a/components/script/dom/webidls/TreeWalker.webidl +++ b/components/script/dom/webidls/TreeWalker.webidl @@ -1,11 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://dom.spec.whatwg.org/#interface-treewalker */ +[Exposed=Window] interface TreeWalker { [SameObject] readonly attribute Node root; diff --git a/components/script/dom/webidls/UIEvent.webidl b/components/script/dom/webidls/UIEvent.webidl index 58f04ff24dc..12850f70e45 100644 --- a/components/script/dom/webidls/UIEvent.webidl +++ b/components/script/dom/webidls/UIEvent.webidl @@ -1,10 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://w3c.github.io/uievents/#interface-uievent -[Constructor(DOMString type, optional UIEventInit eventInitDict)] +[Exposed=Window] interface UIEvent : Event { + [Throws] constructor(DOMString type, optional UIEventInit eventInitDict = {}); // readonly attribute WindowProxy? view; readonly attribute Window? view; readonly attribute long detail; diff --git a/components/script/dom/webidls/URL.webidl b/components/script/dom/webidls/URL.webidl index 7c47b6caa71..47a1ef53129 100644 --- a/components/script/dom/webidls/URL.webidl +++ b/components/script/dom/webidls/URL.webidl @@ -1,12 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://url.spec.whatwg.org/#url -[Constructor(USVString url, optional USVString base), Exposed=(Window,Worker)] +[Exposed=(Window,Worker), + LegacyWindowAlias=webkitURL] interface URL { + [Throws] constructor(USVString url, optional USVString base); [SetterThrows] - /*stringifier*/ attribute USVString href; + stringifier attribute USVString href; readonly attribute USVString origin; attribute USVString protocol; attribute USVString username; @@ -24,8 +26,5 @@ interface URL { // static DOMString createFor(Blob blob); static void revokeObjectURL(DOMString url); - // This is only doing as well as gecko right now. - // https://github.com/servo/servo/issues/7590 is on file for - // adding attribute stringifier support. - stringifier; + USVString toJSON(); }; diff --git a/components/script/dom/webidls/URLSearchParams.webidl b/components/script/dom/webidls/URLSearchParams.webidl index 1012f1bded5..33ba9253624 100644 --- a/components/script/dom/webidls/URLSearchParams.webidl +++ b/components/script/dom/webidls/URLSearchParams.webidl @@ -1,19 +1,23 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://url.spec.whatwg.org/#interface-urlsearchparams */ -[Constructor(optional (USVString or URLSearchParams) init/* = ""*/), Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface URLSearchParams { + [Throws] constructor(optional (sequence<sequence<USVString>> or record<USVString, USVString> or USVString) init = ""); void append(USVString name, USVString value); void delete(USVString name); USVString? get(USVString name); sequence<USVString> getAll(USVString name); boolean has(USVString name); void set(USVString name, USVString value); + + void sort(); + // Be careful with implementing iterable interface. // Search params might be mutated by URL::SetSearch while iterating (discussed in PR #10351). iterable<USVString, USVString>; diff --git a/components/script/dom/webidls/VR.webidl b/components/script/dom/webidls/VR.webidl deleted file mode 100644 index 0fded365be0..00000000000 --- a/components/script/dom/webidls/VR.webidl +++ /dev/null @@ -1,10 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://w3c.github.io/webvr/#interface-navigator -[Pref="dom.webvr.enabled"] -interface VR: EventTarget { - Promise<sequence<VRDisplay>> getDisplays(); - //readonly attribute FrozenArray<VRDisplay> activeVRDisplays; -}; diff --git a/components/script/dom/webidls/VRDisplay.webidl b/components/script/dom/webidls/VRDisplay.webidl deleted file mode 100644 index 6822a994a9b..00000000000 --- a/components/script/dom/webidls/VRDisplay.webidl +++ /dev/null @@ -1,131 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -enum VREye { - "left", - "right" -}; - - -// https://w3c.github.io/webvr/#interface-vrdisplay -[Pref="dom.webvr.enabled"] -interface VRDisplay : EventTarget { - readonly attribute boolean isConnected; - readonly attribute boolean isPresenting; - - /** - * Dictionary of capabilities describing the VRDisplay. - */ - [SameObject] readonly attribute VRDisplayCapabilities capabilities; - - /** - * If this VRDisplay supports room-scale experiences, the optional - * stage attribute contains details on the room-scale parameters. - * The stageParameters attribute can not change between null - * and non-null once the VRDisplay is enumerated; however, - * the values within VRStageParameters may change after - * any call to VRDisplay.submitFrame as the user may re-configure - * their environment at any time. - */ - readonly attribute VRStageParameters? stageParameters; - - /** - * Return the current VREyeParameters for the given eye. - */ - VREyeParameters getEyeParameters(VREye whichEye); - - /** - * An identifier for this distinct VRDisplay. Used as an - * association point in the Gamepad API. - */ - readonly attribute unsigned long displayId; - - /** - * A display name, a user-readable name identifying it. - */ - readonly attribute DOMString displayName; - - /** - * Populates the passed VRFrameData with the information required to render - * the current frame. - */ - boolean getFrameData(VRFrameData frameData); - - /** - * Return a VRPose containing the future predicted pose of the VRDisplay - * when the current frame will be presented. The value returned will not - * change until JavaScript has returned control to the browser. - * - * The VRPose will contain the position, orientation, velocity, - * and acceleration of each of these properties. - */ - [NewObject] VRPose getPose(); - - /** - * Reset the pose for this display, treating its current position and - * orientation as the "origin/zero" values. VRPose.position, - * VRPose.orientation, and VRStageParameters.sittingToStandingTransform may be - * updated when calling resetPose(). This should be called in only - * sitting-space experiences. - */ - void resetPose(); - - /** - * z-depth defining the near plane of the eye view frustum - * enables mapping of values in the render target depth - * attachment to scene coordinates. Initially set to 0.01. - */ - attribute double depthNear; - - /** - * z-depth defining the far plane of the eye view frustum - * enables mapping of values in the render target depth - * attachment to scene coordinates. Initially set to 10000.0. - */ - attribute double depthFar; - - /** - * The callback passed to `requestAnimationFrame` will be called - * any time a new frame should be rendered. When the VRDisplay is - * presenting the callback will be called at the native refresh - * rate of the HMD. When not presenting this function acts - * identically to how window.requestAnimationFrame acts. Content should - * make no assumptions of frame rate or vsync behavior as the HMD runs - * asynchronously from other displays and at differing refresh rates. - */ - unsigned long requestAnimationFrame(FrameRequestCallback callback); - - /** - * Passing the value returned by `requestAnimationFrame` to - * `cancelAnimationFrame` will unregister the callback. - */ - void cancelAnimationFrame(unsigned long handle); - - /** - * Begin presenting to the VRDisplay. Must be called in response to a user gesture. - * Repeat calls while already presenting will update the VRLayers being displayed. - * If the number of values in the leftBounds/rightBounds arrays is not 0 or 4 for - * any of the passed layers the promise is rejected. - * If the source of any of the layers is not present (null), the promise is rejected. - */ - Promise<void> requestPresent(sequence<VRLayer> layers); - - /** - * Stops presenting to the VRDisplay. - */ - Promise<void> exitPresent(); - - /** - * Get the layers currently being presented. - */ - //sequence<VRLayer> getLayers(); - - /** - * The VRLayer provided to the VRDisplay will be captured and presented - * in the HMD. Calling this function has the same effect on the source - * canvas as any other operation that uses its source image, and canvases - * created without preserveDrawingBuffer set to true will be cleared. - */ - void submitFrame(); -}; diff --git a/components/script/dom/webidls/VRDisplayCapabilities.webidl b/components/script/dom/webidls/VRDisplayCapabilities.webidl deleted file mode 100644 index 2d9cccd6a97..00000000000 --- a/components/script/dom/webidls/VRDisplayCapabilities.webidl +++ /dev/null @@ -1,13 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://w3c.github.io/webvr/#interface-vrdisplaycapabilities -[Pref="dom.webvr.enabled"] -interface VRDisplayCapabilities { - readonly attribute boolean hasPosition; - readonly attribute boolean hasOrientation; - readonly attribute boolean hasExternalDisplay; - readonly attribute boolean canPresent; - readonly attribute unsigned long maxLayers; -}; diff --git a/components/script/dom/webidls/VRDisplayEvent.webidl b/components/script/dom/webidls/VRDisplayEvent.webidl deleted file mode 100644 index df0990d32a8..00000000000 --- a/components/script/dom/webidls/VRDisplayEvent.webidl +++ /dev/null @@ -1,23 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://w3c.github.io/webvr/#interface-vrdisplayevent - -enum VRDisplayEventReason { - "navigation", - "mounted", - "unmounted", - "requested" -}; - -[Pref="dom.webvr.enabled", Constructor(DOMString type, VRDisplayEventInit eventInitDict)] -interface VRDisplayEvent : Event { - readonly attribute VRDisplay display; - readonly attribute VRDisplayEventReason? reason; -}; - -dictionary VRDisplayEventInit : EventInit { - required VRDisplay display; - VRDisplayEventReason reason; -}; diff --git a/components/script/dom/webidls/VREyeParameters.webidl b/components/script/dom/webidls/VREyeParameters.webidl deleted file mode 100644 index 5d127f20784..00000000000 --- a/components/script/dom/webidls/VREyeParameters.webidl +++ /dev/null @@ -1,13 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://w3c.github.io/webvr/#interface-vreyeparameters - -[Pref="dom.webvr.enabled"] -interface VREyeParameters { - readonly attribute Float32Array offset; - [SameObject] readonly attribute VRFieldOfView fieldOfView; - readonly attribute unsigned long renderWidth; - readonly attribute unsigned long renderHeight; -}; diff --git a/components/script/dom/webidls/VRFieldOfView.webidl b/components/script/dom/webidls/VRFieldOfView.webidl deleted file mode 100644 index b562c5b0e7f..00000000000 --- a/components/script/dom/webidls/VRFieldOfView.webidl +++ /dev/null @@ -1,13 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://w3c.github.io/webvr/#interface-vrfieldofview - -[Pref="dom.webvr.enabled"] -interface VRFieldOfView { - readonly attribute double upDegrees; - readonly attribute double rightDegrees; - readonly attribute double downDegrees; - readonly attribute double leftDegrees; -}; diff --git a/components/script/dom/webidls/VRFrameData.webidl b/components/script/dom/webidls/VRFrameData.webidl deleted file mode 100644 index baa37ea6f68..00000000000 --- a/components/script/dom/webidls/VRFrameData.webidl +++ /dev/null @@ -1,15 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://w3c.github.io/webvr/#interface-vrframedata - -[Pref="dom.webvr.enabled", Constructor] -interface VRFrameData { - readonly attribute DOMHighResTimeStamp timestamp; - readonly attribute Float32Array leftProjectionMatrix; - readonly attribute Float32Array leftViewMatrix; - readonly attribute Float32Array rightProjectionMatrix; - readonly attribute Float32Array rightViewMatrix; - readonly attribute VRPose pose; -}; diff --git a/components/script/dom/webidls/VRLayer.webidl b/components/script/dom/webidls/VRLayer.webidl deleted file mode 100644 index 47b30b324f7..00000000000 --- a/components/script/dom/webidls/VRLayer.webidl +++ /dev/null @@ -1,13 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://w3c.github.io/webvr/#interface-vrlayer - -//typedef (HTMLCanvasElement or OffscreenCanvas) VRSource; - -dictionary VRLayer { - HTMLCanvasElement source; - sequence<float> leftBounds; - sequence<float> rightBounds; -}; diff --git a/components/script/dom/webidls/VRStageParameters.webidl b/components/script/dom/webidls/VRStageParameters.webidl deleted file mode 100644 index 255a8f07e76..00000000000 --- a/components/script/dom/webidls/VRStageParameters.webidl +++ /dev/null @@ -1,11 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -// https://w3c.github.io/webvr/#interface-vrstageparameters -[Pref="dom.webvr.enabled"] -interface VRStageParameters { - readonly attribute Float32Array sittingToStandingTransform; - readonly attribute float sizeX; - readonly attribute float sizeZ; -}; diff --git a/components/script/dom/webidls/VTTCue.webidl b/components/script/dom/webidls/VTTCue.webidl new file mode 100644 index 00000000000..073aa12f79b --- /dev/null +++ b/components/script/dom/webidls/VTTCue.webidl @@ -0,0 +1,30 @@ +/* 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/. */ + +// https://w3c.github.io/webvtt/#the-vttcue-interface + +enum AutoKeyword { "auto"}; +typedef (double or AutoKeyword) LineAndPositionSetting; +enum DirectionSetting { "" /* horizontal */, "rl", "lr" }; +enum LineAlignSetting { "start", "center", "end" }; +enum PositionAlignSetting { "line-left", "center", "line-right", "auto" }; +enum AlignSetting { "start", "center", "end", "left", "right" }; + +[Pref="dom.webvtt.enabled", Exposed=Window] +interface VTTCue : TextTrackCue { + constructor(double startTime, double endTime, DOMString text); + attribute VTTRegion? region; + attribute DirectionSetting vertical; + attribute boolean snapToLines; + attribute LineAndPositionSetting line; + attribute LineAlignSetting lineAlign; + [SetterThrows] + attribute LineAndPositionSetting position; + attribute PositionAlignSetting positionAlign; + [SetterThrows] + attribute double size; + attribute AlignSetting align; + attribute DOMString text; + DocumentFragment getCueAsHTML(); +}; diff --git a/components/script/dom/webidls/VTTRegion.webidl b/components/script/dom/webidls/VTTRegion.webidl new file mode 100644 index 00000000000..12fbe16170b --- /dev/null +++ b/components/script/dom/webidls/VTTRegion.webidl @@ -0,0 +1,26 @@ +/* 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/. */ + +// https://w3c.github.io/webvtt/#the-vttregion-interface + +enum ScrollSetting { "" /* none */, "up"}; + +[Pref="dom.webvtt.enabled", Exposed=Window] +interface VTTRegion { + [Throws] constructor(); + attribute DOMString id; + [SetterThrows] + attribute double width; + [SetterThrows] + attribute unsigned long lines; + [SetterThrows] + attribute double regionAnchorX; + [SetterThrows] + attribute double regionAnchorY; + [SetterThrows] + attribute double viewportAnchorX; + [SetterThrows] + attribute double viewportAnchorY; + attribute ScrollSetting scroll; +}; diff --git a/components/script/dom/webidls/ValidityState.webidl b/components/script/dom/webidls/ValidityState.webidl index e959e972a7f..a1a553e91ff 100644 --- a/components/script/dom/webidls/ValidityState.webidl +++ b/components/script/dom/webidls/ValidityState.webidl @@ -1,8 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#validitystate +[Exposed=Window] interface ValidityState { readonly attribute boolean valueMissing; readonly attribute boolean typeMismatch; diff --git a/components/script/dom/webidls/VideoTrack.webidl b/components/script/dom/webidls/VideoTrack.webidl new file mode 100644 index 00000000000..90d6c487eaa --- /dev/null +++ b/components/script/dom/webidls/VideoTrack.webidl @@ -0,0 +1,14 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#videotrack + +[Exposed=Window] +interface VideoTrack { + readonly attribute DOMString id; + readonly attribute DOMString kind; + readonly attribute DOMString label; + readonly attribute DOMString language; + attribute boolean selected; +}; diff --git a/components/script/dom/webidls/VideoTrackList.webidl b/components/script/dom/webidls/VideoTrackList.webidl new file mode 100644 index 00000000000..9c880f0d2b7 --- /dev/null +++ b/components/script/dom/webidls/VideoTrackList.webidl @@ -0,0 +1,17 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#videotracklist + +[Exposed=Window] +interface VideoTrackList : EventTarget { + readonly attribute unsigned long length; + getter VideoTrack (unsigned long index); + VideoTrack? getTrackById(DOMString id); + readonly attribute long selectedIndex; + + attribute EventHandler onchange; + attribute EventHandler onaddtrack; + attribute EventHandler onremovetrack; +}; diff --git a/components/script/dom/webidls/VoidFunction.webidl b/components/script/dom/webidls/VoidFunction.webidl new file mode 100644 index 00000000000..6c219ff9380 --- /dev/null +++ b/components/script/dom/webidls/VoidFunction.webidl @@ -0,0 +1,13 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://heycam.github.io/webidl/#VoidFunction + * + * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and + * Opera Software ASA. You are granted a license to use, reproduce + * and create derivative works of this document. + */ + +callback VoidFunction = void (); diff --git a/components/script/dom/webidls/WEBGLColorBufferFloat.webidl b/components/script/dom/webidls/WEBGLColorBufferFloat.webidl new file mode 100644 index 00000000000..dd041927ec1 --- /dev/null +++ b/components/script/dom/webidls/WEBGLColorBufferFloat.webidl @@ -0,0 +1,14 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/WEBGL_color_buffer_float/ + */ + +[NoInterfaceObject, Exposed=Window] +interface WEBGLColorBufferFloat { + const GLenum RGBA32F_EXT = 0x8814; + const GLenum FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT = 0x8211; + const GLenum UNSIGNED_NORMALIZED_EXT = 0x8C17; +}; // interface WEBGL_color_buffer_float diff --git a/components/script/dom/webidls/WEBGLCompressedTextureETC1.webidl b/components/script/dom/webidls/WEBGLCompressedTextureETC1.webidl new file mode 100644 index 00000000000..77f80197c97 --- /dev/null +++ b/components/script/dom/webidls/WEBGLCompressedTextureETC1.webidl @@ -0,0 +1,13 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/ + */ + +[NoInterfaceObject, Exposed=Window] +interface WEBGLCompressedTextureETC1 { + /* Compressed Texture Format */ + const GLenum COMPRESSED_RGB_ETC1_WEBGL = 0x8D64; +}; // interface WEBGLCompressedTextureETC1 diff --git a/components/script/dom/webidls/WEBGLCompressedTextureS3TC.webidl b/components/script/dom/webidls/WEBGLCompressedTextureS3TC.webidl new file mode 100644 index 00000000000..f940028bf5d --- /dev/null +++ b/components/script/dom/webidls/WEBGLCompressedTextureS3TC.webidl @@ -0,0 +1,16 @@ +/* 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/. */ +/* + * WebGL IDL definitions from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ + */ + +[NoInterfaceObject, Exposed=Window] +interface WEBGLCompressedTextureS3TC { + /* Compressed Texture Formats */ + const GLenum COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0; + const GLenum COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1; + const GLenum COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; + const GLenum COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3; +}; // interface WEBGLCompressedTextureS3TC diff --git a/components/script/dom/webidls/WebGL2RenderingContext.webidl b/components/script/dom/webidls/WebGL2RenderingContext.webidl new file mode 100644 index 00000000000..632f1ce5cc6 --- /dev/null +++ b/components/script/dom/webidls/WebGL2RenderingContext.webidl @@ -0,0 +1,588 @@ +/* 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/. */ +// +// WebGL IDL definitions scraped from the Khronos specification: +// https://www.khronos.org/registry/webgl/specs/latest/ +// +// This IDL depends on the typed array specification defined at: +// https://www.khronos.org/registry/typedarray/specs/latest/typedarrays.idl + +typedef long long GLint64; +typedef unsigned long long GLuint64; + +typedef (/*[AllowShared]*/ Uint32Array or sequence<GLuint>) Uint32List; + +interface mixin WebGL2RenderingContextBase +{ + const GLenum READ_BUFFER = 0x0C02; + const GLenum UNPACK_ROW_LENGTH = 0x0CF2; + const GLenum UNPACK_SKIP_ROWS = 0x0CF3; + const GLenum UNPACK_SKIP_PIXELS = 0x0CF4; + const GLenum PACK_ROW_LENGTH = 0x0D02; + const GLenum PACK_SKIP_ROWS = 0x0D03; + const GLenum PACK_SKIP_PIXELS = 0x0D04; + const GLenum COLOR = 0x1800; + const GLenum DEPTH = 0x1801; + const GLenum STENCIL = 0x1802; + const GLenum RED = 0x1903; + const GLenum RGB8 = 0x8051; + const GLenum RGBA8 = 0x8058; + const GLenum RGB10_A2 = 0x8059; + const GLenum TEXTURE_BINDING_3D = 0x806A; + const GLenum UNPACK_SKIP_IMAGES = 0x806D; + const GLenum UNPACK_IMAGE_HEIGHT = 0x806E; + const GLenum TEXTURE_3D = 0x806F; + const GLenum TEXTURE_WRAP_R = 0x8072; + const GLenum MAX_3D_TEXTURE_SIZE = 0x8073; + const GLenum UNSIGNED_INT_2_10_10_10_REV = 0x8368; + const GLenum MAX_ELEMENTS_VERTICES = 0x80E8; + const GLenum MAX_ELEMENTS_INDICES = 0x80E9; + const GLenum TEXTURE_MIN_LOD = 0x813A; + const GLenum TEXTURE_MAX_LOD = 0x813B; + const GLenum TEXTURE_BASE_LEVEL = 0x813C; + const GLenum TEXTURE_MAX_LEVEL = 0x813D; + const GLenum MIN = 0x8007; + const GLenum MAX = 0x8008; + const GLenum DEPTH_COMPONENT24 = 0x81A6; + const GLenum MAX_TEXTURE_LOD_BIAS = 0x84FD; + const GLenum TEXTURE_COMPARE_MODE = 0x884C; + const GLenum TEXTURE_COMPARE_FUNC = 0x884D; + const GLenum CURRENT_QUERY = 0x8865; + const GLenum QUERY_RESULT = 0x8866; + const GLenum QUERY_RESULT_AVAILABLE = 0x8867; + const GLenum STREAM_READ = 0x88E1; + const GLenum STREAM_COPY = 0x88E2; + const GLenum STATIC_READ = 0x88E5; + const GLenum STATIC_COPY = 0x88E6; + const GLenum DYNAMIC_READ = 0x88E9; + const GLenum DYNAMIC_COPY = 0x88EA; + const GLenum MAX_DRAW_BUFFERS = 0x8824; + const GLenum DRAW_BUFFER0 = 0x8825; + const GLenum DRAW_BUFFER1 = 0x8826; + const GLenum DRAW_BUFFER2 = 0x8827; + const GLenum DRAW_BUFFER3 = 0x8828; + const GLenum DRAW_BUFFER4 = 0x8829; + const GLenum DRAW_BUFFER5 = 0x882A; + const GLenum DRAW_BUFFER6 = 0x882B; + const GLenum DRAW_BUFFER7 = 0x882C; + const GLenum DRAW_BUFFER8 = 0x882D; + const GLenum DRAW_BUFFER9 = 0x882E; + const GLenum DRAW_BUFFER10 = 0x882F; + const GLenum DRAW_BUFFER11 = 0x8830; + const GLenum DRAW_BUFFER12 = 0x8831; + const GLenum DRAW_BUFFER13 = 0x8832; + const GLenum DRAW_BUFFER14 = 0x8833; + const GLenum DRAW_BUFFER15 = 0x8834; + const GLenum MAX_FRAGMENT_UNIFORM_COMPONENTS = 0x8B49; + const GLenum MAX_VERTEX_UNIFORM_COMPONENTS = 0x8B4A; + const GLenum SAMPLER_3D = 0x8B5F; + const GLenum SAMPLER_2D_SHADOW = 0x8B62; + const GLenum FRAGMENT_SHADER_DERIVATIVE_HINT = 0x8B8B; + const GLenum PIXEL_PACK_BUFFER = 0x88EB; + const GLenum PIXEL_UNPACK_BUFFER = 0x88EC; + const GLenum PIXEL_PACK_BUFFER_BINDING = 0x88ED; + const GLenum PIXEL_UNPACK_BUFFER_BINDING = 0x88EF; + const GLenum FLOAT_MAT2x3 = 0x8B65; + const GLenum FLOAT_MAT2x4 = 0x8B66; + const GLenum FLOAT_MAT3x2 = 0x8B67; + const GLenum FLOAT_MAT3x4 = 0x8B68; + const GLenum FLOAT_MAT4x2 = 0x8B69; + const GLenum FLOAT_MAT4x3 = 0x8B6A; + const GLenum SRGB = 0x8C40; + const GLenum SRGB8 = 0x8C41; + const GLenum SRGB8_ALPHA8 = 0x8C43; + const GLenum COMPARE_REF_TO_TEXTURE = 0x884E; + const GLenum RGBA32F = 0x8814; + const GLenum RGB32F = 0x8815; + const GLenum RGBA16F = 0x881A; + const GLenum RGB16F = 0x881B; + const GLenum VERTEX_ATTRIB_ARRAY_INTEGER = 0x88FD; + const GLenum MAX_ARRAY_TEXTURE_LAYERS = 0x88FF; + const GLenum MIN_PROGRAM_TEXEL_OFFSET = 0x8904; + const GLenum MAX_PROGRAM_TEXEL_OFFSET = 0x8905; + const GLenum MAX_VARYING_COMPONENTS = 0x8B4B; + const GLenum TEXTURE_2D_ARRAY = 0x8C1A; + const GLenum TEXTURE_BINDING_2D_ARRAY = 0x8C1D; + const GLenum R11F_G11F_B10F = 0x8C3A; + const GLenum UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B; + const GLenum RGB9_E5 = 0x8C3D; + const GLenum UNSIGNED_INT_5_9_9_9_REV = 0x8C3E; + const GLenum TRANSFORM_FEEDBACK_BUFFER_MODE = 0x8C7F; + const GLenum MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS = 0x8C80; + const GLenum TRANSFORM_FEEDBACK_VARYINGS = 0x8C83; + const GLenum TRANSFORM_FEEDBACK_BUFFER_START = 0x8C84; + const GLenum TRANSFORM_FEEDBACK_BUFFER_SIZE = 0x8C85; + const GLenum TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN = 0x8C88; + const GLenum RASTERIZER_DISCARD = 0x8C89; + const GLenum MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS = 0x8C8A; + const GLenum MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = 0x8C8B; + const GLenum INTERLEAVED_ATTRIBS = 0x8C8C; + const GLenum SEPARATE_ATTRIBS = 0x8C8D; + const GLenum TRANSFORM_FEEDBACK_BUFFER = 0x8C8E; + const GLenum TRANSFORM_FEEDBACK_BUFFER_BINDING = 0x8C8F; + const GLenum RGBA32UI = 0x8D70; + const GLenum RGB32UI = 0x8D71; + const GLenum RGBA16UI = 0x8D76; + const GLenum RGB16UI = 0x8D77; + const GLenum RGBA8UI = 0x8D7C; + const GLenum RGB8UI = 0x8D7D; + const GLenum RGBA32I = 0x8D82; + const GLenum RGB32I = 0x8D83; + const GLenum RGBA16I = 0x8D88; + const GLenum RGB16I = 0x8D89; + const GLenum RGBA8I = 0x8D8E; + const GLenum RGB8I = 0x8D8F; + const GLenum RED_INTEGER = 0x8D94; + const GLenum RGB_INTEGER = 0x8D98; + const GLenum RGBA_INTEGER = 0x8D99; + const GLenum SAMPLER_2D_ARRAY = 0x8DC1; + const GLenum SAMPLER_2D_ARRAY_SHADOW = 0x8DC4; + const GLenum SAMPLER_CUBE_SHADOW = 0x8DC5; + const GLenum UNSIGNED_INT_VEC2 = 0x8DC6; + const GLenum UNSIGNED_INT_VEC3 = 0x8DC7; + const GLenum UNSIGNED_INT_VEC4 = 0x8DC8; + const GLenum INT_SAMPLER_2D = 0x8DCA; + const GLenum INT_SAMPLER_3D = 0x8DCB; + const GLenum INT_SAMPLER_CUBE = 0x8DCC; + const GLenum INT_SAMPLER_2D_ARRAY = 0x8DCF; + const GLenum UNSIGNED_INT_SAMPLER_2D = 0x8DD2; + const GLenum UNSIGNED_INT_SAMPLER_3D = 0x8DD3; + const GLenum UNSIGNED_INT_SAMPLER_CUBE = 0x8DD4; + const GLenum UNSIGNED_INT_SAMPLER_2D_ARRAY = 0x8DD7; + const GLenum DEPTH_COMPONENT32F = 0x8CAC; + const GLenum DEPTH32F_STENCIL8 = 0x8CAD; + const GLenum FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD; + const GLenum FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210; + const GLenum FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE = 0x8211; + const GLenum FRAMEBUFFER_ATTACHMENT_RED_SIZE = 0x8212; + const GLenum FRAMEBUFFER_ATTACHMENT_GREEN_SIZE = 0x8213; + const GLenum FRAMEBUFFER_ATTACHMENT_BLUE_SIZE = 0x8214; + const GLenum FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE = 0x8215; + const GLenum FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE = 0x8216; + const GLenum FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE = 0x8217; + const GLenum FRAMEBUFFER_DEFAULT = 0x8218; + // BUG: https://github.com/KhronosGroup/WebGL/issues/2216 + // const GLenum DEPTH_STENCIL_ATTACHMENT = 0x821A; + // const GLenum DEPTH_STENCIL = 0x84F9; + const GLenum UNSIGNED_INT_24_8 = 0x84FA; + const GLenum DEPTH24_STENCIL8 = 0x88F0; + const GLenum UNSIGNED_NORMALIZED = 0x8C17; + const GLenum DRAW_FRAMEBUFFER_BINDING = 0x8CA6; /* Same as FRAMEBUFFER_BINDING */ + const GLenum READ_FRAMEBUFFER = 0x8CA8; + const GLenum DRAW_FRAMEBUFFER = 0x8CA9; + const GLenum READ_FRAMEBUFFER_BINDING = 0x8CAA; + const GLenum RENDERBUFFER_SAMPLES = 0x8CAB; + const GLenum FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER = 0x8CD4; + const GLenum MAX_COLOR_ATTACHMENTS = 0x8CDF; + const GLenum COLOR_ATTACHMENT1 = 0x8CE1; + const GLenum COLOR_ATTACHMENT2 = 0x8CE2; + const GLenum COLOR_ATTACHMENT3 = 0x8CE3; + const GLenum COLOR_ATTACHMENT4 = 0x8CE4; + const GLenum COLOR_ATTACHMENT5 = 0x8CE5; + const GLenum COLOR_ATTACHMENT6 = 0x8CE6; + const GLenum COLOR_ATTACHMENT7 = 0x8CE7; + const GLenum COLOR_ATTACHMENT8 = 0x8CE8; + const GLenum COLOR_ATTACHMENT9 = 0x8CE9; + const GLenum COLOR_ATTACHMENT10 = 0x8CEA; + const GLenum COLOR_ATTACHMENT11 = 0x8CEB; + const GLenum COLOR_ATTACHMENT12 = 0x8CEC; + const GLenum COLOR_ATTACHMENT13 = 0x8CED; + const GLenum COLOR_ATTACHMENT14 = 0x8CEE; + const GLenum COLOR_ATTACHMENT15 = 0x8CEF; + const GLenum FRAMEBUFFER_INCOMPLETE_MULTISAMPLE = 0x8D56; + const GLenum MAX_SAMPLES = 0x8D57; + const GLenum HALF_FLOAT = 0x140B; + const GLenum RG = 0x8227; + const GLenum RG_INTEGER = 0x8228; + const GLenum R8 = 0x8229; + const GLenum RG8 = 0x822B; + const GLenum R16F = 0x822D; + const GLenum R32F = 0x822E; + const GLenum RG16F = 0x822F; + const GLenum RG32F = 0x8230; + const GLenum R8I = 0x8231; + const GLenum R8UI = 0x8232; + const GLenum R16I = 0x8233; + const GLenum R16UI = 0x8234; + const GLenum R32I = 0x8235; + const GLenum R32UI = 0x8236; + const GLenum RG8I = 0x8237; + const GLenum RG8UI = 0x8238; + const GLenum RG16I = 0x8239; + const GLenum RG16UI = 0x823A; + const GLenum RG32I = 0x823B; + const GLenum RG32UI = 0x823C; + const GLenum VERTEX_ARRAY_BINDING = 0x85B5; + const GLenum R8_SNORM = 0x8F94; + const GLenum RG8_SNORM = 0x8F95; + const GLenum RGB8_SNORM = 0x8F96; + const GLenum RGBA8_SNORM = 0x8F97; + const GLenum SIGNED_NORMALIZED = 0x8F9C; + const GLenum COPY_READ_BUFFER = 0x8F36; + const GLenum COPY_WRITE_BUFFER = 0x8F37; + const GLenum COPY_READ_BUFFER_BINDING = 0x8F36; /* Same as COPY_READ_BUFFER */ + const GLenum COPY_WRITE_BUFFER_BINDING = 0x8F37; /* Same as COPY_WRITE_BUFFER */ + const GLenum UNIFORM_BUFFER = 0x8A11; + const GLenum UNIFORM_BUFFER_BINDING = 0x8A28; + const GLenum UNIFORM_BUFFER_START = 0x8A29; + const GLenum UNIFORM_BUFFER_SIZE = 0x8A2A; + const GLenum MAX_VERTEX_UNIFORM_BLOCKS = 0x8A2B; + const GLenum MAX_FRAGMENT_UNIFORM_BLOCKS = 0x8A2D; + const GLenum MAX_COMBINED_UNIFORM_BLOCKS = 0x8A2E; + const GLenum MAX_UNIFORM_BUFFER_BINDINGS = 0x8A2F; + const GLenum MAX_UNIFORM_BLOCK_SIZE = 0x8A30; + const GLenum MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS = 0x8A31; + const GLenum MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS = 0x8A33; + const GLenum UNIFORM_BUFFER_OFFSET_ALIGNMENT = 0x8A34; + const GLenum ACTIVE_UNIFORM_BLOCKS = 0x8A36; + const GLenum UNIFORM_TYPE = 0x8A37; + const GLenum UNIFORM_SIZE = 0x8A38; + const GLenum UNIFORM_BLOCK_INDEX = 0x8A3A; + const GLenum UNIFORM_OFFSET = 0x8A3B; + const GLenum UNIFORM_ARRAY_STRIDE = 0x8A3C; + const GLenum UNIFORM_MATRIX_STRIDE = 0x8A3D; + const GLenum UNIFORM_IS_ROW_MAJOR = 0x8A3E; + const GLenum UNIFORM_BLOCK_BINDING = 0x8A3F; + const GLenum UNIFORM_BLOCK_DATA_SIZE = 0x8A40; + const GLenum UNIFORM_BLOCK_ACTIVE_UNIFORMS = 0x8A42; + const GLenum UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES = 0x8A43; + const GLenum UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER = 0x8A44; + const GLenum UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER = 0x8A46; + const GLenum INVALID_INDEX = 0xFFFFFFFF; + const GLenum MAX_VERTEX_OUTPUT_COMPONENTS = 0x9122; + const GLenum MAX_FRAGMENT_INPUT_COMPONENTS = 0x9125; + const GLenum MAX_SERVER_WAIT_TIMEOUT = 0x9111; + const GLenum OBJECT_TYPE = 0x9112; + const GLenum SYNC_CONDITION = 0x9113; + const GLenum SYNC_STATUS = 0x9114; + const GLenum SYNC_FLAGS = 0x9115; + const GLenum SYNC_FENCE = 0x9116; + const GLenum SYNC_GPU_COMMANDS_COMPLETE = 0x9117; + const GLenum UNSIGNALED = 0x9118; + const GLenum SIGNALED = 0x9119; + const GLenum ALREADY_SIGNALED = 0x911A; + const GLenum TIMEOUT_EXPIRED = 0x911B; + const GLenum CONDITION_SATISFIED = 0x911C; + const GLenum WAIT_FAILED = 0x911D; + const GLenum SYNC_FLUSH_COMMANDS_BIT = 0x00000001; + const GLenum VERTEX_ATTRIB_ARRAY_DIVISOR = 0x88FE; + const GLenum ANY_SAMPLES_PASSED = 0x8C2F; + const GLenum ANY_SAMPLES_PASSED_CONSERVATIVE = 0x8D6A; + const GLenum SAMPLER_BINDING = 0x8919; + const GLenum RGB10_A2UI = 0x906F; + const GLenum INT_2_10_10_10_REV = 0x8D9F; + const GLenum TRANSFORM_FEEDBACK = 0x8E22; + const GLenum TRANSFORM_FEEDBACK_PAUSED = 0x8E23; + const GLenum TRANSFORM_FEEDBACK_ACTIVE = 0x8E24; + const GLenum TRANSFORM_FEEDBACK_BINDING = 0x8E25; + const GLenum TEXTURE_IMMUTABLE_FORMAT = 0x912F; + const GLenum MAX_ELEMENT_INDEX = 0x8D6B; + const GLenum TEXTURE_IMMUTABLE_LEVELS = 0x82DF; + + const GLint64 TIMEOUT_IGNORED = -1; + + /* WebGL-specific enums */ + const GLenum MAX_CLIENT_WAIT_TIMEOUT_WEBGL = 0x9247; + + /* Buffer objects */ + void copyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, + GLintptr writeOffset, GLsizeiptr size); + // MapBufferRange, in particular its read-only and write-only modes, + // can not be exposed safely to JavaScript. GetBufferSubData + // replaces it for the purpose of fetching data back from the GPU. + void getBufferSubData(GLenum target, GLintptr srcByteOffset, /*[AllowShared]*/ ArrayBufferView dstBuffer, + optional GLuint dstOffset = 0, optional GLuint length = 0); + + /* Framebuffer objects */ + // void blitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, + // GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); + void framebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture? texture, GLint level, + GLint layer); + void invalidateFramebuffer(GLenum target, sequence<GLenum> attachments); + void invalidateSubFramebuffer(GLenum target, sequence<GLenum> attachments, + GLint x, GLint y, GLsizei width, GLsizei height); + void readBuffer(GLenum src); + + /* Renderbuffer objects */ + any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname); + void renderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, + GLsizei width, GLsizei height); + + /* Texture objects */ + void texStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, + GLsizei height); + void texStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, + GLsizei height, GLsizei depth); + + //[Throws] + //void texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + // GLsizei depth, GLint border, GLenum format, GLenum type, GLintptr pboOffset); + //[Throws] + //void texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + // GLsizei depth, GLint border, GLenum format, GLenum type, + // TexImageSource source); // May throw DOMException + //[Throws] + //void texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + // GLsizei depth, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView? srcData); + //[Throws] + //void texImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + // GLsizei depth, GLint border, GLenum format, GLenum type, [AllowShared] ArrayBufferView srcData, + // GLuint srcOffset); + + //[Throws] + //void texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + // GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, + // GLintptr pboOffset); + //[Throws] + //void texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + // GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, + // TexImageSource source); // May throw DOMException + //[Throws] + //void texSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + // GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, + // [AllowShared] ArrayBufferView? srcData, optional GLuint srcOffset = 0); + + //void copyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + // GLint x, GLint y, GLsizei width, GLsizei height); + + //void compressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, + // GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, GLintptr offset); + //void compressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, + // GLsizei height, GLsizei depth, GLint border, [AllowShared] ArrayBufferView srcData, + // optional GLuint srcOffset = 0, optional GLuint srcLengthOverride = 0); + + //void compressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + // GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, + // GLenum format, GLsizei imageSize, GLintptr offset); + //void compressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + // GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, + // GLenum format, [AllowShared] ArrayBufferView srcData, + // optional GLuint srcOffset = 0, + // optional GLuint srcLengthOverride = 0); + + /* Programs and shaders */ + [WebGLHandlesContextLoss] GLint getFragDataLocation(WebGLProgram program, DOMString name); + + /* Uniforms */ + void uniform1ui(WebGLUniformLocation? location, GLuint v0); + void uniform2ui(WebGLUniformLocation? location, GLuint v0, GLuint v1); + void uniform3ui(WebGLUniformLocation? location, GLuint v0, GLuint v1, GLuint v2); + void uniform4ui(WebGLUniformLocation? location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); + + void uniform1uiv(WebGLUniformLocation? location, Uint32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform2uiv(WebGLUniformLocation? location, Uint32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform3uiv(WebGLUniformLocation? location, Uint32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform4uiv(WebGLUniformLocation? location, Uint32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + + void uniformMatrix3x2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix4x2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + + void uniformMatrix2x3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix4x3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + + void uniformMatrix2x4fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix3x4fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + + /* Vertex attribs */ + void vertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w); + void vertexAttribI4iv(GLuint index, Int32List values); + void vertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); + void vertexAttribI4uiv(GLuint index, Uint32List values); + void vertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); + + /* Writing to the drawing buffer */ + void vertexAttribDivisor(GLuint index, GLuint divisor); + void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount); + void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei instanceCount); + void drawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, GLintptr offset); + + /* Multiple Render Targets */ + void drawBuffers(sequence<GLenum> buffers); + + void clearBufferfv(GLenum buffer, GLint drawbuffer, Float32List values, + optional GLuint srcOffset = 0); + void clearBufferiv(GLenum buffer, GLint drawbuffer, Int32List values, + optional GLuint srcOffset = 0); + void clearBufferuiv(GLenum buffer, GLint drawbuffer, Uint32List values, + optional GLuint srcOffset = 0); + + void clearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); + + /* Query Objects */ + WebGLQuery? createQuery(); + void deleteQuery(WebGLQuery? query); + /*[WebGLHandlesContextLoss]*/ GLboolean isQuery(WebGLQuery? query); + void beginQuery(GLenum target, WebGLQuery query); + void endQuery(GLenum target); + WebGLQuery? getQuery(GLenum target, GLenum pname); + any getQueryParameter(WebGLQuery query, GLenum pname); + + /* Sampler Objects */ + WebGLSampler? createSampler(); + void deleteSampler(WebGLSampler? sampler); + [WebGLHandlesContextLoss] GLboolean isSampler(WebGLSampler? sampler); + void bindSampler(GLuint unit, WebGLSampler? sampler); + void samplerParameteri(WebGLSampler sampler, GLenum pname, GLint param); + void samplerParameterf(WebGLSampler sampler, GLenum pname, GLfloat param); + any getSamplerParameter(WebGLSampler sampler, GLenum pname); + + /* Sync objects */ + WebGLSync? fenceSync(GLenum condition, GLbitfield flags); + [WebGLHandlesContextLoss] GLboolean isSync(WebGLSync? sync); + void deleteSync(WebGLSync? sync); + GLenum clientWaitSync(WebGLSync sync, GLbitfield flags, GLuint64 timeout); + void waitSync(WebGLSync sync, GLbitfield flags, GLint64 timeout); + any getSyncParameter(WebGLSync sync, GLenum pname); + + /* Transform Feedback */ + WebGLTransformFeedback? createTransformFeedback(); + void deleteTransformFeedback(WebGLTransformFeedback? tf); + [WebGLHandlesContextLoss] GLboolean isTransformFeedback(WebGLTransformFeedback? tf); + void bindTransformFeedback (GLenum target, WebGLTransformFeedback? tf); + void beginTransformFeedback(GLenum primitiveMode); + void endTransformFeedback(); + void transformFeedbackVaryings(WebGLProgram program, sequence<DOMString> varyings, GLenum bufferMode); + WebGLActiveInfo? getTransformFeedbackVarying(WebGLProgram program, GLuint index); + void pauseTransformFeedback(); + void resumeTransformFeedback(); + + /* Uniform Buffer Objects and Transform Feedback Buffers */ + void bindBufferBase(GLenum target, GLuint index, WebGLBuffer? buffer); + void bindBufferRange(GLenum target, GLuint index, WebGLBuffer? buffer, GLintptr offset, GLsizeiptr size); + any getIndexedParameter(GLenum target, GLuint index); + sequence<GLuint>? getUniformIndices(WebGLProgram program, sequence<DOMString> uniformNames); + any getActiveUniforms(WebGLProgram program, sequence<GLuint> uniformIndices, GLenum pname); + GLuint getUniformBlockIndex(WebGLProgram program, DOMString uniformBlockName); + any getActiveUniformBlockParameter(WebGLProgram program, GLuint uniformBlockIndex, GLenum pname); + DOMString? getActiveUniformBlockName(WebGLProgram program, GLuint uniformBlockIndex); + void uniformBlockBinding(WebGLProgram program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); + + /* Vertex Array Objects */ + WebGLVertexArrayObject? createVertexArray(); + void deleteVertexArray(WebGLVertexArrayObject? vertexArray); + [WebGLHandlesContextLoss] GLboolean isVertexArray(WebGLVertexArrayObject? vertexArray); + void bindVertexArray(WebGLVertexArrayObject? array); +}; + +interface mixin WebGL2RenderingContextOverloads +{ + // WebGL1: + void bufferData(GLenum target, GLsizeiptr size, GLenum usage); + void bufferData(GLenum target, /*[AllowShared]*/ BufferSource? srcData, GLenum usage); + void bufferSubData(GLenum target, GLintptr dstByteOffset, /*[AllowShared]*/ BufferSource srcData); + // WebGL2: + void bufferData(GLenum target, /*[AllowShared]*/ ArrayBufferView srcData, GLenum usage, GLuint srcOffset, + optional GLuint length = 0); + void bufferSubData(GLenum target, GLintptr dstByteOffset, /*[AllowShared]*/ ArrayBufferView srcData, + GLuint srcOffset, optional GLuint length = 0); + + // WebGL1 legacy entrypoints: + [Throws] + void texImage2D(GLenum target, GLint level, GLint internalformat, + GLsizei width, GLsizei height, GLint border, GLenum format, + GLenum type, /*[AllowShared]*/ ArrayBufferView? pixels); + [Throws] + void texImage2D(GLenum target, GLint level, GLint internalformat, + GLenum format, GLenum type, TexImageSource source); // May throw DOMException + + [Throws] + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, + GLenum format, GLenum type, /*[AllowShared]*/ ArrayBufferView? pixels); + [Throws] + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLenum format, GLenum type, TexImageSource source); // May throw DOMException + + // WebGL2 entrypoints: + [Throws] + void texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, GLintptr pboOffset); + [Throws] + void texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, + TexImageSource source); // May throw DOMException + [Throws] + void texImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, + GLint border, GLenum format, GLenum type, /*[AllowShared]*/ ArrayBufferView srcData, + GLuint srcOffset); + + //[Throws] + //void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, + // GLsizei height, GLenum format, GLenum type, GLintptr pboOffset); + //[Throws] + //void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, + // GLsizei height, GLenum format, GLenum type, + // TexImageSource source); // May throw DOMException + //[Throws] + //void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, + // GLsizei height, GLenum format, GLenum type, /*[AllowShared]*/ ArrayBufferView srcData, + // GLuint srcOffset); + + //void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, + // GLsizei height, GLint border, GLsizei imageSize, GLintptr offset); + void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, + GLsizei height, GLint border, /*[AllowShared]*/ ArrayBufferView srcData, + optional GLuint srcOffset = 0, optional GLuint srcLengthOverride = 0); + + //void compressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + // GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, GLintptr offset); + void compressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, + /*[AllowShared]*/ ArrayBufferView srcData, + optional GLuint srcOffset = 0, + optional GLuint srcLengthOverride = 0); + + void uniform1fv(WebGLUniformLocation? location, Float32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform2fv(WebGLUniformLocation? location, Float32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform3fv(WebGLUniformLocation? location, Float32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform4fv(WebGLUniformLocation? location, Float32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + + void uniform1iv(WebGLUniformLocation? location, Int32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform2iv(WebGLUniformLocation? location, Int32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform3iv(WebGLUniformLocation? location, Int32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + void uniform4iv(WebGLUniformLocation? location, Int32List data, optional GLuint srcOffset = 0, + optional GLuint srcLength = 0); + + void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, Float32List data, + optional GLuint srcOffset = 0, optional GLuint srcLength = 0); + + /* Reading back pixels */ + // WebGL1: + void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + /*[AllowShared]*/ ArrayBufferView? dstData); + // WebGL2: + void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + GLintptr offset); + void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + /*[AllowShared]*/ ArrayBufferView dstData, GLuint dstOffset); +}; + +[Exposed=Window, Func="WebGL2RenderingContext::is_webgl2_enabled"] +interface WebGL2RenderingContext +{ +}; +WebGL2RenderingContext includes WebGLRenderingContextBase; +WebGL2RenderingContext includes WebGL2RenderingContextBase; +WebGL2RenderingContext includes WebGL2RenderingContextOverloads; diff --git a/components/script/dom/webidls/WebGLActiveInfo.webidl b/components/script/dom/webidls/WebGLActiveInfo.webidl index be9e6f4e2db..42e703db088 100644 --- a/components/script/dom/webidls/WebGLActiveInfo.webidl +++ b/components/script/dom/webidls/WebGLActiveInfo.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.7 diff --git a/components/script/dom/webidls/WebGLBuffer.webidl b/components/script/dom/webidls/WebGLBuffer.webidl index ca2697bb9be..c182be1415d 100644 --- a/components/script/dom/webidls/WebGLBuffer.webidl +++ b/components/script/dom/webidls/WebGLBuffer.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.4 diff --git a/components/script/dom/webidls/WebGLContextEvent.webidl b/components/script/dom/webidls/WebGLContextEvent.webidl index b5c70b8b17f..5c6a1c4f1d7 100644 --- a/components/script/dom/webidls/WebGLContextEvent.webidl +++ b/components/script/dom/webidls/WebGLContextEvent.webidl @@ -1,11 +1,11 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15 -[Constructor(DOMString type, optional WebGLContextEventInit eventInit), - Exposed=Window] +[Exposed=Window] interface WebGLContextEvent : Event { + [Throws] constructor(DOMString type, optional WebGLContextEventInit eventInit = {}); readonly attribute DOMString statusMessage; }; diff --git a/components/script/dom/webidls/WebGLFramebuffer.webidl b/components/script/dom/webidls/WebGLFramebuffer.webidl index 306e2c479ed..b036b752161 100644 --- a/components/script/dom/webidls/WebGLFramebuffer.webidl +++ b/components/script/dom/webidls/WebGLFramebuffer.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.7 diff --git a/components/script/dom/webidls/WebGLObject.webidl b/components/script/dom/webidls/WebGLObject.webidl index 3e8f1f54cca..90feba0ab6a 100644 --- a/components/script/dom/webidls/WebGLObject.webidl +++ b/components/script/dom/webidls/WebGLObject.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.3 diff --git a/components/script/dom/webidls/WebGLProgram.webidl b/components/script/dom/webidls/WebGLProgram.webidl index 2ee21b2a6a1..29125b57b84 100644 --- a/components/script/dom/webidls/WebGLProgram.webidl +++ b/components/script/dom/webidls/WebGLProgram.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.6 diff --git a/components/script/dom/webidls/WebGLQuery.webidl b/components/script/dom/webidls/WebGLQuery.webidl new file mode 100644 index 00000000000..04b3711dd86 --- /dev/null +++ b/components/script/dom/webidls/WebGLQuery.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +// +// WebGL IDL definitions scraped from the Khronos specification: +// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.8 +// + +[Exposed=Window, Pref="dom.webgl2.enabled"] +interface WebGLQuery : WebGLObject { +}; diff --git a/components/script/dom/webidls/WebGLRenderbuffer.webidl b/components/script/dom/webidls/WebGLRenderbuffer.webidl index 3024dc7513e..465f193aaac 100644 --- a/components/script/dom/webidls/WebGLRenderbuffer.webidl +++ b/components/script/dom/webidls/WebGLRenderbuffer.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.5 diff --git a/components/script/dom/webidls/WebGLRenderingContext.webidl b/components/script/dom/webidls/WebGLRenderingContext.webidl index 0d4c14a05df..4003ab86ba5 100644 --- a/components/script/dom/webidls/WebGLRenderingContext.webidl +++ b/components/script/dom/webidls/WebGLRenderingContext.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/ @@ -29,6 +29,8 @@ typedef (ImageData or HTMLCanvasElement or HTMLVideoElement) TexImageSource; +typedef (/*[AllowShared]*/ Float32Array or sequence<GLfloat>) Float32List; +typedef (/*[AllowShared]*/ Int32Array or sequence<GLint>) Int32List; dictionary WebGLContextAttributes { GLboolean alpha = true; @@ -41,8 +43,8 @@ dictionary WebGLContextAttributes { GLboolean failIfMajorPerformanceCaveat = false; }; -[Exposed=Window, NoInterfaceObject] -interface WebGLRenderingContextBase +[Exposed=Window] +interface mixin WebGLRenderingContextBase { /* ClearBufferMask */ @@ -419,7 +421,6 @@ interface WebGLRenderingContextBase const GLenum RGB5_A1 = 0x8057; const GLenum RGB565 = 0x8D62; const GLenum DEPTH_COMPONENT16 = 0x81A5; - const GLenum STENCIL_INDEX = 0x1901; const GLenum STENCIL_INDEX8 = 0x8D48; const GLenum DEPTH_STENCIL = 0x84F9; @@ -469,14 +470,14 @@ interface WebGLRenderingContextBase readonly attribute GLsizei drawingBufferHeight; [WebGLHandlesContextLoss] WebGLContextAttributes? getContextAttributes(); - //[WebGLHandlesContextLoss] boolean isContextLost(); + [WebGLHandlesContextLoss] boolean isContextLost(); sequence<DOMString>? getSupportedExtensions(); object? getExtension(DOMString name); void activeTexture(GLenum texture); - void attachShader(WebGLProgram? program, WebGLShader? shader); - void bindAttribLocation(WebGLProgram? program, GLuint index, DOMString name); + void attachShader(WebGLProgram program, WebGLShader shader); + void bindAttribLocation(WebGLProgram program, GLuint index, DOMString name); void bindBuffer(GLenum target, WebGLBuffer? buffer); void bindFramebuffer(GLenum target, WebGLFramebuffer? framebuffer); void bindRenderbuffer(GLenum target, WebGLRenderbuffer? renderbuffer); @@ -488,55 +489,17 @@ interface WebGLRenderingContextBase void blendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); - // typedef (ArrayBuffer or ArrayBufferView) BufferDataSource; - // FIXME(dmarcos) The function below is the original function in the webIdl: - // void bufferData(GLenum target, BufferDataSource? data, GLenum usage); - // The Code generator doesn't handle BufferDataSource so we're using 'object?' - // in the meantime, and marking the function as [Throws], so we can handle - // the type error from inside. - [Throws] - void bufferData(GLenum target, object? data, GLenum usage); - // FIXME: Codegen requires that this have [Throws] to match the other one. - [Throws] - void bufferData(GLenum target, GLsizeiptr size, GLenum usage); - - //void bufferSubData(GLenum target, GLintptr offset, BufferDataSource? data); - [Throws] - void bufferSubData(GLenum target, GLintptr offset, object? data); - [WebGLHandlesContextLoss] GLenum checkFramebufferStatus(GLenum target); void clear(GLbitfield mask); void clearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); void clearDepth(GLclampf depth); void clearStencil(GLint s); void colorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); - void compileShader(WebGLShader? shader); - - // FIXME(simartin) The Code generator doesn't handle ArrayBufferView so we're - // using 'object' in the meantime, and marking the function as Throws to - // handle the type error from inside. - // void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, - // GLsizei width, GLsizei height, GLint border, - // ArrayBufferView data); - [Throws] - void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, - GLsizei width, GLsizei height, GLint border, - object data); - // void compressedTexSubImage2D(GLenum target, GLint level, - // GLint xoffset, GLint yoffset, - // GLsizei width, GLsizei height, GLenum format, - // ArrayBufferView data); - [Throws] - void compressedTexSubImage2D(GLenum target, GLint level, - GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, GLenum format, - object data); + void compileShader(WebGLShader shader); - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 void copyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8 void copyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); @@ -559,7 +522,7 @@ interface WebGLRenderingContextBase void depthFunc(GLenum func); void depthMask(GLboolean flag); void depthRange(GLclampf zNear, GLclampf zFar); - void detachShader(WebGLProgram? program, WebGLShader? shader); + void detachShader(WebGLProgram program, WebGLShader shader); void disable(GLenum cap); void disableVertexAttribArray(GLuint index); void drawArrays(GLenum mode, GLint first, GLsizei count); @@ -578,37 +541,37 @@ interface WebGLRenderingContextBase void generateMipmap(GLenum target); - WebGLActiveInfo? getActiveAttrib(WebGLProgram? program, GLuint index); - WebGLActiveInfo? getActiveUniform(WebGLProgram? program, GLuint index); - //sequence<WebGLShader>? getAttachedShaders(WebGLProgram? program); + WebGLActiveInfo? getActiveAttrib(WebGLProgram program, GLuint index); + WebGLActiveInfo? getActiveUniform(WebGLProgram program, GLuint index); + sequence<WebGLShader>? getAttachedShaders(WebGLProgram program); - [WebGLHandlesContextLoss] GLint getAttribLocation(WebGLProgram? program, DOMString name); + [WebGLHandlesContextLoss] GLint getAttribLocation(WebGLProgram program, DOMString name); any getBufferParameter(GLenum target, GLenum pname); any getParameter(GLenum pname); [WebGLHandlesContextLoss] GLenum getError(); - //any getFramebufferAttachmentParameter(GLenum target, GLenum attachment, - // GLenum pname); - any getProgramParameter(WebGLProgram? program, GLenum pname); - DOMString? getProgramInfoLog(WebGLProgram? program); - //any getRenderbufferParameter(GLenum target, GLenum pname); - any getShaderParameter(WebGLShader? shader, GLenum pname); + any getFramebufferAttachmentParameter(GLenum target, GLenum attachment, + GLenum pname); + any getProgramParameter(WebGLProgram program, GLenum pname); + DOMString? getProgramInfoLog(WebGLProgram program); + any getRenderbufferParameter(GLenum target, GLenum pname); + any getShaderParameter(WebGLShader shader, GLenum pname); WebGLShaderPrecisionFormat? getShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype); - DOMString? getShaderInfoLog(WebGLShader? shader); + DOMString? getShaderInfoLog(WebGLShader shader); - DOMString? getShaderSource(WebGLShader? shader); + DOMString? getShaderSource(WebGLShader shader); - //any getTexParameter(GLenum target, GLenum pname); + any getTexParameter(GLenum target, GLenum pname); - //any getUniform(WebGLProgram? program, WebGLUniformLocation? location); + any getUniform(WebGLProgram program, WebGLUniformLocation location); - WebGLUniformLocation? getUniformLocation(WebGLProgram? program, DOMString name); + WebGLUniformLocation? getUniformLocation(WebGLProgram program, DOMString name); any getVertexAttrib(GLuint index, GLenum pname); - //[WebGLHandlesContextLoss] GLsizeiptr getVertexAttribOffset(GLuint index, GLenum pname); + [WebGLHandlesContextLoss] GLsizeiptr getVertexAttribOffset(GLuint index, GLenum pname); void hint(GLenum target, GLenum mode); [WebGLHandlesContextLoss] GLboolean isBuffer(WebGLBuffer? buffer); @@ -619,22 +582,16 @@ interface WebGLRenderingContextBase [WebGLHandlesContextLoss] GLboolean isShader(WebGLShader? shader); [WebGLHandlesContextLoss] GLboolean isTexture(WebGLTexture? texture); void lineWidth(GLfloat width); - void linkProgram(WebGLProgram? program); + void linkProgram(WebGLProgram program); void pixelStorei(GLenum pname, GLint param); void polygonOffset(GLfloat factor, GLfloat units); - //void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, - // GLenum format, GLenum type, ArrayBufferView? pixels); - [Throws] - void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, - GLenum format, GLenum type, object? pixels); - void renderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); void sampleCoverage(GLclampf value, GLboolean invert); void scissor(GLint x, GLint y, GLsizei width, GLsizei height); - void shaderSource(WebGLShader? shader, DOMString source); + void shaderSource(WebGLShader shader, DOMString source); void stencilFunc(GLenum func, GLint ref, GLuint mask); void stencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask); @@ -643,130 +600,96 @@ interface WebGLRenderingContextBase void stencilOp(GLenum fail, GLenum zfail, GLenum zpass); void stencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass); - //void texImage2D(GLenum target, GLint level, GLenum internalformat, - // GLsizei width, GLsizei height, GLint border, GLenum format, - // GLenum type, ArrayBufferView? pixels); - // FIXME: SM interface arguments - [Throws] - void texImage2D(GLenum target, GLint level, GLenum internalformat, - GLsizei width, GLsizei height, GLint border, GLenum format, - GLenum type, object? data); - [Throws] - void texImage2D(GLenum target, GLint level, GLenum internalformat, - GLenum format, GLenum type, TexImageSource? source); // May throw DOMException - void texParameterf(GLenum target, GLenum pname, GLfloat param); void texParameteri(GLenum target, GLenum pname, GLint param); - [Throws] - void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, - GLenum format, GLenum type, object? data); - [Throws] - void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLenum format, GLenum type, TexImageSource? source); // May throw DOMException - void uniform1f(WebGLUniformLocation? location, GLfloat x); - //void uniform1fv(WebGLUniformLocation? location, Float32Array v); - //void uniform1fv(WebGLUniformLocation? location, sequence<GLfloat> v); - [Throws] - void uniform1fv(WebGLUniformLocation? location, object v); - void uniform1i(WebGLUniformLocation? location, GLint x); - //void uniform1iv(WebGLUniformLocation? location, Int32Array v); - //void uniform1iv(WebGLUniformLocation? location, sequence<long> v); - [Throws] - void uniform1iv(WebGLUniformLocation? location, object v); void uniform2f(WebGLUniformLocation? location, GLfloat x, GLfloat y); - //void uniform2fv(WebGLUniformLocation? location, Float32Array v); - //void uniform2fv(WebGLUniformLocation? location, sequence<GLfloat> v); - [Throws] - void uniform2fv(WebGLUniformLocation? location, object v); - //void uniform2i(WebGLUniformLocation? location, GLint x, GLint y); - void uniform2i(WebGLUniformLocation? location, GLint x, GLint y); - //void uniform2iv(WebGLUniformLocation? location, Int32Array v); - //void uniform2iv(WebGLUniformLocation? location, sequence<long> v); - [Throws] - void uniform2iv(WebGLUniformLocation? location, object v); void uniform3f(WebGLUniformLocation? location, GLfloat x, GLfloat y, GLfloat z); - [Throws] - void uniform3fv(WebGLUniformLocation? location, object v); - //void uniform3fv(WebGLUniformLocation? location, Float32Array v); - //void uniform3fv(WebGLUniformLocation? location, sequence<GLfloat> v); - void uniform3i(WebGLUniformLocation? location, GLint x, GLint y, GLint z); - //void uniform3iv(WebGLUniformLocation? location, Int32Array v); - //void uniform3iv(WebGLUniformLocation? location, sequence<long> v); - [Throws] - void uniform3iv(WebGLUniformLocation? location, object v); void uniform4f(WebGLUniformLocation? location, GLfloat x, GLfloat y, GLfloat z, GLfloat w); - // FIXME(dmarcos) The function below is the original function in the webIdl: - //void uniform4fv(WebGLUniformLocation? location, Float32Array v); - // The Code genearator doesn't handle typed arrays, so we use object - // instead, and handle the type error ourselves. - [Throws] - void uniform4fv(WebGLUniformLocation? location, object v); - //void uniform4fv(WebGLUniformLocation? location, sequence<GLfloat> v); - void uniform4i(WebGLUniformLocation? location, GLint x, GLint y, GLint z, GLint w); - //void uniform4iv(WebGLUniformLocation? location, Int32Array v); - //void uniform4iv(WebGLUniformLocation? location, sequence<long> v); - // See FIXME above - [Throws] - void uniform4iv(WebGLUniformLocation? location, object v); - //void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, - // Float32Array value); - //void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, - // sequence<GLfloat> value); - [Throws] - void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, - object v); - //void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, - // Float32Array value); - //void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, - // sequence<GLfloat> value); - [Throws] - void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, - object v); - //void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, - // Float32Array value); - //void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, - // sequence<GLfloat> value); - [Throws] - void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, - object v); + void uniform1i(WebGLUniformLocation? location, GLint x); + void uniform2i(WebGLUniformLocation? location, GLint x, GLint y); + void uniform3i(WebGLUniformLocation? location, GLint x, GLint y, GLint z); + void uniform4i(WebGLUniformLocation? location, GLint x, GLint y, GLint z, GLint w); void useProgram(WebGLProgram? program); - void validateProgram(WebGLProgram? program); + void validateProgram(WebGLProgram program); - // FIXME(dmarcos) - // The code generator doesn't handle Float32Array so we're using 'object' void vertexAttrib1f(GLuint indx, GLfloat x); - //void vertexAttrib1fv(GLuint indx, Float32Array values); - [Throws] - void vertexAttrib1fv(GLuint indx, object values); - //void vertexAttrib1fv(GLuint indx, sequence<GLfloat> values); void vertexAttrib2f(GLuint indx, GLfloat x, GLfloat y); - //void vertexAttrib2fv(GLuint indx, Float32Array values); - [Throws] - void vertexAttrib2fv(GLuint indx, object values); - //void vertexAttrib2fv(GLuint indx, sequence<GLfloat> values); void vertexAttrib3f(GLuint indx, GLfloat x, GLfloat y, GLfloat z); - //void vertexAttrib3fv(GLuint indx, Float32Array values); - [Throws] - void vertexAttrib3fv(GLuint indx, object values); - //void vertexAttrib3fv(GLuint indx, sequence<GLfloat> values); void vertexAttrib4f(GLuint indx, GLfloat x, GLfloat y, GLfloat z, GLfloat w); - //void vertexAttrib4fv(GLuint indx, Float32Array values); - [Throws] - void vertexAttrib4fv(GLuint indx, object values); - //void vertexAttrib4fv(GLuint indx, sequence<GLfloat> values); + + void vertexAttrib1fv(GLuint indx, Float32List values); + void vertexAttrib2fv(GLuint indx, Float32List values); + void vertexAttrib3fv(GLuint indx, Float32List values); + void vertexAttrib4fv(GLuint indx, Float32List values); + void vertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); void viewport(GLint x, GLint y, GLsizei width, GLsizei height); }; -[Exposed=Window] +interface mixin WebGLRenderingContextOverloads +{ + void bufferData(GLenum target, GLsizeiptr size, GLenum usage); + void bufferData(GLenum target, /*[AllowShared]*/ BufferSource? data, GLenum usage); + void bufferSubData(GLenum target, GLintptr offset, /*[AllowShared]*/ BufferSource data); + + void compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, + GLsizei width, GLsizei height, GLint border, + /*[AllowShared]*/ ArrayBufferView data); + void compressedTexSubImage2D(GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, + /*[AllowShared]*/ ArrayBufferView data); + + void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, + GLenum format, GLenum type, /*[AllowShared]*/ ArrayBufferView? pixels); + + [Throws] + void texImage2D(GLenum target, GLint level, GLint internalformat, + GLsizei width, GLsizei height, GLint border, GLenum format, + GLenum type, /*[AllowShared]*/ ArrayBufferView? pixels); + [Throws] + void texImage2D(GLenum target, GLint level, GLint internalformat, + GLenum format, GLenum type, TexImageSource source); // May throw DOMException + + [Throws] + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, + GLenum format, GLenum type, /*[AllowShared]*/ ArrayBufferView? pixels); + [Throws] + void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLenum format, GLenum type, TexImageSource source); // May throw DOMException + + void uniform1fv(WebGLUniformLocation? location, Float32List v); + void uniform2fv(WebGLUniformLocation? location, Float32List v); + void uniform3fv(WebGLUniformLocation? location, Float32List v); + void uniform4fv(WebGLUniformLocation? location, Float32List v); + + void uniform1iv(WebGLUniformLocation? location, Int32List v); + void uniform2iv(WebGLUniformLocation? location, Int32List v); + void uniform3iv(WebGLUniformLocation? location, Int32List v); + void uniform4iv(WebGLUniformLocation? location, Int32List v); + + void uniformMatrix2fv(WebGLUniformLocation? location, GLboolean transpose, Float32List value); + void uniformMatrix3fv(WebGLUniformLocation? location, GLboolean transpose, Float32List value); + void uniformMatrix4fv(WebGLUniformLocation? location, GLboolean transpose, Float32List value); +}; + +interface mixin WebGLRenderingContextExtensions { + [Throws, Pref="dom.webgl.dom_to_texture.enabled"] + void texImageDOM(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, + GLenum format, GLenum type, HTMLIFrameElement source); // May throw DOMException +}; + +[Exposed=(Window)] interface WebGLRenderingContext { }; -WebGLRenderingContext implements WebGLRenderingContextBase; +WebGLRenderingContext includes WebGLRenderingContextBase; +WebGLRenderingContext includes WebGLRenderingContextOverloads; +WebGLRenderingContext includes WebGLRenderingContextExtensions; diff --git a/components/script/dom/webidls/WebGLSampler.webidl b/components/script/dom/webidls/WebGLSampler.webidl new file mode 100644 index 00000000000..90c66b65a1d --- /dev/null +++ b/components/script/dom/webidls/WebGLSampler.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +// +// WebGL IDL definitions scraped from the Khronos specification: +// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.8 +// + +[Exposed=Window, Pref="dom.webgl2.enabled"] +interface WebGLSampler : WebGLObject { +}; diff --git a/components/script/dom/webidls/WebGLShader.webidl b/components/script/dom/webidls/WebGLShader.webidl index 671da6405ff..af5f375dd99 100644 --- a/components/script/dom/webidls/WebGLShader.webidl +++ b/components/script/dom/webidls/WebGLShader.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.8 diff --git a/components/script/dom/webidls/WebGLShaderPrecisionFormat.webidl b/components/script/dom/webidls/WebGLShaderPrecisionFormat.webidl index eb7b1370b31..10fed47b558 100644 --- a/components/script/dom/webidls/WebGLShaderPrecisionFormat.webidl +++ b/components/script/dom/webidls/WebGLShaderPrecisionFormat.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.7 diff --git a/components/script/dom/webidls/WebGLSync.webidl b/components/script/dom/webidls/WebGLSync.webidl new file mode 100644 index 00000000000..f8cd33ef9ce --- /dev/null +++ b/components/script/dom/webidls/WebGLSync.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +// +// WebGL IDL definitions scraped from the Khronos specification: +// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14 +// + +[Exposed=Window, Pref="dom.webgl2.enabled"] +interface WebGLSync : WebGLObject { +}; diff --git a/components/script/dom/webidls/WebGLTexture.webidl b/components/script/dom/webidls/WebGLTexture.webidl index 42313c98683..384cea6644a 100644 --- a/components/script/dom/webidls/WebGLTexture.webidl +++ b/components/script/dom/webidls/WebGLTexture.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/#5.9 diff --git a/components/script/dom/webidls/WebGLTransformFeedback.webidl b/components/script/dom/webidls/WebGLTransformFeedback.webidl new file mode 100644 index 00000000000..871c0f15c7b --- /dev/null +++ b/components/script/dom/webidls/WebGLTransformFeedback.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +// +// WebGL IDL definitions scraped from the Khronos specification: +// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15 +// + +[Exposed=(Window), Pref="dom.webgl2.enabled"] +interface WebGLTransformFeedback : WebGLObject { +}; diff --git a/components/script/dom/webidls/WebGLUniformLocation.webidl b/components/script/dom/webidls/WebGLUniformLocation.webidl index f068eead6e2..4cf09e42da4 100644 --- a/components/script/dom/webidls/WebGLUniformLocation.webidl +++ b/components/script/dom/webidls/WebGLUniformLocation.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // // WebGL IDL definitions scraped from the Khronos specification: // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.10 diff --git a/components/script/dom/webidls/WebGLVertexArrayObject.webidl b/components/script/dom/webidls/WebGLVertexArrayObject.webidl new file mode 100644 index 00000000000..a42d8cbe051 --- /dev/null +++ b/components/script/dom/webidls/WebGLVertexArrayObject.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +// +// WebGL IDL definitions scraped from the Khronos specification: +// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.17 +// + +[Exposed=(Window), Pref="dom.webgl2.enabled"] +interface WebGLVertexArrayObject : WebGLObject { +}; diff --git a/components/script/dom/webidls/WebGLVertexArrayObjectOES.webidl b/components/script/dom/webidls/WebGLVertexArrayObjectOES.webidl new file mode 100644 index 00000000000..e576bd6089d --- /dev/null +++ b/components/script/dom/webidls/WebGLVertexArrayObjectOES.webidl @@ -0,0 +1,11 @@ +/* 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/. */ +/* + * WebGL IDL definitions scraped from the Khronos specification: + * https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/ + */ + +[NoInterfaceObject, Exposed=Window] +interface WebGLVertexArrayObjectOES: WebGLObject { +}; diff --git a/components/script/dom/webidls/WebSocket.webidl b/components/script/dom/webidls/WebSocket.webidl index b0f36a76657..089b2c5a378 100644 --- a/components/script/dom/webidls/WebSocket.webidl +++ b/components/script/dom/webidls/WebSocket.webidl @@ -1,13 +1,14 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#the-websocket-interface enum BinaryType { "blob", "arraybuffer" }; -[Constructor(DOMString url, optional (DOMString or sequence<DOMString>) protocols), Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface WebSocket : EventTarget { + [Throws] constructor(DOMString url, optional (DOMString or sequence<DOMString>) protocols); readonly attribute DOMString url; //ready state const unsigned short CONNECTING = 0; @@ -23,13 +24,13 @@ interface WebSocket : EventTarget { attribute EventHandler onclose; //readonly attribute DOMString extensions; readonly attribute DOMString protocol; - [Throws] void close([Clamp] optional unsigned short code, optional USVString reason); + [Throws] void close(optional [Clamp] unsigned short code, optional USVString reason); //messaging attribute EventHandler onmessage; attribute BinaryType binaryType; [Throws] void send(USVString data); [Throws] void send(Blob data); - //void send(ArrayBuffer data); - //void send(ArrayBufferView data); + [Throws] void send(ArrayBuffer data); + [Throws] void send(ArrayBufferView data); }; diff --git a/components/script/dom/webidls/WheelEvent.webidl b/components/script/dom/webidls/WheelEvent.webidl new file mode 100644 index 00000000000..15fc033a21b --- /dev/null +++ b/components/script/dom/webidls/WheelEvent.webidl @@ -0,0 +1,33 @@ +/* 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/. */ + +// https://w3c.github.io/uievents/#interface-wheelevent +[Exposed=Window] +interface WheelEvent : MouseEvent { + [Throws] constructor(DOMString typeArg, optional WheelEventInit wheelEventInitDict = {}); + const unsigned long DOM_DELTA_PIXEL = 0x00; + const unsigned long DOM_DELTA_LINE = 0x01; + const unsigned long DOM_DELTA_PAGE = 0x02; + readonly attribute double deltaX; + readonly attribute double deltaY; + readonly attribute double deltaZ; + readonly attribute unsigned long deltaMode; +}; + +// https://w3c.github.io/uievents/#idl-wheeleventinit +dictionary WheelEventInit : MouseEventInit { + double deltaX = 0.0; + double deltaY = 0.0; + double deltaZ = 0.0; + unsigned long deltaMode = 0; +}; + +// https://w3c.github.io/uievents/#idl-interface-WheelEvent-initializers +partial interface WheelEvent { + // Deprecated in DOM Level 3 + void initWheelEvent (DOMString typeArg, boolean bubblesArg, boolean cancelableArg, + Window? viewArg, long detailArg, + double deltaX, double deltaY, + double deltaZ, unsigned long deltaMode); +}; diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index 47c753f43b1..fa3460e9889 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -1,20 +1,21 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#window -[PrimaryGlobal] +[Global=Window, Exposed=Window /*, LegacyUnenumerableNamedProperties */] /*sealed*/ interface Window : GlobalScope { // the current browsing context [Unforgeable] readonly attribute WindowProxy window; [BinaryName="Self_", Replaceable] readonly attribute WindowProxy self; [Unforgeable] readonly attribute Document document; - // https://github.com/servo/servo/issues/14453 - // attribute DOMString name; + attribute DOMString name; - [/*PutForwards=href, */Unforgeable] readonly attribute Location location; + [PutForwards=href, Unforgeable] readonly attribute Location location; readonly attribute History history; + [Pref="dom.customelements.enabled"] + readonly attribute CustomElementRegistry customElements; //[Replaceable] readonly attribute BarProp locationbar; //[Replaceable] readonly attribute BarProp menubar; //[Replaceable] readonly attribute BarProp personalbar; @@ -23,24 +24,24 @@ //[Replaceable] readonly attribute BarProp toolbar; attribute DOMString status; void close(); - //readonly attribute boolean closed; - //void stop(); + readonly attribute boolean closed; + void stop(); //void focus(); //void blur(); // other browsing contexts [Replaceable] readonly attribute WindowProxy frames; - //[Replaceable] readonly attribute unsigned long length; + [Replaceable] readonly attribute unsigned long length; // Note that this can return null in the case that the browsing context has been discarded. // https://github.com/whatwg/html/issues/2115 [Unforgeable] readonly attribute WindowProxy? top; - // attribute any opener; + attribute any opener; // Note that this can return null in the case that the browsing context has been discarded. // https://github.com/whatwg/html/issues/2115 - readonly attribute WindowProxy? parent; + [Replaceable] readonly attribute WindowProxy? parent; readonly attribute Element? frameElement; - //WindowProxy open(optional DOMString url = "about:blank", optional DOMString target = "_blank", - // optional DOMString features = "", optional boolean replace = false); + [Throws] WindowProxy? open(optional USVString url = "", optional DOMString target = "_blank", + optional DOMString features = ""); //getter WindowProxy (unsigned long index); // https://github.com/servo/servo/issues/14453 @@ -54,52 +55,23 @@ // user prompts void alert(DOMString message); void alert(); - //boolean confirm(optional DOMString message = ""); - //DOMString? prompt(optional DOMString message = "", optional DOMString default = ""); + boolean confirm(optional DOMString message = ""); + DOMString? prompt(optional DOMString message = "", optional DOMString default = ""); //void print(); //any showModalDialog(DOMString url, optional any argument); unsigned long requestAnimationFrame(FrameRequestCallback callback); void cancelAnimationFrame(unsigned long handle); - //void postMessage(any message, DOMString targetOrigin, optional sequence<Transferable> transfer); [Throws] - void postMessage(any message, DOMString targetOrigin); - - // also has obsolete members -}; -Window implements GlobalEventHandlers; -Window implements WindowEventHandlers; - -[NoInterfaceObject] -interface WindowProxy {}; - -// https://html.spec.whatwg.org/multipage/#timers -[NoInterfaceObject, Exposed=(Window,Worker)] -interface WindowTimers { - long setTimeout(Function handler, optional long timeout = 0, any... arguments); - long setTimeout(DOMString handler, optional long timeout = 0, any... arguments); - void clearTimeout(optional long handle = 0); - long setInterval(Function handler, optional long timeout = 0, any... arguments); - long setInterval(DOMString handler, optional long timeout = 0, any... arguments); - void clearInterval(optional long handle = 0); -}; -Window implements WindowTimers; - -// https://html.spec.whatwg.org/multipage/#atob -[NoInterfaceObject, Exposed=(Window,Worker)] -interface WindowBase64 { + void postMessage(any message, USVString targetOrigin, optional sequence<object> transfer = []); [Throws] - DOMString btoa(DOMString btoa); - [Throws] - DOMString atob(DOMString atob); -}; -Window implements WindowBase64; + void postMessage(any message, optional WindowPostMessageOptions options = {}); -// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-window.performance-attribute -partial interface Window { - [Replaceable] readonly attribute Performance performance; + // also has obsolete members }; +Window includes GlobalEventHandlers; +Window includes WindowEventHandlers; // https://html.spec.whatwg.org/multipage/#Window-partial partial interface Window { @@ -130,7 +102,7 @@ dictionary ScrollToOptions : ScrollOptions { // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface partial interface Window { [Exposed=(Window), NewObject] MediaQueryList matchMedia(DOMString query); - [SameObject] readonly attribute Screen screen; + [SameObject, Replaceable] readonly attribute Screen screen; // browsing context void moveTo(long x, long y); @@ -139,36 +111,39 @@ partial interface Window { void resizeBy(long x, long y); // viewport - readonly attribute long innerWidth; - readonly attribute long innerHeight; + [Replaceable] readonly attribute long innerWidth; + [Replaceable] readonly attribute long innerHeight; // viewport scrolling - readonly attribute long scrollX; - readonly attribute long pageXOffset; - readonly attribute long scrollY; - readonly attribute long pageYOffset; - void scroll(optional ScrollToOptions options); + [Replaceable] readonly attribute long scrollX; + [Replaceable] readonly attribute long pageXOffset; + [Replaceable] readonly attribute long scrollY; + [Replaceable] readonly attribute long pageYOffset; + void scroll(optional ScrollToOptions options = {}); void scroll(unrestricted double x, unrestricted double y); - void scrollTo(optional ScrollToOptions options); + void scrollTo(optional ScrollToOptions options = {}); void scrollTo(unrestricted double x, unrestricted double y); - void scrollBy(optional ScrollToOptions options); + void scrollBy(optional ScrollToOptions options = {}); void scrollBy(unrestricted double x, unrestricted double y); // client - readonly attribute long screenX; - readonly attribute long screenY; - readonly attribute long outerWidth; - readonly attribute long outerHeight; - readonly attribute double devicePixelRatio; + [Replaceable] readonly attribute long screenX; + [Replaceable] readonly attribute long screenY; + [Replaceable] readonly attribute long outerWidth; + [Replaceable] readonly attribute long outerHeight; + [Replaceable] readonly attribute double devicePixelRatio; }; // Proprietary extensions. partial interface Window { + [Pref="dom.servo_helpers.enabled"] void debug(DOMString arg); + [Pref="dom.servo_helpers.enabled"] void gc(); + [Pref="dom.servo_helpers.enabled"] void trap(); - [Func="Window::global_is_mozbrowser", Throws] - void openURLInDefaultBrowser(DOMString href); + [Pref="dom.servo_helpers.enabled"] + void js_backtrace(); }; // WebDriver extensions @@ -179,18 +154,16 @@ partial interface Window { }; // https://html.spec.whatwg.org/multipage/#dom-sessionstorage -[NoInterfaceObject] -interface WindowSessionStorage { +interface mixin WindowSessionStorage { readonly attribute Storage sessionStorage; }; -Window implements WindowSessionStorage; +Window includes WindowSessionStorage; // https://html.spec.whatwg.org/multipage/#dom-localstorage -[NoInterfaceObject] -interface WindowLocalStorage { +interface mixin WindowLocalStorage { readonly attribute Storage localStorage; }; -Window implements WindowLocalStorage; +Window includes WindowLocalStorage; // http://w3c.github.io/animation-timing/#framerequestcallback callback FrameRequestCallback = void (DOMHighResTimeStamp time); @@ -201,3 +174,22 @@ partial interface Window { readonly attribute TestRunner testRunner; //readonly attribute EventSender eventSender; }; + +partial interface Window { + [Pref="css.animations.testing.enabled"] + readonly attribute unsigned long runningAnimationCount; +}; + +// https://w3c.github.io/selection-api/#dom-document +partial interface Window { + Selection? getSelection(); +}; + +// https://dom.spec.whatwg.org/#interface-window-extensions +partial interface Window { + [Replaceable] readonly attribute any event; // historical +}; + +dictionary WindowPostMessageOptions : PostMessageOptions { + USVString targetOrigin = "/"; +}; diff --git a/components/script/dom/webidls/WindowOrWorkerGlobalScope.webidl b/components/script/dom/webidls/WindowOrWorkerGlobalScope.webidl index f0b8218fbe2..2ab8ba2c25d 100644 --- a/components/script/dom/webidls/WindowOrWorkerGlobalScope.webidl +++ b/components/script/dom/webidls/WindowOrWorkerGlobalScope.webidl @@ -1,30 +1,45 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#windoworworkerglobalscope -// typedef (DOMString or Function) TimerHandler; +typedef (DOMString or Function) TimerHandler; -[NoInterfaceObject, Exposed=(Window,Worker)] -interface WindowOrWorkerGlobalScope { - // [Replaceable] readonly attribute USVString origin; +[Exposed=(Window,Worker)] +interface mixin WindowOrWorkerGlobalScope { + [Replaceable] readonly attribute USVString origin; // base64 utility methods - // DOMString btoa(DOMString data); - // DOMString atob(DOMString data); + [Throws] DOMString btoa(DOMString data); + [Throws] DOMString atob(DOMString data); // timers - // long setTimeout(TimerHandler handler, optional long timeout = 0, any... arguments); - // void clearTimeout(optional long handle = 0); - // long setInterval(TimerHandler handler, optional long timeout = 0, any... arguments); - // void clearInterval(optional long handle = 0); + long setTimeout(TimerHandler handler, optional long timeout = 0, any... arguments); + void clearTimeout(optional long handle = 0); + long setInterval(TimerHandler handler, optional long timeout = 0, any... arguments); + void clearInterval(optional long handle = 0); + + // microtask queuing + void queueMicrotask(VoidFunction callback); // ImageBitmap - // Promise<ImageBitmap> createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options); + [Pref="dom.imagebitmap.enabled"] + Promise<ImageBitmap> createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options = {}); // Promise<ImageBitmap> createImageBitmap( // ImageBitmapSource image, long sx, long sy, long sw, long sh, optional ImageBitmapOptions options); }; -Window implements WindowOrWorkerGlobalScope; -WorkerGlobalScope implements WindowOrWorkerGlobalScope; +// https://w3c.github.io/hr-time/#the-performance-attribute +partial interface mixin WindowOrWorkerGlobalScope { + [Replaceable] + readonly attribute Performance performance; +}; + +// https://w3c.github.io/webappsec-secure-contexts/#monkey-patching-global-object +partial interface mixin WindowOrWorkerGlobalScope { + readonly attribute boolean isSecureContext; +}; + +Window includes WindowOrWorkerGlobalScope; +WorkerGlobalScope includes WindowOrWorkerGlobalScope; diff --git a/components/script/dom/webidls/WindowProxy.webidl b/components/script/dom/webidls/WindowProxy.webidl new file mode 100644 index 00000000000..a4fd2a3f79c --- /dev/null +++ b/components/script/dom/webidls/WindowProxy.webidl @@ -0,0 +1,7 @@ +/* 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/. */ + +// https://html.spec.whatwg.org/multipage/#the-windowproxy-exotic-object +[Exposed=(Window,DissimilarOriginWindow), NoInterfaceObject] +interface WindowProxy {}; diff --git a/components/script/dom/webidls/Worker.webidl b/components/script/dom/webidls/Worker.webidl index deb519d78df..93df577ec32 100644 --- a/components/script/dom/webidls/Worker.webidl +++ b/components/script/dom/webidls/Worker.webidl @@ -1,20 +1,31 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#abstractworker -[NoInterfaceObject, Exposed=(Window,Worker)] -interface AbstractWorker { +[Exposed=(Window,Worker)] +interface mixin AbstractWorker { attribute EventHandler onerror; }; // https://html.spec.whatwg.org/multipage/#worker -[Constructor(DOMString scriptURL), Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface Worker : EventTarget { + [Throws] constructor(USVString scriptURL, optional WorkerOptions options = {}); void terminate(); -[Throws] -void postMessage(any message/*, optional sequence<Transferable> transfer*/); - attribute EventHandler onmessage; + [Throws] void postMessage(any message, sequence<object> transfer); + [Throws] void postMessage(any message, optional PostMessageOptions options = {}); + attribute EventHandler onmessage; + attribute EventHandler onmessageerror; }; -Worker implements AbstractWorker; + +dictionary WorkerOptions { + WorkerType type = "classic"; + RequestCredentials credentials = "same-origin"; // credentials is only used if type is "module" + DOMString name = ""; +}; + +enum WorkerType { "classic", "module" }; + +Worker includes AbstractWorker; diff --git a/components/script/dom/webidls/WorkerGlobalScope.webidl b/components/script/dom/webidls/WorkerGlobalScope.webidl index dcdd2957d43..191ab63967d 100644 --- a/components/script/dom/webidls/WorkerGlobalScope.webidl +++ b/components/script/dom/webidls/WorkerGlobalScope.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#workerglobalscope [Abstract, Exposed=Worker] @@ -22,5 +22,3 @@ partial interface WorkerGlobalScope { // not obsolete void importScripts(DOMString... urls); readonly attribute WorkerNavigator navigator; }; -WorkerGlobalScope implements WindowTimers; -WorkerGlobalScope implements WindowBase64; diff --git a/components/script/dom/webidls/WorkerLocation.webidl b/components/script/dom/webidls/WorkerLocation.webidl index e4c410f720a..8985da7e6e9 100644 --- a/components/script/dom/webidls/WorkerLocation.webidl +++ b/components/script/dom/webidls/WorkerLocation.webidl @@ -1,12 +1,12 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#worker-locations [Exposed=Worker] interface WorkerLocation { - /*stringifier*/ readonly attribute USVString href; - // readonly attribute USVString origin; + stringifier readonly attribute USVString href; + readonly attribute USVString origin; readonly attribute USVString protocol; readonly attribute USVString host; readonly attribute USVString hostname; @@ -14,9 +14,4 @@ interface WorkerLocation { readonly attribute USVString pathname; readonly attribute USVString search; readonly attribute USVString hash; - - // This is only doing as well as gecko right now. - // https://github.com/servo/servo/issues/7590 is on file for - // adding attribute stringifier support. - stringifier; }; diff --git a/components/script/dom/webidls/WorkerNavigator.webidl b/components/script/dom/webidls/WorkerNavigator.webidl index c60eda76896..5be43960802 100644 --- a/components/script/dom/webidls/WorkerNavigator.webidl +++ b/components/script/dom/webidls/WorkerNavigator.webidl @@ -1,13 +1,13 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://html.spec.whatwg.org/multipage/#workernavigator [Exposed=Worker] interface WorkerNavigator {}; -WorkerNavigator implements NavigatorID; -WorkerNavigator implements NavigatorLanguage; -//WorkerNavigator implements NavigatorOnLine; +WorkerNavigator includes NavigatorID; +WorkerNavigator includes NavigatorLanguage; +//WorkerNavigator includes NavigatorOnLine; // https://w3c.github.io/permissions/#navigator-and-workernavigator-extension @@ -15,3 +15,8 @@ WorkerNavigator implements NavigatorLanguage; partial interface WorkerNavigator { [Pref="dom.permissions.enabled"] readonly attribute Permissions permissions; }; + +[Exposed=DedicatedWorker] +partial interface WorkerNavigator { + [SameObject, Pref="dom.webgpu.enabled"] readonly attribute GPU gpu; +}; diff --git a/components/script/dom/webidls/Worklet.webidl b/components/script/dom/webidls/Worklet.webidl new file mode 100644 index 00000000000..16ec8e8d7c9 --- /dev/null +++ b/components/script/dom/webidls/Worklet.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// https://drafts.css-houdini.org/worklets/#worklet +[Pref="dom.worklet.enabled", Exposed=(Window)] +interface Worklet { + [NewObject] Promise<void> addModule(USVString moduleURL, optional WorkletOptions options = {}); +}; + +dictionary WorkletOptions { + RequestCredentials credentials = "omit"; +}; diff --git a/components/script/dom/webidls/WorkletGlobalScope.webidl b/components/script/dom/webidls/WorkletGlobalScope.webidl new file mode 100644 index 00000000000..21f7a7e0af0 --- /dev/null +++ b/components/script/dom/webidls/WorkletGlobalScope.webidl @@ -0,0 +1,10 @@ +/* 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/. */ + +// https://drafts.css-houdini.org/worklets/#workletglobalscope +// TODO: The spec IDL doesn't make this a subclass of EventTarget +// https://github.com/whatwg/html/issues/2611 +[Pref="dom.worklet.enabled", Exposed=Worklet] +interface WorkletGlobalScope: GlobalScope { +}; diff --git a/components/script/dom/webidls/XMLDocument.webidl b/components/script/dom/webidls/XMLDocument.webidl index 150267dd62a..64d11d29cd7 100644 --- a/components/script/dom/webidls/XMLDocument.webidl +++ b/components/script/dom/webidls/XMLDocument.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is: * https://dom.spec.whatwg.org/#interface-document @@ -8,4 +8,5 @@ */ // https://dom.spec.whatwg.org/#interface-document +[Exposed=Window] interface XMLDocument : Document {}; diff --git a/components/script/dom/webidls/XMLHttpRequest.webidl b/components/script/dom/webidls/XMLHttpRequest.webidl index 270e45ca7ed..2c043b9407a 100644 --- a/components/script/dom/webidls/XMLHttpRequest.webidl +++ b/components/script/dom/webidls/XMLHttpRequest.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://xhr.spec.whatwg.org/#interface-xmlhttprequest @@ -12,8 +12,11 @@ * http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0. */ +// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit +typedef (Blob or BufferSource or FormData or DOMString or URLSearchParams) XMLHttpRequestBodyInit; + // https://fetch.spec.whatwg.org/#bodyinit -typedef (Blob or /*BufferSource or */ FormData or DOMString or URLSearchParams) BodyInit; +typedef (ReadableStream or XMLHttpRequestBodyInit) BodyInit; enum XMLHttpRequestResponseType { "", @@ -21,11 +24,12 @@ enum XMLHttpRequestResponseType { "blob", "document", "json", - "text" + "text", }; -[Constructor, Exposed=(Window,Worker)] +[Exposed=(Window,Worker)] interface XMLHttpRequest : XMLHttpRequestEventTarget { + [Throws] constructor(); // event handler attribute EventHandler onreadystatechange; @@ -53,7 +57,7 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget { attribute boolean withCredentials; readonly attribute XMLHttpRequestUpload upload; [Throws] - void send(optional (Document or BodyInit)? data = null); + void send(optional (Document or XMLHttpRequestBodyInit)? data = null); void abort(); // response diff --git a/components/script/dom/webidls/XMLHttpRequestEventTarget.webidl b/components/script/dom/webidls/XMLHttpRequestEventTarget.webidl index 2310e9b9153..bde137a2b48 100644 --- a/components/script/dom/webidls/XMLHttpRequestEventTarget.webidl +++ b/components/script/dom/webidls/XMLHttpRequestEventTarget.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://xhr.spec.whatwg.org/#interface-xmlhttprequest diff --git a/components/script/dom/webidls/XMLHttpRequestUpload.webidl b/components/script/dom/webidls/XMLHttpRequestUpload.webidl index 76d917a3c77..3dfee1210c5 100644 --- a/components/script/dom/webidls/XMLHttpRequestUpload.webidl +++ b/components/script/dom/webidls/XMLHttpRequestUpload.webidl @@ -1,6 +1,6 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* * The origin of this IDL file is * https://xhr.spec.whatwg.org/#interface-xmlhttprequest diff --git a/components/script/dom/webidls/XMLSerializer.webidl b/components/script/dom/webidls/XMLSerializer.webidl new file mode 100644 index 00000000000..c0111220e42 --- /dev/null +++ b/components/script/dom/webidls/XMLSerializer.webidl @@ -0,0 +1,14 @@ +/* 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/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/DOM-Parsing/#the-domparser-interface + */ + +[Exposed=Window] +interface XMLSerializer { + [Throws] constructor(); + [Throws] + DOMString serializeToString(Node root); +}; diff --git a/components/script/dom/webidls/XRCompositionLayer.webidl b/components/script/dom/webidls/XRCompositionLayer.webidl new file mode 100644 index 00000000000..bde8aed6cdf --- /dev/null +++ b/components/script/dom/webidls/XRCompositionLayer.webidl @@ -0,0 +1,20 @@ +/* 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/. */ + +// TODO: Implement the layer types +// https://github.com/servo/servo/issues/27493 + +// https://immersive-web.github.io/layers/#xrcompositionlayer +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRCompositionLayer : XRLayer { +// readonly attribute XRLayerLayout layout; +// +// attribute boolean blendTextureSourceAlpha; +// attribute boolean? chromaticAberrationCorrection; +// attribute float? fixedFoveation; +// +// readonly attribute boolean needsRedraw; +// +// void destroy(); +}; diff --git a/components/script/dom/webidls/XRCubeLayer.webidl b/components/script/dom/webidls/XRCubeLayer.webidl new file mode 100644 index 00000000000..66e8eb18130 --- /dev/null +++ b/components/script/dom/webidls/XRCubeLayer.webidl @@ -0,0 +1,17 @@ +/* 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/. */ + +// TODO: Implement the layer types +// https://github.com/servo/servo/issues/27493 + +// https://immersive-web.github.io/layers/#xrcubelayer + +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRCubeLayer : XRCompositionLayer { +// attribute XRSpace space; +// attribute DOMPointReadOnly orientation; +// +// // Events +// attribute EventHandler onredraw; +}; diff --git a/components/script/dom/webidls/XRCylinderLayer.webidl b/components/script/dom/webidls/XRCylinderLayer.webidl new file mode 100644 index 00000000000..9a3e2d051c6 --- /dev/null +++ b/components/script/dom/webidls/XRCylinderLayer.webidl @@ -0,0 +1,20 @@ +/* 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/. */ + +// TODO: Implement the layer types +// https://github.com/servo/servo/issues/27493 + +// https://immersive-web.github.io/layers/#xrcylinderlayer +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRCylinderLayer : XRCompositionLayer { +// attribute XRSpace space; +// attribute XRRigidTransform transform; +// +// attribute float radius; +// attribute float centralAngle; +// attribute float aspectRatio; +// +// // Events +// attribute EventHandler onredraw; +}; diff --git a/components/script/dom/webidls/XREquirectLayer.webidl b/components/script/dom/webidls/XREquirectLayer.webidl new file mode 100644 index 00000000000..98a82bc80dd --- /dev/null +++ b/components/script/dom/webidls/XREquirectLayer.webidl @@ -0,0 +1,21 @@ +/* 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/. */ + +// TODO: Implement the layer types +// https://github.com/servo/servo/issues/27493 + +// https://immersive-web.github.io/layers/#xrequirectlayer +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XREquirectLayer : XRCompositionLayer { +// attribute XRSpace space; +// attribute XRRigidTransform transform; +// +// attribute float radius; +// attribute float centralHorizontalAngle; +// attribute float upperVerticalAngle; +// attribute float lowerVerticalAngle; +// +// // Events +// attribute EventHandler onredraw; +}; diff --git a/components/script/dom/webidls/XRFrame.webidl b/components/script/dom/webidls/XRFrame.webidl new file mode 100644 index 00000000000..e7a4eef1ddd --- /dev/null +++ b/components/script/dom/webidls/XRFrame.webidl @@ -0,0 +1,15 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrframe-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRFrame { + readonly attribute XRSession session; + + [Throws] XRViewerPose? getViewerPose(XRReferenceSpace referenceSpace); + [Throws] XRPose? getPose(XRSpace space, XRSpace relativeTo); + [Pref="dom.webxr.hands.enabled", Throws] XRJointPose? getJointPose(XRJointSpace space, XRSpace relativeTo); + sequence<XRHitTestResult> getHitTestResults(XRHitTestSource hitTestSource); +}; diff --git a/components/script/dom/webidls/XRHand.webidl b/components/script/dom/webidls/XRHand.webidl new file mode 100644 index 00000000000..cff30268ba6 --- /dev/null +++ b/components/script/dom/webidls/XRHand.webidl @@ -0,0 +1,41 @@ +/* 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/. */ + +// https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md + +[SecureContext, Exposed=Window, Pref="dom.webxr.hands.enabled"] +interface XRHand { + readonly attribute long length; + getter XRJointSpace(unsigned long index); + + const unsigned long WRIST = 0; + const unsigned long THUMB_METACARPAL = 1; + const unsigned long THUMB_PHALANX_PROXIMAL = 2; + const unsigned long THUMB_PHALANX_DISTAL = 3; + const unsigned long THUMB_PHALANX_TIP = 4; + + const unsigned long INDEX_METACARPAL = 5; + const unsigned long INDEX_PHALANX_PROXIMAL = 6; + const unsigned long INDEX_PHALANX_INTERMEDIATE = 7; + const unsigned long INDEX_PHALANX_DISTAL = 8; + const unsigned long INDEX_PHALANX_TIP = 9; + + const unsigned long MIDDLE_METACARPAL = 10; + const unsigned long MIDDLE_PHALANX_PROXIMAL = 11; + const unsigned long MIDDLE_PHALANX_INTERMEDIATE = 12; + const unsigned long MIDDLE_PHALANX_DISTAL = 13; + const unsigned long MIDDLE_PHALANX_TIP = 14; + + const unsigned long RING_METACARPAL = 15; + const unsigned long RING_PHALANX_PROXIMAL = 16; + const unsigned long RING_PHALANX_INTERMEDIATE = 17; + const unsigned long RING_PHALANX_DISTAL = 18; + const unsigned long RING_PHALANX_TIP = 19; + + const unsigned long LITTLE_METACARPAL = 20; + const unsigned long LITTLE_PHALANX_PROXIMAL = 21; + const unsigned long LITTLE_PHALANX_INTERMEDIATE = 22; + const unsigned long LITTLE_PHALANX_DISTAL = 23; + const unsigned long LITTLE_PHALANX_TIP = 24; +}; diff --git a/components/script/dom/webidls/XRHitTestResult.webidl b/components/script/dom/webidls/XRHitTestResult.webidl new file mode 100644 index 00000000000..08bf4bf5389 --- /dev/null +++ b/components/script/dom/webidls/XRHitTestResult.webidl @@ -0,0 +1,10 @@ +/* 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/. */ + +// https://immersive-web.github.io/hit-test/#xrhittestresult-interface + +[SecureContext, Exposed=Window] +interface XRHitTestResult { + XRPose? getPose(XRSpace baseSpace); +}; diff --git a/components/script/dom/webidls/XRHitTestSource.webidl b/components/script/dom/webidls/XRHitTestSource.webidl new file mode 100644 index 00000000000..a3a56cebed4 --- /dev/null +++ b/components/script/dom/webidls/XRHitTestSource.webidl @@ -0,0 +1,22 @@ +/* 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/. */ + +// https://immersive-web.github.io/hit-test/#xrhittestsource-interface + +enum XRHitTestTrackableType { + "point", + "plane", + "mesh" +}; + +dictionary XRHitTestOptionsInit { + required XRSpace space; + sequence<XRHitTestTrackableType> entityTypes; + XRRay offsetRay; +}; + +[SecureContext, Exposed=Window] +interface XRHitTestSource { + void cancel(); +}; diff --git a/components/script/dom/webidls/XRInputSource.webidl b/components/script/dom/webidls/XRInputSource.webidl new file mode 100644 index 00000000000..b663666c872 --- /dev/null +++ b/components/script/dom/webidls/XRInputSource.webidl @@ -0,0 +1,30 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrinputsource-interface + +enum XRHandedness { + "none", + "left", + "right" +}; + +enum XRTargetRayMode { + "gaze", + "tracked-pointer", + "screen" +}; + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRInputSource { + readonly attribute XRHandedness handedness; + readonly attribute XRTargetRayMode targetRayMode; + [SameObject] readonly attribute XRSpace targetRaySpace; + [SameObject] readonly attribute XRSpace? gripSpace; + // [SameObject] readonly attribute Gamepad? gamepad; + /* [SameObject] */ readonly attribute /* FrozenArray<DOMString> */ any profiles; + + [Pref="dom.webxr.hands.enabled"] + readonly attribute XRHand? hand; +}; diff --git a/components/script/dom/webidls/XRInputSourceArray.webidl b/components/script/dom/webidls/XRInputSourceArray.webidl new file mode 100644 index 00000000000..f8a0eb4308d --- /dev/null +++ b/components/script/dom/webidls/XRInputSourceArray.webidl @@ -0,0 +1,12 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrinputsourcearray-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRInputSourceArray { + iterable<XRInputSource>; + readonly attribute unsigned long length; + getter XRInputSource(unsigned long index); +}; diff --git a/components/script/dom/webidls/XRInputSourceEvent.webidl b/components/script/dom/webidls/XRInputSourceEvent.webidl new file mode 100644 index 00000000000..f1ffbf2a799 --- /dev/null +++ b/components/script/dom/webidls/XRInputSourceEvent.webidl @@ -0,0 +1,17 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrinputsourceevent-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRInputSourceEvent : Event { + [Throws] constructor(DOMString type, XRInputSourceEventInit eventInitDict); + [SameObject] readonly attribute XRFrame frame; + [SameObject] readonly attribute XRInputSource inputSource; +}; + +dictionary XRInputSourceEventInit : EventInit { + required XRFrame frame; + required XRInputSource inputSource; +}; diff --git a/components/script/dom/webidls/XRInputSourcesChangeEvent.webidl b/components/script/dom/webidls/XRInputSourcesChangeEvent.webidl new file mode 100644 index 00000000000..aba56f8b0a6 --- /dev/null +++ b/components/script/dom/webidls/XRInputSourcesChangeEvent.webidl @@ -0,0 +1,19 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrinputsourceschangedevent-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.test"] +interface XRInputSourcesChangeEvent : Event { + constructor(DOMString type, XRInputSourcesChangeEventInit eventInitDict); + [SameObject] readonly attribute XRSession session; + /* [SameObject] */ readonly attribute /* FrozenArray<XRInputSource> */ any added; + /* [SameObject] */ readonly attribute /* FrozenArray<XRInputSource> */ any removed; +}; + +dictionary XRInputSourcesChangeEventInit : EventInit { + required XRSession session; + required sequence<XRInputSource> added; + required sequence<XRInputSource> removed; +}; diff --git a/components/script/dom/webidls/XRJointPose.webidl b/components/script/dom/webidls/XRJointPose.webidl new file mode 100644 index 00000000000..70750b66cc4 --- /dev/null +++ b/components/script/dom/webidls/XRJointPose.webidl @@ -0,0 +1,10 @@ +/* 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/. */ + +// https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md + +[SecureContext, Exposed=Window, Pref="dom.webxr.hands.enabled"] +interface XRJointPose: XRPose { + readonly attribute float? radius; +}; diff --git a/components/script/dom/webidls/XRJointSpace.webidl b/components/script/dom/webidls/XRJointSpace.webidl new file mode 100644 index 00000000000..0815a73ec58 --- /dev/null +++ b/components/script/dom/webidls/XRJointSpace.webidl @@ -0,0 +1,8 @@ +/* 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/. */ + +// https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md + +[SecureContext, Exposed=Window, Pref="dom.webxr.hands.enabled"] +interface XRJointSpace: XRSpace {}; diff --git a/components/script/dom/webidls/XRLayer.webidl b/components/script/dom/webidls/XRLayer.webidl new file mode 100644 index 00000000000..5f719db7bdb --- /dev/null +++ b/components/script/dom/webidls/XRLayer.webidl @@ -0,0 +1,7 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrlayer +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRLayer : EventTarget {}; diff --git a/components/script/dom/webidls/XRLayerEvent.webidl b/components/script/dom/webidls/XRLayerEvent.webidl new file mode 100644 index 00000000000..9a05272138e --- /dev/null +++ b/components/script/dom/webidls/XRLayerEvent.webidl @@ -0,0 +1,14 @@ +/* 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/. */ + +// https://immersive-web.github.io/layers/#xrlayerevent-interface +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRLayerEvent : Event { + constructor(DOMString type, XRLayerEventInit eventInitDict); + [SameObject] readonly attribute XRLayer layer; +}; + +dictionary XRLayerEventInit : EventInit { + required XRLayer layer; +}; diff --git a/components/script/dom/webidls/XRMediaBinding.webidl b/components/script/dom/webidls/XRMediaBinding.webidl new file mode 100644 index 00000000000..87a858f098e --- /dev/null +++ b/components/script/dom/webidls/XRMediaBinding.webidl @@ -0,0 +1,19 @@ +/* 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/. */ + +// https://immersive-web.github.io/layers/#xrmediabindingtype +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRMediaBinding { + [Throws] constructor(XRSession session); + + [Throws] XRQuadLayer createQuadLayer(HTMLVideoElement video, XRMediaLayerInit init); + [Throws] XRCylinderLayer createCylinderLayer(HTMLVideoElement video, XRMediaLayerInit init); + [Throws] XREquirectLayer createEquirectLayer(HTMLVideoElement video, XRMediaLayerInit init); +}; + +dictionary XRMediaLayerInit { + required XRSpace space; + XRLayerLayout layout = "mono"; + boolean invertStereo = false; +}; diff --git a/components/script/dom/webidls/XRPose.webidl b/components/script/dom/webidls/XRPose.webidl new file mode 100644 index 00000000000..a8ffd440010 --- /dev/null +++ b/components/script/dom/webidls/XRPose.webidl @@ -0,0 +1,11 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrpose-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRPose { + readonly attribute XRRigidTransform transform; + // readonly attribute boolean emulatedPosition; +}; diff --git a/components/script/dom/webidls/XRProjectionLayer.webidl b/components/script/dom/webidls/XRProjectionLayer.webidl new file mode 100644 index 00000000000..c96afe4c28d --- /dev/null +++ b/components/script/dom/webidls/XRProjectionLayer.webidl @@ -0,0 +1,12 @@ +/* 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/. */ + +// TODO: Implement the layer types +// https://github.com/servo/servo/issues/27493 + +// https://immersive-web.github.io/layers/#xrprojectionlayer +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRProjectionLayer : XRCompositionLayer { +// readonly attribute boolean ignoreDepthValues; +}; diff --git a/components/script/dom/webidls/XRQuadLayer.webidl b/components/script/dom/webidls/XRQuadLayer.webidl new file mode 100644 index 00000000000..78c513ecdb9 --- /dev/null +++ b/components/script/dom/webidls/XRQuadLayer.webidl @@ -0,0 +1,19 @@ +/* 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/. */ + +// TODO: Implement the layer types +// https://github.com/servo/servo/issues/27493 + +// https://immersive-web.github.io/layers/#xrquadlayer +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRQuadLayer : XRCompositionLayer { +// attribute XRSpace space; +// attribute XRRigidTransform transform; +// +// attribute float width; +// attribute float height; +// +// // Events +// attribute EventHandler onredraw; +}; diff --git a/components/script/dom/webidls/XRRay.webidl b/components/script/dom/webidls/XRRay.webidl new file mode 100644 index 00000000000..f578436685d --- /dev/null +++ b/components/script/dom/webidls/XRRay.webidl @@ -0,0 +1,21 @@ +/* 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/. */ + +// https://immersive-web.github.io/hit-test/#xrray-interface + +dictionary XRRayDirectionInit { + double x = 0; + double y = 0; + double z = -1; + double w = 0; +}; + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRRay { + [Throws] constructor(optional DOMPointInit origin = {}, optional XRRayDirectionInit direction = {}); + [Throws] constructor(XRRigidTransform transform); + [SameObject] readonly attribute DOMPointReadOnly origin; + [SameObject] readonly attribute DOMPointReadOnly direction; + [SameObject] readonly attribute Float32Array matrix; +}; diff --git a/components/script/dom/webidls/XRReferenceSpace.webidl b/components/script/dom/webidls/XRReferenceSpace.webidl new file mode 100644 index 00000000000..0479bd12beb --- /dev/null +++ b/components/script/dom/webidls/XRReferenceSpace.webidl @@ -0,0 +1,19 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrreferencespace-interface + +enum XRReferenceSpaceType { + "viewer", + "local", + "local-floor", + "bounded-floor", + "unbounded" +}; + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRReferenceSpace : XRSpace { + XRReferenceSpace getOffsetReferenceSpace(XRRigidTransform originOffset); + // attribute EventHandler onreset; +}; diff --git a/components/script/dom/webidls/XRRenderState.webidl b/components/script/dom/webidls/XRRenderState.webidl new file mode 100644 index 00000000000..52c72511923 --- /dev/null +++ b/components/script/dom/webidls/XRRenderState.webidl @@ -0,0 +1,24 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrrenderstate-interface + +dictionary XRRenderStateInit { + double depthNear; + double depthFar; + double inlineVerticalFieldOfView; + XRWebGLLayer? baseLayer; + sequence<XRLayer>? layers; +}; + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] interface XRRenderState { + readonly attribute double depthNear; + readonly attribute double depthFar; + readonly attribute double? inlineVerticalFieldOfView; + readonly attribute XRWebGLLayer? baseLayer; + + // https://immersive-web.github.io/layers/#xrrenderstatechanges + // workaround until we have FrozenArray + readonly attribute /* FrozenArray<XRLayer> */ any layers; +}; diff --git a/components/script/dom/webidls/XRRigidTransform.webidl b/components/script/dom/webidls/XRRigidTransform.webidl new file mode 100644 index 00000000000..91505a96285 --- /dev/null +++ b/components/script/dom/webidls/XRRigidTransform.webidl @@ -0,0 +1,14 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrrigidtransform-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRRigidTransform { + [Throws] constructor(optional DOMPointInit position = {}, optional DOMPointInit orientation = {}); + readonly attribute DOMPointReadOnly position; + readonly attribute DOMPointReadOnly orientation; + readonly attribute Float32Array matrix; + readonly attribute XRRigidTransform inverse; +}; diff --git a/components/script/dom/webidls/XRSession.webidl b/components/script/dom/webidls/XRSession.webidl new file mode 100644 index 00000000000..45cdbe2339e --- /dev/null +++ b/components/script/dom/webidls/XRSession.webidl @@ -0,0 +1,52 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrsession-interface + +enum XREnvironmentBlendMode { + "opaque", + "additive", + "alpha-blend", +}; + +enum XRVisibilityState { + "visible", + "visible-blurred", + "hidden", +}; + +callback XRFrameRequestCallback = void (DOMHighResTimeStamp time, XRFrame frame); + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRSession : EventTarget { + // // Attributes + readonly attribute XREnvironmentBlendMode environmentBlendMode; + + readonly attribute XRVisibilityState visibilityState; + [SameObject] readonly attribute XRRenderState renderState; + [SameObject] readonly attribute XRInputSourceArray inputSources; + + // // Methods + [Throws] void updateRenderState(optional XRRenderStateInit state = {}); + Promise<XRReferenceSpace> requestReferenceSpace(XRReferenceSpaceType type); + + long requestAnimationFrame(XRFrameRequestCallback callback); + void cancelAnimationFrame(long handle); + + Promise<void> end(); + + // hit test module + Promise<XRHitTestSource> requestHitTestSource(XRHitTestOptionsInit options); + + // // Events + attribute EventHandler onend; + attribute EventHandler onselect; + attribute EventHandler onsqueeze; + attribute EventHandler oninputsourceschange; + attribute EventHandler onselectstart; + attribute EventHandler onselectend; + attribute EventHandler onsqueezestart; + attribute EventHandler onsqueezeend; + attribute EventHandler onvisibilitychange; +}; diff --git a/components/script/dom/webidls/XRSessionEvent.webidl b/components/script/dom/webidls/XRSessionEvent.webidl new file mode 100644 index 00000000000..19506af613d --- /dev/null +++ b/components/script/dom/webidls/XRSessionEvent.webidl @@ -0,0 +1,15 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrsessionevent-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRSessionEvent : Event { + [Throws] constructor(DOMString type, XRSessionEventInit eventInitDict); + [SameObject] readonly attribute XRSession session; +}; + +dictionary XRSessionEventInit : EventInit { + required XRSession session; +}; diff --git a/components/script/dom/webidls/XRSpace.webidl b/components/script/dom/webidls/XRSpace.webidl new file mode 100644 index 00000000000..54401b051c8 --- /dev/null +++ b/components/script/dom/webidls/XRSpace.webidl @@ -0,0 +1,10 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrspace-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRSpace : EventTarget { + // XRRigidTransform? getTransformTo(XRSpace other); +}; diff --git a/components/script/dom/webidls/XRSubImage.webidl b/components/script/dom/webidls/XRSubImage.webidl new file mode 100644 index 00000000000..c96067fab17 --- /dev/null +++ b/components/script/dom/webidls/XRSubImage.webidl @@ -0,0 +1,9 @@ +/* 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/. */ + +// https://immersive-web.github.io/layers/#xrsubimagetype +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRSubImage { + [SameObject] readonly attribute XRViewport viewport; +}; diff --git a/components/script/dom/webidls/XRSystem.webidl b/components/script/dom/webidls/XRSystem.webidl new file mode 100644 index 00000000000..b7eeed756bb --- /dev/null +++ b/components/script/dom/webidls/XRSystem.webidl @@ -0,0 +1,35 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrsystem-interface +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRSystem: EventTarget { + // Methods + Promise<boolean> isSessionSupported(XRSessionMode mode); + Promise<XRSession> requestSession(XRSessionMode mode, optional XRSessionInit parameters = {}); + + // Events + // attribute EventHandler ondevicechange; +}; + +[SecureContext] +partial interface Navigator { + [SameObject, Pref="dom.webxr.enabled"] readonly attribute XRSystem xr; +}; + +enum XRSessionMode { + "inline", + "immersive-vr", + "immersive-ar" +}; + +dictionary XRSessionInit { + sequence<any> requiredFeatures; + sequence<any> optionalFeatures; +}; + +partial interface XRSystem { + // https://github.com/immersive-web/webxr-test-api/ + [SameObject, Pref="dom.webxr.test"] readonly attribute XRTest test; +}; diff --git a/components/script/dom/webidls/XRTest.webidl b/components/script/dom/webidls/XRTest.webidl new file mode 100644 index 00000000000..1ae98f703bf --- /dev/null +++ b/components/script/dom/webidls/XRTest.webidl @@ -0,0 +1,42 @@ +/* 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/. */ + +// https://github.com/immersive-web/webxr-test-api/ + +[Exposed=Window, Pref="dom.webxr.test"] +interface XRTest { + // Simulates connecting a device to the system. + // Used to instantiate a fake device for use in tests. + Promise<FakeXRDevice> simulateDeviceConnection(FakeXRDeviceInit init); + + // // Simulates a user activation (aka user gesture) for the current scope. + // // The activation is only guaranteed to be valid in the provided function and only applies to WebXR + // // Device API methods. + void simulateUserActivation(Function f); + + // // Disconnect all fake devices + Promise<void> disconnectAllDevices(); +}; + +dictionary FakeXRDeviceInit { + boolean supportsImmersive = false; + sequence<XRSessionMode> supportedModes; + + required sequence<FakeXRViewInit> views; + + // this is actually sequence<any>, but we don't support + // non-string features anyway + sequence<DOMString> supportedFeatures; + // Whether the space supports tracking in inline sessions + boolean supportsTrackingInInline = true; + // The bounds coordinates. If null, bounded reference spaces are not supported. + sequence<FakeXRBoundsPoint> boundsCoodinates; + // Eye level used for calculating floor-level spaces + FakeXRRigidTransformInit floorOrigin; + FakeXRRigidTransformInit viewerOrigin; + + // Hit test extensions: + FakeXRWorldInit world; +}; + diff --git a/components/script/dom/webidls/XRView.webidl b/components/script/dom/webidls/XRView.webidl new file mode 100644 index 00000000000..7f433425a8b --- /dev/null +++ b/components/script/dom/webidls/XRView.webidl @@ -0,0 +1,18 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrview-interface + +enum XREye { + "left", + "right", + "none", +}; + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRView { + readonly attribute XREye eye; + readonly attribute Float32Array projectionMatrix; + readonly attribute XRRigidTransform transform; +}; diff --git a/components/script/dom/webidls/XRViewerPose.webidl b/components/script/dom/webidls/XRViewerPose.webidl new file mode 100644 index 00000000000..6a2663067ca --- /dev/null +++ b/components/script/dom/webidls/XRViewerPose.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrviewerpose-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRViewerPose : XRPose { + // readonly attribute FrozenArray<XRView> views; + // workaround until we have FrozenArray + // see https://github.com/servo/servo/issues/10427#issuecomment-449593626 + readonly attribute any views; +}; diff --git a/components/script/dom/webidls/XRViewport.webidl b/components/script/dom/webidls/XRViewport.webidl new file mode 100644 index 00000000000..325b52c9f8f --- /dev/null +++ b/components/script/dom/webidls/XRViewport.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrviewport-interface + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRViewport { + readonly attribute long x; + readonly attribute long y; + readonly attribute long width; + readonly attribute long height; +}; diff --git a/components/script/dom/webidls/XRWebGLBinding.webidl b/components/script/dom/webidls/XRWebGLBinding.webidl new file mode 100644 index 00000000000..5823423ef86 --- /dev/null +++ b/components/script/dom/webidls/XRWebGLBinding.webidl @@ -0,0 +1,83 @@ +/* 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/. */ + +// https://immersive-web.github.io/layers/#XRWebGLBindingtype +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRWebGLBinding { + constructor(XRSession session, XRWebGLRenderingContext context); + +// readonly attribute double nativeProjectionScaleFactor; + + [Throws] XRProjectionLayer createProjectionLayer(XRTextureType textureType, + optional XRProjectionLayerInit init = {}); + [Throws] XRQuadLayer createQuadLayer(XRTextureType textureType, + optional XRQuadLayerInit init); + [Throws] XRCylinderLayer createCylinderLayer(XRTextureType textureType, + optional XRCylinderLayerInit init); + [Throws] XREquirectLayer createEquirectLayer(XRTextureType textureType, + optional XREquirectLayerInit init); + [Throws] XRCubeLayer createCubeLayer(optional XRCubeLayerInit init); + + [Throws] XRWebGLSubImage getSubImage(XRCompositionLayer layer, XRFrame frame, optional XREye eye = "none"); + [Throws] XRWebGLSubImage getViewSubImage(XRProjectionLayer layer, XRView view); +}; + +dictionary XRProjectionLayerInit { + boolean depth = true; + boolean stencil = false; + boolean alpha = true; + double scaleFactor = 1.0; +}; + +dictionary XRQuadLayerInit : XRLayerInit { + XRRigidTransform? transform; + float width = 1.0; + float height = 1.0; + boolean isStatic = false; +}; + +dictionary XRCylinderLayerInit : XRLayerInit { + XRRigidTransform? transform; + float radius = 2.0; + float centralAngle = 0.78539; + float aspectRatio = 2.0; + boolean isStatic = false; +}; + +dictionary XREquirectLayerInit : XRLayerInit { + XRRigidTransform? transform; + float radius = 0; + float centralHorizontalAngle = 6.28318; + float upperVerticalAngle = 1.570795; + float lowerVerticalAngle = -1.570795; + boolean isStatic = false; +}; + +dictionary XRCubeLayerInit : XRLayerInit { + DOMPointReadOnly? orientation; + boolean isStatic = false; +}; + +dictionary XRLayerInit { + required XRSpace space; + required unsigned long viewPixelWidth; + required unsigned long viewPixelHeight; + XRLayerLayout layout = "mono"; + boolean depth = false; + boolean stencil = false; + boolean alpha = true; +}; + +enum XRTextureType { + "texture", + "texture-array" +}; + +enum XRLayerLayout { + "default", + "mono", + "stereo", + "stereo-left-right", + "stereo-top-bottom" +}; diff --git a/components/script/dom/webidls/XRWebGLLayer.webidl b/components/script/dom/webidls/XRWebGLLayer.webidl new file mode 100644 index 00000000000..87d2dc4dccb --- /dev/null +++ b/components/script/dom/webidls/XRWebGLLayer.webidl @@ -0,0 +1,41 @@ +/* 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/. */ + +// https://immersive-web.github.io/webxr/#xrwebgllayer-interface + +typedef (WebGLRenderingContext or + WebGL2RenderingContext) XRWebGLRenderingContext; + +dictionary XRWebGLLayerInit { + boolean antialias = true; + boolean depth = true; + boolean stencil = false; + boolean alpha = true; + boolean ignoreDepthValues = false; + double framebufferScaleFactor = 1.0; +}; + +[SecureContext, Exposed=Window, Pref="dom.webxr.enabled"] +interface XRWebGLLayer: XRLayer { + [Throws] constructor(XRSession session, + XRWebGLRenderingContext context, + optional XRWebGLLayerInit layerInit = {}); + // Attributes + readonly attribute boolean antialias; + readonly attribute boolean ignoreDepthValues; + + [SameObject] readonly attribute WebGLFramebuffer? framebuffer; + readonly attribute unsigned long framebufferWidth; + readonly attribute unsigned long framebufferHeight; + + // Methods + XRViewport? getViewport(XRView view); + + // // Static Methods + // static double getNativeFramebufferScaleFactor(XRSession session); +}; + +partial interface WebGLRenderingContext { + [Pref="dom.webxr.enabled"] Promise<void> makeXRCompatible(); +}; diff --git a/components/script/dom/webidls/XRWebGLSubImage.webidl b/components/script/dom/webidls/XRWebGLSubImage.webidl new file mode 100644 index 00000000000..2682206cc0a --- /dev/null +++ b/components/script/dom/webidls/XRWebGLSubImage.webidl @@ -0,0 +1,13 @@ +/* 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/. */ + +// https://immersive-web.github.io/layers/#xrwebglsubimagetype +[SecureContext, Exposed=Window, Pref="dom.webxr.layers.enabled"] +interface XRWebGLSubImage : XRSubImage { + [SameObject] readonly attribute WebGLTexture colorTexture; + [SameObject] readonly attribute WebGLTexture? depthStencilTexture; + readonly attribute unsigned long? imageIndex; + readonly attribute unsigned long textureWidth; + readonly attribute unsigned long textureHeight; +}; diff --git a/components/script/dom/websocket.rs b/components/script/dom/websocket.rs index ec69eb93346..a3c9848ed9b 100644 --- a/components/script/dom/websocket.rs +++ b/components/script/dom/websocket.rs @@ -1,48 +1,48 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::WebSocketBinding; -use dom::bindings::codegen::Bindings::WebSocketBinding::{BinaryType, WebSocketMethods}; -use dom::bindings::codegen::UnionTypes::StringOrStringSequence; -use dom::bindings::conversions::ToJSValConvertible; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString, is_token}; -use dom::blob::{Blob, BlobImpl}; -use dom::closeevent::CloseEvent; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::messageevent::MessageEvent; -use dom::urlhelper::UrlHelper; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; +use crate::dom::bindings::codegen::Bindings::WebSocketBinding::{BinaryType, WebSocketMethods}; +use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence; +use crate::dom::bindings::conversions::ToJSValConvertible; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::{is_token, DOMString, USVString}; +use crate::dom::blob::Blob; +use crate::dom::closeevent::CloseEvent; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageevent::MessageEvent; +use crate::script_runtime::CommonScriptMsg; +use crate::script_runtime::ScriptThreadEventCategory::WebSocketEvent; +use crate::task::{TaskCanceller, TaskOnce}; +use crate::task_source::websocket::WebsocketTaskSource; +use crate::task_source::TaskSource; use dom_struct::dom_struct; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; -use js::jsapi::JSAutoCompartment; +use ipc_channel::router::ROUTER; +use js::jsapi::{JSAutoRealm, JSObject}; use js::jsval::UndefinedValue; -use js::typedarray::{ArrayBuffer, CreateWith}; -use net_traits::{WebSocketCommunicate, WebSocketConnectData, WebSocketDomAction, WebSocketNetworkEvent}; -use net_traits::CoreResourceMsg::WebsocketConnect; +use js::rust::CustomAutoRooterGuard; +use js::typedarray::{ArrayBuffer, ArrayBufferView, CreateWith}; +use net_traits::request::{Referrer, RequestBuilder, RequestMode}; use net_traits::MessageData; -use script_runtime::CommonScriptMsg; -use script_runtime::ScriptThreadEventCategory::WebSocketEvent; -use script_thread::{Runnable, RunnableWrapper}; -use servo_url::ServoUrl; -use std::ascii::AsciiExt; +use net_traits::{CoreResourceMsg, FetchChannels}; +use net_traits::{WebSocketDomAction, WebSocketNetworkEvent}; +use profile_traits::ipc as ProfiledIpc; +use script_traits::serializable::BlobImpl; +use servo_url::{ImmutableOrigin, ServoUrl}; use std::borrow::ToOwned; use std::cell::Cell; use std::ptr; -use std::thread; -use task_source::TaskSource; -use task_source::networking::NetworkingTaskSource; -#[derive(JSTraceable, PartialEq, Copy, Clone, Debug, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] enum WebSocketRequestState { Connecting = 0, Open = 1, @@ -68,30 +68,34 @@ mod close_code { pub const TLS_FAILED: u16 = 1015; } -pub fn close_the_websocket_connection(address: Trusted<WebSocket>, - task_source: &NetworkingTaskSource, - wrapper: &RunnableWrapper, - code: Option<u16>, - reason: String) { - let close_task = box CloseTask { +fn close_the_websocket_connection( + address: Trusted<WebSocket>, + task_source: &WebsocketTaskSource, + canceller: &TaskCanceller, + code: Option<u16>, + reason: String, +) { + let close_task = CloseTask { address: address, failed: false, code: code, reason: Some(reason), }; - task_source.queue_with_wrapper(close_task, &wrapper).unwrap(); + let _ = task_source.queue_with_canceller(close_task, &canceller); } -pub fn fail_the_websocket_connection(address: Trusted<WebSocket>, - task_source: &NetworkingTaskSource, - wrapper: &RunnableWrapper) { - let close_task = box CloseTask { +fn fail_the_websocket_connection( + address: Trusted<WebSocket>, + task_source: &WebsocketTaskSource, + canceller: &TaskCanceller, +) { + let close_task = CloseTask { address: address, failed: true, code: Some(close_code::ABNORMAL), reason: None, }; - task_source.queue_with_wrapper(close_task, &wrapper).unwrap(); + let _ = task_source.queue_with_canceller(close_task, &canceller); } #[dom_struct] @@ -101,36 +105,41 @@ pub struct WebSocket { ready_state: Cell<WebSocketRequestState>, buffered_amount: Cell<u64>, clearing_buffer: Cell<bool>, //Flag to tell if there is a running thread to clear buffered_amount - #[ignore_heap_size_of = "Defined in std"] - sender: DOMRefCell<Option<IpcSender<WebSocketDomAction>>>, + #[ignore_malloc_size_of = "Defined in std"] + sender: IpcSender<WebSocketDomAction>, binary_type: Cell<BinaryType>, - protocol: DOMRefCell<String>, //Subprotocol selected by server + protocol: DomRefCell<String>, //Subprotocol selected by server } impl WebSocket { - fn new_inherited(url: ServoUrl) -> WebSocket { + fn new_inherited(url: ServoUrl, sender: IpcSender<WebSocketDomAction>) -> WebSocket { WebSocket { eventtarget: EventTarget::new_inherited(), url: url, ready_state: Cell::new(WebSocketRequestState::Connecting), buffered_amount: Cell::new(0), clearing_buffer: Cell::new(false), - sender: DOMRefCell::new(None), + sender: sender, binary_type: Cell::new(BinaryType::Blob), - protocol: DOMRefCell::new("".to_owned()), + protocol: DomRefCell::new("".to_owned()), } } - fn new(global: &GlobalScope, url: ServoUrl) -> Root<WebSocket> { - reflect_dom_object(box WebSocket::new_inherited(url), - global, WebSocketBinding::Wrap) + fn new( + global: &GlobalScope, + url: ServoUrl, + sender: IpcSender<WebSocketDomAction>, + ) -> DomRoot<WebSocket> { + reflect_dom_object(Box::new(WebSocket::new_inherited(url, sender)), global) } - /// https://html.spec.whatwg.org/multipage/#dom-websocket - pub fn Constructor(global: &GlobalScope, - url: DOMString, - protocols: Option<StringOrStringSequence>) - -> Fallible<Root<WebSocket>> { + /// <https://html.spec.whatwg.org/multipage/#dom-websocket> + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + url: DOMString, + protocols: Option<StringOrStringSequence>, + ) -> Fallible<DomRoot<WebSocket>> { // Steps 1-2. let url_record = ServoUrl::parse(&url).or(Err(Error::Syntax))?; @@ -146,13 +155,11 @@ impl WebSocket { } // Step 5. - let protocols = protocols.map_or(vec![], |p| { - match p { - StringOrStringSequence::String(string) => vec![string.into()], - StringOrStringSequence::StringSequence(seq) => { - seq.into_iter().map(String::from).collect() - }, - } + let protocols = protocols.map_or(vec![], |p| match p { + StringOrStringSequence::String(string) => vec![string.into()], + StringOrStringSequence::StringSequence(seq) => { + seq.into_iter().map(String::from).collect() + }, }); // Step 6. @@ -160,7 +167,10 @@ impl WebSocket { // https://tools.ietf.org/html/rfc6455#section-4.1 // Handshake requirements, step 10 - if protocols[i + 1..].iter().any(|p| p.eq_ignore_ascii_case(protocol)) { + if protocols[i + 1..] + .iter() + .any(|p| p.eq_ignore_ascii_case(protocol)) + { return Err(Error::Syntax); } @@ -170,63 +180,65 @@ impl WebSocket { } } - let ws = WebSocket::new(global, url_record.clone()); + // Create the interface for communication with the resource thread + let (dom_action_sender, resource_action_receiver): ( + IpcSender<WebSocketDomAction>, + IpcReceiver<WebSocketDomAction>, + ) = ipc::channel().unwrap(); + let (resource_event_sender, dom_event_receiver): ( + IpcSender<WebSocketNetworkEvent>, + ProfiledIpc::IpcReceiver<WebSocketNetworkEvent>, + ) = ProfiledIpc::channel(global.time_profiler_chan().clone()).unwrap(); + + let ws = WebSocket::new(global, url_record.clone(), dom_action_sender); let address = Trusted::new(&*ws); - let connect_data = WebSocketConnectData { - resource_url: url_record, - origin: UrlHelper::Origin(&global.get_url()).0, - protocols: protocols, - }; + // Step 8. + let request = RequestBuilder::new(url_record, Referrer::NoReferrer) + .origin(global.origin().immutable().clone()) + .mode(RequestMode::WebSocket { protocols }); - // Create the interface for communication with the resource thread - let (dom_action_sender, resource_action_receiver): - (IpcSender<WebSocketDomAction>, - IpcReceiver<WebSocketDomAction>) = ipc::channel().unwrap(); - let (resource_event_sender, dom_event_receiver): - (IpcSender<WebSocketNetworkEvent>, - IpcReceiver<WebSocketNetworkEvent>) = ipc::channel().unwrap(); - - let connect = WebSocketCommunicate { + let channels = FetchChannels::WebSocket { event_sender: resource_event_sender, action_receiver: resource_action_receiver, }; - - // Step 8. - let _ = global.core_resource_thread().send(WebsocketConnect(connect, connect_data)); - - *ws.sender.borrow_mut() = Some(dom_action_sender); - - let task_source = global.networking_task_source(); - let wrapper = global.get_runnable_wrapper(); - thread::spawn(move || { - while let Ok(event) = dom_event_receiver.recv() { - match event { - WebSocketNetworkEvent::ConnectionEstablished { protocol_in_use } => { - let open_thread = box ConnectionEstablishedTask { - address: address.clone(), - protocol_in_use, - }; - task_source.queue_with_wrapper(open_thread, &wrapper).unwrap(); - }, - WebSocketNetworkEvent::MessageReceived(message) => { - let message_thread = box MessageReceivedTask { - address: address.clone(), - message: message, - }; - task_source.queue_with_wrapper(message_thread, &wrapper).unwrap(); - }, - WebSocketNetworkEvent::Fail => { - fail_the_websocket_connection(address.clone(), - &task_source, &wrapper); - }, - WebSocketNetworkEvent::Close(code, reason) => { - close_the_websocket_connection(address.clone(), - &task_source, &wrapper, code, reason); - }, - } - } - }); + let _ = global + .core_resource_thread() + .send(CoreResourceMsg::Fetch(request, channels)); + + let task_source = global.websocket_task_source(); + let canceller = global.task_canceller(WebsocketTaskSource::NAME); + ROUTER.add_route( + dom_event_receiver.to_opaque(), + Box::new(move |message| match message.to().unwrap() { + WebSocketNetworkEvent::ConnectionEstablished { protocol_in_use } => { + let open_thread = ConnectionEstablishedTask { + address: address.clone(), + protocol_in_use, + }; + let _ = task_source.queue_with_canceller(open_thread, &canceller); + }, + WebSocketNetworkEvent::MessageReceived(message) => { + let message_thread = MessageReceivedTask { + address: address.clone(), + message: message, + }; + let _ = task_source.queue_with_canceller(message_thread, &canceller); + }, + WebSocketNetworkEvent::Fail => { + fail_the_websocket_connection(address.clone(), &task_source, &canceller); + }, + WebSocketNetworkEvent::Close(code, reason) => { + close_the_websocket_connection( + address.clone(), + &task_source, + &canceller, + code, + reason, + ); + }, + }), + ); // Step 7. Ok(ws) @@ -246,7 +258,7 @@ impl WebSocket { match data_byte_len.checked_add(self.buffered_amount.get()) { None => panic!(), - Some(new_amount) => self.buffered_amount.set(new_amount) + Some(new_amount) => self.buffered_amount.set(new_amount), }; if return_after_buffer { @@ -256,18 +268,27 @@ impl WebSocket { if !self.clearing_buffer.get() && self.ready_state.get() == WebSocketRequestState::Open { self.clearing_buffer.set(true); - let task = box BufferedAmountTask { - address: address, - }; + let task = Box::new(BufferedAmountTask { address: address }); + let pipeline_id = self.global().pipeline_id(); self.global() .script_chan() - .send(CommonScriptMsg::RunnableMsg(WebSocketEvent, task)) + // TODO: Use a dedicated `websocket-task-source` task source instead. + .send(CommonScriptMsg::Task( + WebSocketEvent, + task, + Some(pipeline_id), + WebsocketTaskSource::NAME, + )) .unwrap(); } Ok(true) } + + pub fn origin(&self) -> ImmutableOrigin { + self.url.origin() + } } impl WebSocketMethods for WebSocket { @@ -310,18 +331,18 @@ impl WebSocketMethods for WebSocket { // https://html.spec.whatwg.org/multipage/#dom-websocket-protocol fn Protocol(&self) -> DOMString { - DOMString::from(self.protocol.borrow().clone()) + DOMString::from(self.protocol.borrow().clone()) } // https://html.spec.whatwg.org/multipage/#dom-websocket-send fn Send(&self, data: USVString) -> ErrorResult { let data_byte_len = data.0.as_bytes().len() as u64; - let send_data = try!(self.send_impl(data_byte_len)); + let send_data = self.send_impl(data_byte_len)?; if send_data { - let mut other_sender = self.sender.borrow_mut(); - let my_sender = other_sender.as_mut().unwrap(); - let _ = my_sender.send(WebSocketDomAction::SendMessage(MessageData::Text(data.0))); + let _ = self + .sender + .send(WebSocketDomAction::SendMessage(MessageData::Text(data.0))); } Ok(()) @@ -334,15 +355,43 @@ impl WebSocketMethods for WebSocket { If the buffer limit is reached in the first place, there are likely other major problems */ let data_byte_len = blob.Size(); - let send_data = try!(self.send_impl(data_byte_len)); + let send_data = self.send_impl(data_byte_len)?; if send_data { - let mut other_sender = self.sender.borrow_mut(); - let my_sender = other_sender.as_mut().unwrap(); let bytes = blob.get_bytes().unwrap_or(vec![]); - let _ = my_sender.send(WebSocketDomAction::SendMessage(MessageData::Binary(bytes))); + let _ = self + .sender + .send(WebSocketDomAction::SendMessage(MessageData::Binary(bytes))); + } + + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-websocket-send + fn Send__(&self, array: CustomAutoRooterGuard<ArrayBuffer>) -> ErrorResult { + let bytes = array.to_vec(); + let data_byte_len = bytes.len(); + let send_data = self.send_impl(data_byte_len as u64)?; + + if send_data { + let _ = self + .sender + .send(WebSocketDomAction::SendMessage(MessageData::Binary(bytes))); } + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-websocket-send + fn Send___(&self, array: CustomAutoRooterGuard<ArrayBufferView>) -> ErrorResult { + let bytes = array.to_vec(); + let data_byte_len = bytes.len(); + let send_data = self.send_impl(data_byte_len as u64)?; + if send_data { + let _ = self + .sender + .send(WebSocketDomAction::SendMessage(MessageData::Binary(bytes))); + } Ok(()) } @@ -355,50 +404,54 @@ impl WebSocketMethods for WebSocket { } } if let Some(ref reason) = reason { - if reason.0.as_bytes().len() > 123 { //reason cannot be larger than 123 bytes + if reason.0.as_bytes().len() > 123 { + //reason cannot be larger than 123 bytes return Err(Error::Syntax); } } match self.ready_state.get() { - WebSocketRequestState::Closing | WebSocketRequestState::Closed => {} //Do nothing - WebSocketRequestState::Connecting => { //Connection is not yet established + WebSocketRequestState::Closing | WebSocketRequestState::Closed => {}, //Do nothing + WebSocketRequestState::Connecting => { + //Connection is not yet established /*By setting the state to closing, the open function - will abort connecting the websocket*/ + will abort connecting the websocket*/ self.ready_state.set(WebSocketRequestState::Closing); let address = Trusted::new(self); - let task_source = self.global().networking_task_source(); - fail_the_websocket_connection(address, &task_source, &self.global().get_runnable_wrapper()); - } + // TODO: use a dedicated task source, + // https://html.spec.whatwg.org/multipage/#websocket-task-source + // When making the switch, also update the task_canceller call. + let task_source = self.global().websocket_task_source(); + fail_the_websocket_connection( + address, + &task_source, + &self.global().task_canceller(WebsocketTaskSource::NAME), + ); + }, WebSocketRequestState::Open => { self.ready_state.set(WebSocketRequestState::Closing); // Kick off _Start the WebSocket Closing Handshake_ // https://tools.ietf.org/html/rfc6455#section-7.1.2 let reason = reason.map(|reason| reason.0); - let mut other_sender = self.sender.borrow_mut(); - let my_sender = other_sender.as_mut().unwrap(); - let _ = my_sender.send(WebSocketDomAction::Close(code, reason)); - } + let _ = self.sender.send(WebSocketDomAction::Close(code, reason)); + }, } Ok(()) //Return Ok } } - /// Task queued when *the WebSocket connection is established*. -/// https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol:concept-websocket-established +/// <https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol:concept-websocket-established> struct ConnectionEstablishedTask { address: Trusted<WebSocket>, protocol_in_use: Option<String>, } -impl Runnable for ConnectionEstablishedTask { - fn name(&self) -> &'static str { "ConnectionEstablishedTask" } - - /// https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol:concept-websocket-established - fn handler(self: Box<Self>) { +impl TaskOnce for ConnectionEstablishedTask { + /// <https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol:concept-websocket-established> + fn run_once(self) { let ws = self.address.root(); // Step 1. @@ -421,15 +474,13 @@ struct BufferedAmountTask { address: Trusted<WebSocket>, } -impl Runnable for BufferedAmountTask { +impl TaskOnce for BufferedAmountTask { // See https://html.spec.whatwg.org/multipage/#dom-websocket-bufferedamount // // To be compliant with standards, we need to reset bufferedAmount only when the event loop // reaches step 1. In our implementation, the bytes will already have been sent on a background // thread. - fn name(&self) -> &'static str { "BufferedAmountTask" } - - fn handler(self: Box<Self>) { + fn run_once(self) { let ws = self.address.root(); ws.buffered_amount.set(0); @@ -444,10 +495,8 @@ struct CloseTask { reason: Option<String>, } -impl Runnable for CloseTask { - fn name(&self) -> &'static str { "CloseTask" } - - fn handler(self: Box<Self>) { +impl TaskOnce for CloseTask { + fn run_once(self) { let ws = self.address.root(); if ws.ready_state.get() == WebSocketRequestState::Closed { @@ -470,13 +519,15 @@ impl Runnable for CloseTask { let clean_close = !self.failed; let code = self.code.unwrap_or(close_code::NO_STATUS); let reason = DOMString::from(self.reason.unwrap_or("".to_owned())); - let close_event = CloseEvent::new(&ws.global(), - atom!("close"), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable, - clean_close, - code, - reason); + let close_event = CloseEvent::new( + &ws.global(), + atom!("close"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + clean_close, + code, + reason, + ); close_event.upcast::<Event>().fire(ws.upcast()); } } @@ -486,14 +537,15 @@ struct MessageReceivedTask { message: MessageData, } -impl Runnable for MessageReceivedTask { - fn name(&self) -> &'static str { "MessageReceivedTask" } - +impl TaskOnce for MessageReceivedTask { #[allow(unsafe_code)] - fn handler(self: Box<Self>) { + fn run_once(self) { let ws = self.address.root(); - debug!("MessageReceivedTask::handler({:p}): readyState={:?}", &*ws, - ws.ready_state.get()); + debug!( + "MessageReceivedTask::handler({:p}): readyState={:?}", + &*ws, + ws.ready_state.get() + ); // Step 1. if ws.ready_state.get() != WebSocketRequestState::Open { @@ -505,30 +557,37 @@ impl Runnable for MessageReceivedTask { // global.get_cx() returns a valid `JSContext` pointer, so this is safe. unsafe { let cx = global.get_cx(); - let _ac = JSAutoCompartment::new(cx, ws.reflector().get_jsobject().get()); - rooted!(in(cx) let mut message = UndefinedValue()); + let _ac = JSAutoRealm::new(*cx, ws.reflector().get_jsobject().get()); + rooted!(in(*cx) let mut message = UndefinedValue()); match self.message { - MessageData::Text(text) => text.to_jsval(cx, message.handle_mut()), - MessageData::Binary(data) => { - match ws.binary_type.get() { - BinaryType::Blob => { - let blob = Blob::new(&global, BlobImpl::new_from_bytes(data), "".to_owned()); - blob.to_jsval(cx, message.handle_mut()); - } - BinaryType::Arraybuffer => { - rooted!(in(cx) let mut array_buffer = ptr::null_mut()); - assert!(ArrayBuffer::create(cx, - CreateWith::Slice(&data), - array_buffer.handle_mut()) - .is_ok()); - - (*array_buffer).to_jsval(cx, message.handle_mut()); - } - - } + MessageData::Text(text) => text.to_jsval(*cx, message.handle_mut()), + MessageData::Binary(data) => match ws.binary_type.get() { + BinaryType::Blob => { + let blob = + Blob::new(&global, BlobImpl::new_from_bytes(data, "".to_owned())); + blob.to_jsval(*cx, message.handle_mut()); + }, + BinaryType::Arraybuffer => { + rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>()); + assert!(ArrayBuffer::create( + *cx, + CreateWith::Slice(&data), + array_buffer.handle_mut() + ) + .is_ok()); + + (*array_buffer).to_jsval(*cx, message.handle_mut()); + }, }, } - MessageEvent::dispatch_jsval(ws.upcast(), &global, message.handle()); + MessageEvent::dispatch_jsval( + ws.upcast(), + &global, + message.handle(), + Some(&ws.origin().ascii_serialization()), + None, + vec![], + ); } } } diff --git a/components/script/dom/wheelevent.rs b/components/script/dom/wheelevent.rs new file mode 100644 index 00000000000..d7e33b84633 --- /dev/null +++ b/components/script/dom/wheelevent.rs @@ -0,0 +1,160 @@ +/* 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 crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; +use crate::dom::bindings::codegen::Bindings::WheelEventBinding; +use crate::dom::bindings::codegen::Bindings::WheelEventBinding::WheelEventMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::mouseevent::MouseEvent; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use std::cell::Cell; + +#[dom_struct] +pub struct WheelEvent { + mouseevent: MouseEvent, + delta_x: Cell<Finite<f64>>, + delta_y: Cell<Finite<f64>>, + delta_z: Cell<Finite<f64>>, + delta_mode: Cell<u32>, +} + +impl WheelEvent { + fn new_inherited() -> WheelEvent { + WheelEvent { + mouseevent: MouseEvent::new_inherited(), + delta_x: Cell::new(Finite::wrap(0.0)), + delta_y: Cell::new(Finite::wrap(0.0)), + delta_z: Cell::new(Finite::wrap(0.0)), + delta_mode: Cell::new(0), + } + } + + pub fn new_unintialized(window: &Window) -> DomRoot<WheelEvent> { + reflect_dom_object(Box::new(WheelEvent::new_inherited()), window) + } + + pub fn new( + window: &Window, + type_: DOMString, + can_bubble: EventBubbles, + cancelable: EventCancelable, + view: Option<&Window>, + detail: i32, + delta_x: Finite<f64>, + delta_y: Finite<f64>, + delta_z: Finite<f64>, + delta_mode: u32, + ) -> DomRoot<WheelEvent> { + let ev = WheelEvent::new_unintialized(window); + ev.InitWheelEvent( + type_, + bool::from(can_bubble), + bool::from(cancelable), + view, + detail, + delta_x, + delta_y, + delta_z, + delta_mode, + ); + + ev + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &WheelEventBinding::WheelEventInit, + ) -> Fallible<DomRoot<WheelEvent>> { + let event = WheelEvent::new( + window, + type_, + EventBubbles::from(init.parent.parent.parent.parent.bubbles), + EventCancelable::from(init.parent.parent.parent.parent.cancelable), + init.parent.parent.parent.view.as_deref(), + init.parent.parent.parent.detail, + init.deltaX, + init.deltaY, + init.deltaZ, + init.deltaMode, + ); + + Ok(event) + } +} + +impl WheelEventMethods for WheelEvent { + // https://w3c.github.io/uievents/#widl-WheelEvent-deltaX + fn DeltaX(&self) -> Finite<f64> { + self.delta_x.get() + } + + // https://w3c.github.io/uievents/#widl-WheelEvent-deltaY + fn DeltaY(&self) -> Finite<f64> { + self.delta_y.get() + } + + // https://w3c.github.io/uievents/#widl-WheelEvent-deltaZ + fn DeltaZ(&self) -> Finite<f64> { + self.delta_z.get() + } + + // https://w3c.github.io/uievents/#widl-WheelEvent-deltaMode + fn DeltaMode(&self) -> u32 { + self.delta_mode.get() + } + + // https://w3c.github.io/uievents/#widl-WheelEvent-initWheelEvent + fn InitWheelEvent( + &self, + type_arg: DOMString, + can_bubble_arg: bool, + cancelable_arg: bool, + view_arg: Option<&Window>, + detail_arg: i32, + delta_x_arg: Finite<f64>, + delta_y_arg: Finite<f64>, + delta_z_arg: Finite<f64>, + delta_mode_arg: u32, + ) { + if self.upcast::<Event>().dispatching() { + return; + } + + self.upcast::<MouseEvent>().InitMouseEvent( + type_arg, + can_bubble_arg, + cancelable_arg, + view_arg, + detail_arg, + self.mouseevent.ScreenX(), + self.mouseevent.ScreenY(), + self.mouseevent.ClientX(), + self.mouseevent.ClientY(), + self.mouseevent.CtrlKey(), + self.mouseevent.AltKey(), + self.mouseevent.ShiftKey(), + self.mouseevent.MetaKey(), + self.mouseevent.Button(), + self.mouseevent.GetRelatedTarget().as_deref(), + ); + self.delta_x.set(delta_x_arg); + self.delta_y.set(delta_y_arg); + self.delta_z.set(delta_z_arg); + self.delta_mode.set(delta_mode_arg); + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.mouseevent.IsTrusted() + } +} diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 18b4b1311b9..b6d2c114388 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -1,138 +1,162 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::{ + DocumentMethods, DocumentReadyState, +}; +use crate::dom::bindings::codegen::Bindings::HistoryBinding::HistoryBinding::HistoryMethods; +use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{ + ImageBitmapOptions, ImageBitmapSource, +}; +use crate::dom::bindings::codegen::Bindings::MediaQueryListBinding::MediaQueryListBinding::MediaQueryListMethods; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit; +use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; +use crate::dom::bindings::codegen::Bindings::WindowBinding::{ + self, FrameRequestCallback, WindowMethods, WindowPostMessageOptions, +}; +use crate::dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions}; +use crate::dom::bindings::codegen::UnionTypes::{RequestOrUSVString, StringOrFunction}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox}; +use crate::dom::bindings::utils::{GlobalStaticData, WindowProxyHandler}; +use crate::dom::bindings::weakref::DOMTracker; +use crate::dom::bluetooth::BluetoothExtraPermissionData; +use crate::dom::crypto::Crypto; +use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; +use crate::dom::customelementregistry::CustomElementRegistry; +use crate::dom::document::{AnimationFrameCallback, Document, ReflowTriggerCondition}; +use crate::dom::element::Element; +use crate::dom::event::{Event, EventStatus}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::hashchangeevent::HashChangeEvent; +use crate::dom::history::History; +use crate::dom::identityhub::Identities; +use crate::dom::location::Location; +use crate::dom::mediaquerylist::{MediaQueryList, MediaQueryListMatchState}; +use crate::dom::mediaquerylistevent::MediaQueryListEvent; +use crate::dom::messageevent::MessageEvent; +use crate::dom::navigator::Navigator; +use crate::dom::node::{document_from_node, from_untrusted_node_address, Node, NodeDamage}; +use crate::dom::performance::Performance; +use crate::dom::promise::Promise; +use crate::dom::screen::Screen; +use crate::dom::selection::Selection; +use crate::dom::storage::Storage; +use crate::dom::testrunner::TestRunner; +use crate::dom::webglrenderingcontext::WebGLCommandSender; +use crate::dom::windowproxy::WindowProxy; +use crate::dom::worklet::Worklet; +use crate::dom::workletglobalscope::WorkletGlobalScopeType; +use crate::fetch; +use crate::layout_image::fetch_image_for_layout; +use crate::malloc_size_of::MallocSizeOf; +use crate::microtask::MicrotaskQueue; +use crate::realms::InRealm; +use crate::script_runtime::{ + CommonScriptMsg, JSContext, Runtime, ScriptChan, ScriptPort, ScriptThreadEventCategory, +}; +use crate::script_thread::{ImageCacheMsg, MainThreadScriptChan, MainThreadScriptMsg}; +use crate::script_thread::{ScriptThread, SendableMainThreadScriptChan}; +use crate::task_manager::TaskManager; +use crate::task_source::{TaskSource, TaskSourceName}; +use crate::timers::{IsInterval, TimerCallback}; +use crate::webdriver_handlers::jsval_to_webdriver; use app_units::Au; +use backtrace::Backtrace; use base64; use bluetooth_traits::BluetoothRequest; -use cssparser::Parser; +use canvas_traits::webgl::WebGLChan; +use crossbeam_channel::{unbounded, Sender, TryRecvError}; +use cssparser::{Parser, ParserInput, SourceLocation}; use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType}; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull; -use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; -use dom::bindings::codegen::Bindings::FunctionBinding::Function; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionState; -use dom::bindings::codegen::Bindings::RequestBinding::RequestInit; -use dom::bindings::codegen::Bindings::WindowBinding::{self, FrameRequestCallback, WindowMethods}; -use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions}; -use dom::bindings::codegen::UnionTypes::RequestOrUSVString; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::num::Finite; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::bindings::structuredclone::StructuredCloneData; -use dom::bindings::trace::RootedTraceableBox; -use dom::bindings::utils::{GlobalStaticData, WindowProxyHandler}; -use dom::bluetooth::BluetoothExtraPermissionData; -use dom::browsingcontext::BrowsingContext; -use dom::crypto::Crypto; -use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; -use dom::document::{AnimationFrameCallback, Document}; -use dom::element::Element; -use dom::event::Event; -use dom::globalscope::GlobalScope; -use dom::history::History; -use dom::htmliframeelement::build_mozbrowser_custom_event; -use dom::location::Location; -use dom::mediaquerylist::{MediaQueryList, WeakMediaQueryListVec}; -use dom::messageevent::MessageEvent; -use dom::navigator::Navigator; -use dom::node::{Node, NodeDamage, document_from_node, from_untrusted_node_address}; -use dom::performance::Performance; -use dom::promise::Promise; -use dom::screen::Screen; -use dom::storage::Storage; -use dom::testrunner::TestRunner; use dom_struct::dom_struct; -use euclid::{Point2D, Rect, Size2D}; -use fetch; -use ipc_channel::ipc::{self, IpcSender}; +use embedder_traits::{EmbedderMsg, EventLoopWaker, PromptDefinition, PromptOrigin, PromptResult}; +use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect}; +use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; +use ipc_channel::ipc::IpcSender; use ipc_channel::router::ROUTER; -use js::jsapi::{HandleObject, HandleValue, JSAutoCompartment, JSContext}; -use js::jsapi::{JS_GC, JS_GetRuntime}; +use js::conversions::ToJSValConvertible; +use js::jsapi::Heap; +use js::jsapi::JSAutoRealm; +use js::jsapi::JSObject; +use js::jsapi::JSPROP_ENUMERATE; +use js::jsapi::{GCReason, StackFormat, JS_GC}; use js::jsval::UndefinedValue; -use js::rust::Runtime; -use layout_image::fetch_image_for_layout; -use msg::constellation_msg::{FrameType, PipelineId}; -use net_traits::{ResourceThreads, ReferrerPolicy}; +use js::jsval::{JSVal, NullValue}; +use js::rust::wrappers::JS_DefineProperty; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; +use media::WindowGLContext; +use msg::constellation_msg::{BrowsingContextId, PipelineId}; use net_traits::image_cache::{ImageCache, ImageResponder, ImageResponse}; use net_traits::image_cache::{PendingImageId, PendingImageResponse}; use net_traits::storage_thread::StorageType; +use net_traits::ResourceThreads; use num_traits::ToPrimitive; -use open; +use parking_lot::Mutex as ParkMutex; +use profile_traits::ipc as ProfiledIpc; use profile_traits::mem::ProfilerChan as MemProfilerChan; -use profile_traits::time::ProfilerChan as TimeProfilerChan; -use script_layout_interface::{TrustedNodeAddress, PendingImageState}; -use script_layout_interface::message::{Msg, Reflow, ReflowQueryType, ScriptReflow}; -use script_layout_interface::reporter::CSSErrorReporter; +use profile_traits::time::{ProfilerChan as TimeProfilerChan, ProfilerMsg}; +use script_layout_interface::message::{Msg, QueryMsg, Reflow, ReflowGoal, ScriptReflow}; use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC}; -use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse}; -use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse}; -use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory}; -use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper}; -use script_thread::{SendableMainThreadScriptChan, ImageCacheMsg}; -use script_traits::{ConstellationControlMsg, LoadData, MozBrowserEvent, UntrustedNodeAddress}; -use script_traits::{DocumentState, TimerEvent, TimerEventId}; -use script_traits::{ScriptMsg as ConstellationMsg, TimerSchedulerMsg, WindowSizeData, WindowSizeType}; +use script_layout_interface::rpc::{ + NodeScrollIdResponse, ResolvedStyleResponse, TextIndexResponse, +}; +use script_layout_interface::{PendingImageState, TrustedNodeAddress}; use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult}; -use servo_atoms::Atom; -use servo_config::opts; -use servo_config::prefs::PREFS; -use servo_geometry::{f32_rect_to_au_rect, max_rect}; -use servo_url::{Host, ImmutableOrigin, MutableOrigin, ServoUrl}; -use std::ascii::AsciiExt; +use script_traits::{ConstellationControlMsg, DocumentState, HistoryEntryReplacement, LoadData}; +use script_traits::{ + ScriptMsg, ScriptToConstellationChan, ScrollState, StructuredSerializedData, TimerEventId, +}; +use script_traits::{TimerSchedulerMsg, WebrenderIpcSender, WindowSizeData, WindowSizeType}; +use selectors::attr::CaseSensitivity; +use servo_arc::Arc as ServoArc; +use servo_geometry::{f32_rect_to_au_rect, MaxRect}; +use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; +use std::borrow::Cow; use std::borrow::ToOwned; use std::cell::Cell; -use std::collections::{HashMap, HashSet}; use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; use std::default::Default; use std::env; -use std::fs; -use std::io::{Write, stderr, stdout}; +use std::io::{stderr, stdout, Write}; use std::mem; use std::rc::Rc; -use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::{Sender, channel}; -use std::sync::mpsc::TryRecvError::{Disconnected, Empty}; -use style::context::ReflowGoal; -use style::error_reporting::ParseErrorReporter; +use std::sync::{Arc, Mutex}; +use style::dom::OpaqueNode; +use style::error_reporting::{ContextualParseError, ParseErrorReporter}; use style::media_queries; -use style::parser::{LengthParsingMode, ParserContext as CssParserContext}; -use style::properties::PropertyId; -use style::properties::longhands::overflow_x; +use style::parser::ParserContext as CssParserContext; +use style::properties::style_structs::Font; +use style::properties::{PropertyId, ShorthandId}; use style::selector_parser::PseudoElement; use style::str::HTML_SPACE_CHARACTERS; -use style::stylesheets::CssRuleType; -use task_source::dom_manipulation::DOMManipulationTaskSource; -use task_source::file_reading::FileReadingTaskSource; -use task_source::history_traversal::HistoryTraversalTaskSource; -use task_source::networking::NetworkingTaskSource; -use task_source::user_interaction::UserInteractionTaskSource; -use time; -use timers::{IsInterval, TimerCallback}; -#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] -use tinyfiledialogs::{self, MessageBoxIcon}; +use style::stylesheets::{CssRuleType, Origin}; +use style_traits::{CSSPixel, DevicePixel, ParsingMode}; use url::Position; -use webdriver_handlers::jsval_to_webdriver; -use webrender_traits::ClipId; -use webvr_traits::WebVRMsg; +use webrender_api::units::{DeviceIntPoint, DeviceIntSize, LayoutPixel}; +use webrender_api::{DocumentId, ExternalScrollId}; /// Current state of the window object -#[derive(JSTraceable, Copy, Clone, Debug, PartialEq, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] enum WindowState { Alive, - Zombie, // Pipeline is closed, but the window hasn't been GCed yet. + Zombie, // Pipeline is closed, but the window hasn't been GCed yet. } /// Extra information concerning the reason for reflowing. -#[derive(Debug, HeapSizeOf)] +#[derive(Debug, MallocSizeOf)] pub enum ReflowReason { CachedPageNeededReflow, RefreshTick, @@ -149,82 +173,80 @@ pub enum ReflowReason { ImageLoaded, RequestAnimationFrame, WebFontLoaded, + WorkletLoaded, FramedContentChanged, IFrameLoadEvent, MissingExplicitReflow, ElementStateChanged, + PendingReflow, } #[dom_struct] pub struct Window { globalscope: GlobalScope, - #[ignore_heap_size_of = "trait objects are hard"] + #[ignore_malloc_size_of = "trait objects are hard"] script_chan: MainThreadScriptChan, - #[ignore_heap_size_of = "task sources are hard"] - dom_manipulation_task_source: DOMManipulationTaskSource, - #[ignore_heap_size_of = "task sources are hard"] - user_interaction_task_source: UserInteractionTaskSource, - #[ignore_heap_size_of = "task sources are hard"] - networking_task_source: NetworkingTaskSource, - #[ignore_heap_size_of = "task sources are hard"] - history_traversal_task_source: HistoryTraversalTaskSource, - #[ignore_heap_size_of = "task sources are hard"] - file_reading_task_source: FileReadingTaskSource, - navigator: MutNullableJS<Navigator>, - #[ignore_heap_size_of = "Arc"] - image_cache: Arc<ImageCache>, - #[ignore_heap_size_of = "channels are hard"] + task_manager: TaskManager, + navigator: MutNullableDom<Navigator>, + #[ignore_malloc_size_of = "Arc"] + image_cache: Arc<dyn ImageCache>, + #[ignore_malloc_size_of = "channels are hard"] image_cache_chan: Sender<ImageCacheMsg>, - browsing_context: MutNullableJS<BrowsingContext>, - document: MutNullableJS<Document>, - history: MutNullableJS<History>, - performance: MutNullableJS<Performance>, - navigation_start: u64, - navigation_start_precise: f64, - screen: MutNullableJS<Screen>, - session_storage: MutNullableJS<Storage>, - local_storage: MutNullableJS<Storage>, - status: DOMRefCell<DOMString>, + window_proxy: MutNullableDom<WindowProxy>, + document: MutNullableDom<Document>, + location: MutNullableDom<Location>, + history: MutNullableDom<History>, + custom_element_registry: MutNullableDom<CustomElementRegistry>, + performance: MutNullableDom<Performance>, + navigation_start: Cell<u64>, + navigation_start_precise: Cell<u64>, + screen: MutNullableDom<Screen>, + session_storage: MutNullableDom<Storage>, + local_storage: MutNullableDom<Storage>, + status: DomRefCell<DOMString>, /// For sending timeline markers. Will be ignored if /// no devtools server - devtools_markers: DOMRefCell<HashSet<TimelineMarkerType>>, - #[ignore_heap_size_of = "channels are hard"] - devtools_marker_sender: DOMRefCell<Option<IpcSender<Option<TimelineMarker>>>>, + devtools_markers: DomRefCell<HashSet<TimelineMarkerType>>, + #[ignore_malloc_size_of = "channels are hard"] + devtools_marker_sender: DomRefCell<Option<IpcSender<Option<TimelineMarker>>>>, /// Pending resize event, if any. resize_event: Cell<Option<(WindowSizeData, WindowSizeType)>>, /// Parent id associated with this page, if any. - parent_info: Option<(PipelineId, FrameType)>, + parent_info: Option<PipelineId>, /// Global static data related to the DOM. dom_static: GlobalStaticData, /// The JavaScript runtime. - #[ignore_heap_size_of = "Rc<T> is hard"] - js_runtime: DOMRefCell<Option<Rc<Runtime>>>, + #[ignore_malloc_size_of = "Rc<T> is hard"] + js_runtime: DomRefCell<Option<Rc<Runtime>>>, /// A handle for communicating messages to the layout thread. - #[ignore_heap_size_of = "channels are hard"] + /// + /// This channel shouldn't be accessed directly, but through `Window::layout_chan()`, + /// which returns `None` if there's no layout thread anymore. + #[ignore_malloc_size_of = "channels are hard"] layout_chan: Sender<Msg>, /// A handle to perform RPC calls into the layout, quickly. - #[ignore_heap_size_of = "trait objects are hard"] - layout_rpc: Box<LayoutRPC + Send + 'static>, + #[ignore_malloc_size_of = "trait objects are hard"] + layout_rpc: Box<dyn LayoutRPC + Send + 'static>, /// The current size of the window, in pixels. - window_size: Cell<Option<WindowSizeData>>, + window_size: Cell<WindowSizeData>, /// A handle for communicating messages to the bluetooth thread. - #[ignore_heap_size_of = "channels are hard"] + #[ignore_malloc_size_of = "channels are hard"] bluetooth_thread: IpcSender<BluetoothRequest>, bluetooth_extra_permission_data: BluetoothExtraPermissionData, /// An enlarged rectangle around the page contents visible in the viewport, used /// to prevent creating display list items for content that is far away from the viewport. - page_clip_rect: Cell<Rect<Au>>, + page_clip_rect: Cell<UntypedRect<Au>>, /// Flag to suppress reflows. The first reflow will come either with /// RefreshTick or with FirstLoad. Until those first reflows, we want to @@ -235,109 +257,203 @@ pub struct Window { pending_reflow_count: Cell<u32>, /// A channel for communicating results of async scripts back to the webdriver server - #[ignore_heap_size_of = "channels are hard"] - webdriver_script_chan: DOMRefCell<Option<IpcSender<WebDriverJSResult>>>, + #[ignore_malloc_size_of = "channels are hard"] + webdriver_script_chan: DomRefCell<Option<IpcSender<WebDriverJSResult>>>, /// The current state of the window object current_state: Cell<WindowState>, - current_viewport: Cell<Rect<Au>>, - - /// A flag to prevent async events from attempting to interact with this window. - #[ignore_heap_size_of = "defined in std"] - ignore_further_async_events: DOMRefCell<Arc<AtomicBool>>, + current_viewport: Cell<UntypedRect<Au>>, error_reporter: CSSErrorReporter, /// A list of scroll offsets for each scrollable element. - scroll_offsets: DOMRefCell<HashMap<UntrustedNodeAddress, Point2D<f32>>>, + scroll_offsets: DomRefCell<HashMap<OpaqueNode, Vector2D<f32, LayoutPixel>>>, /// All the MediaQueryLists we need to update - media_query_lists: WeakMediaQueryListVec, + media_query_lists: DOMTracker<MediaQueryList>, - test_runner: MutNullableJS<TestRunner>, + test_runner: MutNullableDom<TestRunner>, - /// A handle for communicating messages to the webvr thread, if available. - #[ignore_heap_size_of = "channels are hard"] - webvr_thread: Option<IpcSender<WebVRMsg>>, + /// A handle for communicating messages to the WebGL thread, if available. + #[ignore_malloc_size_of = "channels are hard"] + webgl_chan: Option<WebGLChan>, - /// A map for storing the previous permission state read results. - permission_state_invocation_results: DOMRefCell<HashMap<String, PermissionState>>, + #[ignore_malloc_size_of = "defined in webxr"] + webxr_registry: webxr_api::Registry, /// 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 /// 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<HashMap<PendingImageId, Vec<JS<Node>>>>, + pending_layout_images: DomRefCell<HashMap<PendingImageId, Vec<Dom<Node>>>>, /// Directory to store unminified scripts for this window if unminify-js /// opt is enabled. - unminified_js_dir: DOMRefCell<Option<String>>, + unminified_js_dir: DomRefCell<Option<String>>, + + /// Directory with stored unminified scripts + local_script_source: Option<String>, + + /// Worklets + test_worklet: MutNullableDom<Worklet>, + /// <https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet> + paint_worklet: MutNullableDom<Worklet>, + /// The Webrender Document id associated with this window. + #[ignore_malloc_size_of = "defined in webrender_api"] + webrender_document: DocumentId, + + /// Flag to identify whether mutation observers are present(true)/absent(false) + exists_mut_observer: Cell<bool>, + + /// Webrender API Sender + #[ignore_malloc_size_of = "Wraps an IpcSender"] + webrender_api_sender: WebrenderIpcSender, + + /// Indicate whether a SetDocumentStatus message has been sent after a reflow is complete. + /// It is used to avoid sending idle message more than once, which is unneccessary. + has_sent_idle_message: Cell<bool>, + + /// Flag that indicates if the layout thread is busy handling a request. + #[ignore_malloc_size_of = "Arc<T> is hard"] + layout_is_busy: Arc<AtomicBool>, + + /// Emits notifications when there is a relayout. + relayout_event: bool, + + /// True if it is safe to write to the image. + prepare_for_screenshot: bool, + + /// Unminify Javascript. + unminify_js: bool, + + /// Where to load userscripts from, if any. An empty string will load from + /// the resources/user-agent-js directory, and if the option isn't passed userscripts + /// won't be loaded. + userscripts_path: Option<String>, + + /// Replace unpaired surrogates in DOM strings with U+FFFD. + /// See <https://github.com/servo/servo/issues/6564> + replace_surrogates: bool, + + /// Window's GL context from application + #[ignore_malloc_size_of = "defined in script_thread"] + player_context: WindowGLContext, + + /// A mechanism to force the compositor to process events. + #[ignore_malloc_size_of = "traits are cumbersome"] + event_loop_waker: Option<Box<dyn EventLoopWaker>>, + + visible: Cell<bool>, + + /// A shared marker for the validity of any cached layout values. A value of true + /// indicates that any such values remain valid; any new layout that invalidates + /// those values will cause the marker to be set to false. + #[ignore_malloc_size_of = "Rc is hard"] + layout_marker: DomRefCell<Rc<Cell<bool>>>, + + /// https://dom.spec.whatwg.org/#window-current-event + current_event: DomRefCell<Option<Dom<Event>>>, } impl Window { + pub fn task_manager(&self) -> &TaskManager { + &self.task_manager + } + + pub fn get_exists_mut_observer(&self) -> bool { + self.exists_mut_observer.get() + } + + pub fn set_exists_mut_observer(&self) { + self.exists_mut_observer.set(true); + } + #[allow(unsafe_code)] pub fn clear_js_runtime_for_script_deallocation(&self) { unsafe { *self.js_runtime.borrow_for_script_deallocation() = None; - self.browsing_context.set(None); + self.window_proxy.set(None); self.current_state.set(WindowState::Zombie); - self.ignore_further_async_events.borrow().store(true, Ordering::Relaxed); + self.ignore_all_tasks(); } } - pub fn origin(&self) -> &MutableOrigin { - self.globalscope.origin() - } - - pub fn get_cx(&self) -> *mut JSContext { - self.js_runtime.borrow().as_ref().unwrap().cx() - } - - pub fn dom_manipulation_task_source(&self) -> DOMManipulationTaskSource { - self.dom_manipulation_task_source.clone() + /// A convenience method for + /// https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded + pub fn discard_browsing_context(&self) { + let proxy = match self.window_proxy.get() { + Some(proxy) => proxy, + None => panic!("Discarding a BC from a window that has none"), + }; + proxy.discard_browsing_context(); + // Step 4 of https://html.spec.whatwg.org/multipage/#discard-a-document + // Other steps performed when the `PipelineExit` message + // is handled by the ScriptThread. + self.ignore_all_tasks(); + } + + /// Cancel all current, and ignore all subsequently queued, tasks. + pub fn ignore_all_tasks(&self) { + let mut ignore_flags = self.task_manager.task_cancellers.borrow_mut(); + for task_source_name in TaskSourceName::all() { + let flag = ignore_flags + .entry(task_source_name) + .or_insert(Default::default()); + flag.store(true, Ordering::SeqCst); + } } - pub fn user_interaction_task_source(&self) -> UserInteractionTaskSource { - self.user_interaction_task_source.clone() + /// Get a sender to the time profiler thread. + pub fn time_profiler_chan(&self) -> &TimeProfilerChan { + self.globalscope.time_profiler_chan() } - pub fn networking_task_source(&self) -> NetworkingTaskSource { - self.networking_task_source.clone() + pub fn origin(&self) -> &MutableOrigin { + self.globalscope.origin() } - pub fn history_traversal_task_source(&self) -> Box<ScriptChan + Send> { - self.history_traversal_task_source.clone() + #[allow(unsafe_code)] + pub fn get_cx(&self) -> JSContext { + unsafe { JSContext::from_ptr(self.js_runtime.borrow().as_ref().unwrap().cx()) } } - pub fn file_reading_task_source(&self) -> FileReadingTaskSource { - self.file_reading_task_source.clone() + pub fn get_js_runtime(&self) -> Ref<Option<Rc<Runtime>>> { + self.js_runtime.borrow() } pub fn main_thread_script_chan(&self) -> &Sender<MainThreadScriptMsg> { &self.script_chan.0 } - pub fn parent_info(&self) -> Option<(PipelineId, FrameType)> { + pub fn parent_info(&self) -> Option<PipelineId> { self.parent_info } - pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) { - let (tx, rx) = channel(); - (box SendableMainThreadScriptChan(tx), box rx) + pub fn new_script_pair(&self) -> (Box<dyn ScriptChan + Send>, Box<dyn ScriptPort + Send>) { + let (tx, rx) = unbounded(); + (Box::new(SendableMainThreadScriptChan(tx)), Box::new(rx)) } - pub fn image_cache(&self) -> Arc<ImageCache> { + pub fn image_cache(&self) -> Arc<dyn ImageCache> { self.image_cache.clone() } /// This can panic if it is called after the browsing context has been discarded - pub fn browsing_context(&self) -> Root<BrowsingContext> { - self.browsing_context.get().unwrap() - } - - pub fn maybe_browsing_context(&self) -> Option<Root<BrowsingContext>> { - self.browsing_context.get() + pub fn window_proxy(&self) -> DomRoot<WindowProxy> { + self.window_proxy.get().unwrap() + } + + /// Returns the window proxy if it has not been discarded. + /// <https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded> + pub fn undiscarded_window_proxy(&self) -> Option<DomRoot<WindowProxy>> { + self.window_proxy.get().and_then(|window_proxy| { + if window_proxy.is_browsing_context_discarded() { + None + } else { + Some(window_proxy) + } + }) } pub fn bluetooth_thread(&self) -> IpcSender<BluetoothRequest> { @@ -345,30 +461,37 @@ impl Window { } pub fn bluetooth_extra_permission_data(&self) -> &BluetoothExtraPermissionData { - &self.bluetooth_extra_permission_data + &self.bluetooth_extra_permission_data } - pub fn css_error_reporter(&self) -> &ParseErrorReporter { - &self.error_reporter + pub fn css_error_reporter(&self) -> Option<&dyn ParseErrorReporter> { + Some(&self.error_reporter) } /// Sets a new list of scroll offsets. /// /// This is called when layout gives us new ones and WebRender is in use. - pub fn set_scroll_offsets(&self, offsets: HashMap<UntrustedNodeAddress, Point2D<f32>>) { + pub fn set_scroll_offsets(&self, offsets: HashMap<OpaqueNode, Vector2D<f32, LayoutPixel>>) { *self.scroll_offsets.borrow_mut() = offsets } - pub fn current_viewport(&self) -> Rect<Au> { + pub fn current_viewport(&self) -> UntypedRect<Au> { self.current_viewport.clone().get() } - pub fn webvr_thread(&self) -> Option<IpcSender<WebVRMsg>> { - self.webvr_thread.clone() + pub(crate) fn webgl_chan(&self) -> Option<WebGLCommandSender> { + self.webgl_chan + .as_ref() + .map(|chan| WebGLCommandSender::new(chan.clone(), self.get_event_loop_waker())) + } + + pub fn webxr_registry(&self) -> webxr_api::Registry { + self.webxr_registry.clone() } - pub fn permission_state_invocation_results(&self) -> &DOMRefCell<HashMap<String, PermissionState>> { - &self.permission_state_invocation_results + fn new_paint_worklet(&self) -> DomRoot<Worklet> { + debug!("Creating new paint worklet."); + Worklet::new(self, WorkletGlobalScopeType::Paint) } pub fn pending_image_notification(&self, response: PendingImageResponse) { @@ -385,25 +508,47 @@ impl Window { node.dirty(NodeDamage::OtherNodeDamage); } match response.response { - ImageResponse::MetadataLoaded(_) => {} - ImageResponse::Loaded(_) | - ImageResponse::PlaceholderLoaded(_) | - ImageResponse::None => { nodes.remove(); } + ImageResponse::MetadataLoaded(_) => {}, + ImageResponse::Loaded(_, _) | + ImageResponse::PlaceholderLoaded(_, _) | + ImageResponse::None => { + nodes.remove(); + }, } self.add_pending_reflow(); } -} -#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] -fn display_alert_dialog(message: &str) { - if !opts::get().headless { - tinyfiledialogs::message_box_ok("Alert!", message, MessageBoxIcon::Warning); + pub fn get_webrender_api_sender(&self) -> WebrenderIpcSender { + self.webrender_api_sender.clone() + } + + pub fn get_userscripts_path(&self) -> Option<String> { + self.userscripts_path.clone() + } + + pub fn replace_surrogates(&self) -> bool { + self.replace_surrogates + } + + pub fn unminify_js(&self) -> bool { + self.unminify_js + } + + pub fn get_player_context(&self) -> WindowGLContext { + self.player_context.clone() } -} -#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] -fn display_alert_dialog(_message: &str) { - // tinyfiledialogs not supported on Android + pub fn get_event_loop_waker(&self) -> Option<Box<dyn EventLoopWaker>> { + self.event_loop_waker.as_ref().map(|w| (*w).clone_box()) + } + + // see note at https://dom.spec.whatwg.org/#concept-event-dispatch step 2 + pub fn dispatch_event_with_target_override(&self, event: &Event) -> EventStatus { + if self.has_document() { + assert!(self.Document().can_invoke_script()); + } + event.dispatch(self.upcast(), true) + } } // https://html.spec.whatwg.org/multipage/#atob @@ -432,7 +577,8 @@ pub fn base64_atob(input: DOMString) -> Fallible<DOMString> { fn is_html_space(c: char) -> bool { HTML_SPACE_CHARACTERS.iter().any(|&m| m == c) } - let without_spaces = input.chars() + let without_spaces = input + .chars() .filter(|&c| !is_html_space(c)) .collect::<String>(); let mut input = &*without_spaces; @@ -451,7 +597,7 @@ pub fn base64_atob(input: DOMString) -> Fallible<DOMString> { // "If the length of input divides by 4 leaving a remainder of 1, // throw an InvalidCharacterError exception and abort these steps." if input.len() % 4 == 1 { - return Err(Error::InvalidCharacter) + return Err(Error::InvalidCharacter); } // "If input contains a character that is not in the following list of @@ -461,14 +607,16 @@ pub fn base64_atob(input: DOMString) -> Fallible<DOMString> { // U+002B PLUS SIGN (+) // U+002F SOLIDUS (/) // Alphanumeric ASCII characters" - if input.chars().any(|c| c != '+' && c != '/' && !c.is_alphanumeric()) { - return Err(Error::InvalidCharacter) + if input + .chars() + .any(|c| c != '+' && c != '/' && !c.is_alphanumeric()) + { + return Err(Error::InvalidCharacter); } - match base64::decode(&input) { - Ok(data) => Ok(DOMString::from(data.iter().map(|&b| b as char).collect::<String>())), - Err(..) => Err(Error::InvalidCharacter) - } + let data = base64::decode_config(&input, base64::STANDARD.decode_allow_trailing_bits(true)) + .map_err(|_| Error::InvalidCharacter)?; + Ok(data.iter().map(|&b| b as char).collect::<String>().into()) } impl WindowMethods for Window { @@ -479,7 +627,7 @@ impl WindowMethods for Window { // https://html.spec.whatwg.org/multipage/#dom-alert fn Alert(&self, s: DOMString) { - // Right now, just print to the console + // Print to the console. // Ensure that stderr doesn't trample through the alert() we use to // communicate test results (see executorservo.py in wptrunner). { @@ -487,135 +635,269 @@ impl WindowMethods for Window { let mut stderr = stderr.lock(); let stdout = stdout(); let mut stdout = stdout.lock(); - writeln!(&mut stdout, "ALERT: {}", s).unwrap(); + writeln!(&mut stdout, "\nALERT: {}", s).unwrap(); stdout.flush().unwrap(); stderr.flush().unwrap(); } + let (sender, receiver) = + ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); + let prompt = PromptDefinition::Alert(s.to_string(), sender); + let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted); + self.send_to_embedder(msg); + receiver.recv().unwrap(); + } + + // https://html.spec.whatwg.org/multipage/#dom-confirm + fn Confirm(&self, s: DOMString) -> bool { + let (sender, receiver) = + ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); + let prompt = PromptDefinition::OkCancel(s.to_string(), sender); + let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted); + self.send_to_embedder(msg); + receiver.recv().unwrap() == PromptResult::Primary + } + + // https://html.spec.whatwg.org/multipage/#dom-prompt + fn Prompt(&self, message: DOMString, default: DOMString) -> Option<DOMString> { + let (sender, receiver) = + ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); + let prompt = PromptDefinition::Input(message.to_string(), default.to_string(), sender); + let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted); + self.send_to_embedder(msg); + receiver.recv().unwrap().map(|s| s.into()) + } + + // https://html.spec.whatwg.org/multipage/#dom-window-stop + fn Stop(&self) { + // TODO: Cancel ongoing navigation. + let doc = self.Document(); + doc.abort(); + } + + // https://html.spec.whatwg.org/multipage/#dom-open + fn Open( + &self, + url: USVString, + target: DOMString, + features: DOMString, + ) -> Fallible<Option<DomRoot<WindowProxy>>> { + self.window_proxy().open(url, target, features) + } + + // https://html.spec.whatwg.org/multipage/#dom-opener + fn Opener(&self, cx: JSContext, in_realm_proof: InRealm) -> JSVal { + // Step 1, Let current be this Window object's browsing context. + let current = match self.window_proxy.get() { + Some(proxy) => proxy, + // Step 2, If current is null, then return null. + None => return NullValue(), + }; + // Still step 2, since the window's BC is the associated doc's BC, + // see https://html.spec.whatwg.org/multipage/#window-bc + // and a doc's BC is null if it has been discarded. + // see https://html.spec.whatwg.org/multipage/#concept-document-bc + if current.is_browsing_context_discarded() { + return NullValue(); + } + // Step 3 to 5. + current.opener(*cx, in_realm_proof) + } - let (sender, receiver) = ipc::channel().unwrap(); - let global_scope = self.upcast::<GlobalScope>(); - global_scope - .constellation_chan() - .send(ConstellationMsg::Alert(global_scope.pipeline_id(), s.to_string(), sender)) - .unwrap(); - - let should_display_alert_dialog = receiver.recv().unwrap(); - if should_display_alert_dialog { - display_alert_dialog(&s); + #[allow(unsafe_code)] + // https://html.spec.whatwg.org/multipage/#dom-opener + fn SetOpener(&self, cx: JSContext, value: HandleValue) { + // Step 1. + if value.is_null() { + return self.window_proxy().disown(); + } + // Step 2. + let obj = self.reflector().get_jsobject(); + unsafe { + assert!(JS_DefineProperty( + *cx, + obj, + "opener\0".as_ptr() as *const libc::c_char, + value, + JSPROP_ENUMERATE as u32 + )); } } + // https://html.spec.whatwg.org/multipage/#dom-window-closed + fn Closed(&self) -> bool { + self.window_proxy + .get() + .map(|ref proxy| proxy.is_browsing_context_discarded() || proxy.is_closing()) + .unwrap_or(true) + } + // https://html.spec.whatwg.org/multipage/#dom-window-close fn Close(&self) { - self.main_thread_script_chan() - .send(MainThreadScriptMsg::ExitWindow(self.upcast::<GlobalScope>().pipeline_id())) - .unwrap(); + // Step 1, Let current be this Window object's browsing context. + // Step 2, If current is null or its is closing is true, then return. + let window_proxy = match self.window_proxy.get() { + Some(proxy) => proxy, + None => return, + }; + if window_proxy.is_closing() { + return; + } + // Note: check the length of the "session history", as opposed to the joint session history? + // see https://github.com/whatwg/html/issues/3734 + if let Ok(history_length) = self.History().GetLength() { + let is_auxiliary = window_proxy.is_auxiliary(); + + // https://html.spec.whatwg.org/multipage/#script-closable + let is_script_closable = (self.is_top_level() && history_length == 1) || is_auxiliary; + + // TODO: rest of Step 3: + // Is the incumbent settings object's responsible browsing context familiar with current? + // Is the incumbent settings object's responsible browsing context allowed to navigate current? + if is_script_closable { + // Step 3.1, set current's is closing to true. + window_proxy.close(); + + // Step 3.2, queue a task on the DOM manipulation task source to close current. + let this = Trusted::new(self); + let task = task!(window_close_browsing_context: move || { + let window = this.root(); + let document = window.Document(); + // https://html.spec.whatwg.org/multipage/#closing-browsing-contexts + // Step 1, prompt to unload. + if document.prompt_to_unload(false) { + // Step 2, unload. + document.unload(false); + // Step 3, remove from the user interface + let _ = window.send_to_embedder(EmbedderMsg::CloseBrowser); + // Step 4, discard browsing context. + // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded + // which calls into https://html.spec.whatwg.org/multipage/#discard-a-document. + window.discard_browsing_context(); + + let _ = window.send_to_constellation(ScriptMsg::DiscardTopLevelBrowsingContext); + } + }); + self.task_manager() + .dom_manipulation_task_source() + .queue(task, &self.upcast::<GlobalScope>()) + .expect("Queuing window_close_browsing_context task to work"); + } + } } // https://html.spec.whatwg.org/multipage/#dom-document-2 - fn Document(&self) -> Root<Document> { - self.document.get().expect("Document accessed before initialization.") + fn Document(&self) -> DomRoot<Document> { + self.document + .get() + .expect("Document accessed before initialization.") } // https://html.spec.whatwg.org/multipage/#dom-history - fn History(&self) -> Root<History> { + fn History(&self) -> DomRoot<History> { self.history.or_init(|| History::new(self)) } + // https://html.spec.whatwg.org/multipage/#dom-window-customelements + fn CustomElements(&self) -> DomRoot<CustomElementRegistry> { + self.custom_element_registry + .or_init(|| CustomElementRegistry::new(self)) + } + // https://html.spec.whatwg.org/multipage/#dom-location - fn Location(&self) -> Root<Location> { - self.Document().GetLocation().unwrap() + fn Location(&self) -> DomRoot<Location> { + self.location.or_init(|| Location::new(self)) } // https://html.spec.whatwg.org/multipage/#dom-sessionstorage - fn SessionStorage(&self) -> Root<Storage> { - self.session_storage.or_init(|| Storage::new(self, StorageType::Session)) + fn SessionStorage(&self) -> DomRoot<Storage> { + self.session_storage + .or_init(|| Storage::new(self, StorageType::Session)) } // https://html.spec.whatwg.org/multipage/#dom-localstorage - fn LocalStorage(&self) -> Root<Storage> { - self.local_storage.or_init(|| Storage::new(self, StorageType::Local)) + fn LocalStorage(&self) -> DomRoot<Storage> { + self.local_storage + .or_init(|| Storage::new(self, StorageType::Local)) } // https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#dfn-GlobalCrypto - fn Crypto(&self) -> Root<Crypto> { + fn Crypto(&self) -> DomRoot<Crypto> { self.upcast::<GlobalScope>().crypto() } // https://html.spec.whatwg.org/multipage/#dom-frameelement - fn GetFrameElement(&self) -> Option<Root<Element>> { + fn GetFrameElement(&self) -> Option<DomRoot<Element>> { // Steps 1-3. - let context = match self.browsing_context.get() { - None => return None, - Some(context) => context, - }; + let window_proxy = self.window_proxy.get()?; + // Step 4-5. - let container = match context.frame_element() { - None => return None, - Some(container) => container, - }; + let container = window_proxy.frame_element()?; + // Step 6. let container_doc = document_from_node(container); - let current_doc = GlobalScope::current().as_window().Document(); - if !current_doc.origin().same_origin_domain(container_doc.origin()) { + let current_doc = GlobalScope::current() + .expect("No current global object") + .as_window() + .Document(); + if !current_doc + .origin() + .same_origin_domain(container_doc.origin()) + { return None; } // Step 7. - Some(Root::from_ref(container)) + Some(DomRoot::from_ref(container)) } // https://html.spec.whatwg.org/multipage/#dom-navigator - fn Navigator(&self) -> Root<Navigator> { + fn Navigator(&self) -> DomRoot<Navigator> { self.navigator.or_init(|| Navigator::new(self)) } - #[allow(unsafe_code)] - // https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout - unsafe fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, - args: Vec<HandleValue>) -> i32 { - self.upcast::<GlobalScope>().set_timeout_or_interval( - TimerCallback::FunctionTimerCallback(callback), - args, - timeout, - IsInterval::NonInterval) - } - - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout - unsafe fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, - timeout: i32, args: Vec<HandleValue>) -> i32 { + fn SetTimeout( + &self, + _cx: JSContext, + callback: StringOrFunction, + timeout: i32, + args: Vec<HandleValue>, + ) -> i32 { + let callback = match callback { + StringOrFunction::String(i) => TimerCallback::StringTimerCallback(i), + StringOrFunction::Function(i) => TimerCallback::FunctionTimerCallback(i), + }; self.upcast::<GlobalScope>().set_timeout_or_interval( - TimerCallback::StringTimerCallback(callback), + callback, args, timeout, - IsInterval::NonInterval) + IsInterval::NonInterval, + ) } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout fn ClearTimeout(&self, handle: i32) { - self.upcast::<GlobalScope>().clear_timeout_or_interval(handle); - } - - #[allow(unsafe_code)] - // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval - unsafe fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, - timeout: i32, args: Vec<HandleValue>) -> i32 { - self.upcast::<GlobalScope>().set_timeout_or_interval( - TimerCallback::FunctionTimerCallback(callback), - args, - timeout, - IsInterval::Interval) + self.upcast::<GlobalScope>() + .clear_timeout_or_interval(handle); } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval - unsafe fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, - timeout: i32, args: Vec<HandleValue>) -> i32 { + fn SetInterval( + &self, + _cx: JSContext, + callback: StringOrFunction, + timeout: i32, + args: Vec<HandleValue>, + ) -> i32 { + let callback = match callback { + StringOrFunction::String(i) => TimerCallback::StringTimerCallback(i), + StringOrFunction::Function(i) => TimerCallback::FunctionTimerCallback(i), + }; self.upcast::<GlobalScope>().set_timeout_or_interval( - TimerCallback::StringTimerCallback(callback), + callback, args, timeout, - IsInterval::Interval) + IsInterval::Interval, + ) } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval @@ -623,60 +905,74 @@ impl WindowMethods for Window { self.ClearTimeout(handle); } + // https://html.spec.whatwg.org/multipage/#dom-queuemicrotask + fn QueueMicrotask(&self, callback: Rc<VoidFunction>) { + self.upcast::<GlobalScope>() + .queue_function_as_microtask(callback); + } + + // https://html.spec.whatwg.org/multipage/#dom-createimagebitmap + fn CreateImageBitmap( + &self, + image: ImageBitmapSource, + options: &ImageBitmapOptions, + ) -> Rc<Promise> { + let p = self + .upcast::<GlobalScope>() + .create_image_bitmap(image, options); + p + } + // https://html.spec.whatwg.org/multipage/#dom-window - fn Window(&self) -> Root<BrowsingContext> { - self.browsing_context() + fn Window(&self) -> DomRoot<WindowProxy> { + self.window_proxy() } // https://html.spec.whatwg.org/multipage/#dom-self - fn Self_(&self) -> Root<BrowsingContext> { - self.browsing_context() + fn Self_(&self) -> DomRoot<WindowProxy> { + self.window_proxy() } // https://html.spec.whatwg.org/multipage/#dom-frames - fn Frames(&self) -> Root<BrowsingContext> { + fn Frames(&self) -> DomRoot<WindowProxy> { println!("frames!"); - self.browsing_context() + self.window_proxy() + } + + // https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts + fn Length(&self) -> u32 { + let doc = self.Document(); + doc.iter_iframes().count() as u32 } // https://html.spec.whatwg.org/multipage/#dom-parent - fn GetParent(&self) -> Option<Root<BrowsingContext>> { + fn GetParent(&self) -> Option<DomRoot<WindowProxy>> { // Steps 1-3. - let context = match self.maybe_browsing_context() { - Some(context) => context, - None => return None, - }; - if context.is_discarded() { - return None; - } + let window_proxy = self.undiscarded_window_proxy()?; + // Step 4. - if let Some(parent) = context.parent() { - return Some(Root::from_ref(parent)); + if let Some(parent) = window_proxy.parent() { + return Some(DomRoot::from_ref(parent)); } // Step 5. - Some(context) + Some(window_proxy) } // https://html.spec.whatwg.org/multipage/#dom-top - fn GetTop(&self) -> Option<Root<BrowsingContext>> { + fn GetTop(&self) -> Option<DomRoot<WindowProxy>> { // Steps 1-3. - let context = match self.maybe_browsing_context() { - Some(context) => context, - None => return None, - }; - if context.is_discarded() { - return None; - } + let window_proxy = self.undiscarded_window_proxy()?; + // Steps 4-5. - Some(Root::from_ref(context.top())) + Some(DomRoot::from_ref(window_proxy.top())) } // https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/ // NavigationTiming/Overview.html#sec-window.performance-attribute - fn Performance(&self) -> Root<Performance> { + fn Performance(&self) -> DomRoot<Performance> { self.performance.or_init(|| { - Performance::new(self, self.navigation_start, - self.navigation_start_precise) + let global_scope = self.upcast::<GlobalScope>(); + Performance::new(global_scope, self.navigation_start_precise.get()) }) } @@ -687,7 +983,7 @@ impl WindowMethods for Window { window_event_handlers!(); // https://developer.mozilla.org/en-US/docs/Web/API/Window/screen - fn Screen(&self) -> Root<Screen> { + fn Screen(&self) -> DomRoot<Screen> { self.screen.or_init(|| Screen::new(self)) } @@ -701,46 +997,63 @@ impl WindowMethods for Window { base64_atob(atob) } - /// https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe + /// <https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe> fn RequestAnimationFrame(&self, callback: Rc<FrameRequestCallback>) -> u32 { self.Document() .request_animation_frame(AnimationFrameCallback::FrameRequestCallback { callback }) } - /// https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe + /// <https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe> fn CancelAnimationFrame(&self, ident: u32) { let doc = self.Document(); doc.cancel_animation_frame(ident); } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-window-postmessage - unsafe fn PostMessage(&self, - cx: *mut JSContext, - message: HandleValue, - origin: DOMString) - -> ErrorResult { - // Step 3-5. - let origin = match &origin[..] { - "*" => None, - "/" => { - // TODO(#12715): Should be the origin of the incumbent settings - // object, not self's. - Some(self.Document().origin().immutable().clone()) - }, - url => match ServoUrl::parse(&url) { - Ok(url) => Some(url.origin().clone()), - Err(_) => return Err(Error::Syntax), - } - }; - - // Step 1-2, 6-8. - // TODO(#12717): Should implement the `transfer` argument. - let data = try!(StructuredCloneData::write(cx, message)); - - // Step 9. - self.post_message(origin, data); - Ok(()) + fn PostMessage( + &self, + cx: JSContext, + message: HandleValue, + target_origin: USVString, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + let incumbent = GlobalScope::incumbent().expect("no incumbent global?"); + let source = incumbent.as_window(); + let source_origin = source.Document().origin().immutable().clone(); + + self.post_message_impl(&target_origin, source_origin, source, cx, message, transfer) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + fn PostMessage_( + &self, + cx: JSContext, + message: HandleValue, + options: RootedTraceableBox<WindowPostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .parent + .transfer + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let transfer = CustomAutoRooterGuard::new(*cx, &mut rooted); + + let incumbent = GlobalScope::incumbent().expect("no incumbent global?"); + let source = incumbent.as_window(); + + let source_origin = source.Document().origin().immutable().clone(); + + self.post_message_impl( + &options.targetOrigin, + source_origin, + source, + cx, + message, + transfer, + ) } // https://html.spec.whatwg.org/multipage/#dom-window-captureevents @@ -761,7 +1074,7 @@ impl WindowMethods for Window { #[allow(unsafe_code)] fn Gc(&self) { unsafe { - JS_GC(JS_GetRuntime(self.get_cx())); + JS_GC(*self.get_cx(), GCReason::API); } } @@ -771,8 +1084,22 @@ impl WindowMethods for Window { } #[allow(unsafe_code)] - unsafe fn WebdriverCallback(&self, cx: *mut JSContext, val: HandleValue) { - let rv = jsval_to_webdriver(cx, val); + fn Js_backtrace(&self) { + unsafe { + capture_stack!(in(*self.get_cx()) let stack); + let js_stack = stack.and_then(|s| s.as_string(None, StackFormat::SpiderMonkey)); + let rust_stack = Backtrace::new(); + println!( + "Current JS stack:\n{}\nCurrent Rust stack:\n{:?}", + js_stack.unwrap_or(String::new()), + rust_stack + ); + } + } + + #[allow(unsafe_code)] + fn WebdriverCallback(&self, cx: JSContext, val: HandleValue) { + let rv = unsafe { jsval_to_webdriver(*cx, &self.globalscope, val) }; let opt_chan = self.webdriver_script_chan.borrow_mut().take(); if let Some(chan) = opt_chan { chan.send(rv).unwrap(); @@ -787,39 +1114,54 @@ impl WindowMethods for Window { } // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle - fn GetComputedStyle(&self, - element: &Element, - pseudo: Option<DOMString>) -> Root<CSSStyleDeclaration> { + fn GetComputedStyle( + &self, + element: &Element, + pseudo: Option<DOMString>, + ) -> DomRoot<CSSStyleDeclaration> { // Steps 1-4. - let pseudo = match pseudo.map(|mut s| { s.make_ascii_lowercase(); s }) { - Some(ref pseudo) if pseudo == ":before" || pseudo == "::before" => - Some(PseudoElement::Before), - Some(ref pseudo) if pseudo == ":after" || pseudo == "::after" => - Some(PseudoElement::After), - _ => None + let pseudo = match pseudo.map(|mut s| { + s.make_ascii_lowercase(); + s + }) { + Some(ref pseudo) if pseudo == ":before" || pseudo == "::before" => { + Some(PseudoElement::Before) + }, + Some(ref pseudo) if pseudo == ":after" || pseudo == "::after" => { + Some(PseudoElement::After) + }, + _ => None, }; // Step 5. - CSSStyleDeclaration::new(self, - CSSStyleOwner::Element(JS::from_ref(element)), - pseudo, - CSSModificationAccess::Readonly) + CSSStyleDeclaration::new( + self, + CSSStyleOwner::Element(Dom::from_ref(element)), + pseudo, + CSSModificationAccess::Readonly, + ) } // https://drafts.csswg.org/cssom-view/#dom-window-innerheight //TODO Include Scrollbar fn InnerHeight(&self) -> i32 { - self.window_size.get() - .and_then(|e| e.initial_viewport.height.to_i32()) - .unwrap_or(0) + self.window_size + .get() + .initial_viewport + .height + .to_i32() + .unwrap_or(0) } // https://drafts.csswg.org/cssom-view/#dom-window-innerwidth //TODO Include Scrollbar fn InnerWidth(&self) -> i32 { - self.window_size.get() - .and_then(|e| e.initial_viewport.width.to_i32()) - .unwrap_or(0) + self.window_size + .get() + .initial_viewport + .width + .to_i32() + .unwrap_or(0) } // https://drafts.csswg.org/cssom-view/#dom-window-scrollx @@ -848,7 +1190,6 @@ impl WindowMethods for Window { let left = options.left.unwrap_or(0.0f64); let top = options.top.unwrap_or(0.0f64); self.scroll(left, top, options.parent.behavior); - } // https://drafts.csswg.org/cssom-view/#dom-window-scroll @@ -867,7 +1208,7 @@ impl WindowMethods for Window { } // https://drafts.csswg.org/cssom-view/#dom-window-scrollby - fn ScrollBy(&self, options: &ScrollToOptions) { + fn ScrollBy(&self, options: &ScrollToOptions) { // Step 1 let x = options.left.unwrap_or(0.0f64); let y = options.top.unwrap_or(0.0f64); @@ -876,43 +1217,43 @@ impl WindowMethods for Window { } // https://drafts.csswg.org/cssom-view/#dom-window-scrollby - fn ScrollBy_(&self, x: f64, y: f64) { + fn ScrollBy_(&self, x: f64, y: f64) { // Step 3 let left = x + self.ScrollX() as f64; // Step 4 - let top = y + self.ScrollY() as f64; + let top = y + self.ScrollY() as f64; // Step 5 self.scroll(left, top, ScrollBehavior::Auto); } // https://drafts.csswg.org/cssom-view/#dom-window-resizeto - fn ResizeTo(&self, x: i32, y: i32) { + fn ResizeTo(&self, width: i32, height: i32) { // Step 1 //TODO determine if this operation is allowed - let size = Size2D::new(x.to_u32().unwrap_or(1), y.to_u32().unwrap_or(1)); - self.upcast::<GlobalScope>() - .constellation_chan() - .send(ConstellationMsg::ResizeTo(size)) - .unwrap() + let dpr = self.device_pixel_ratio(); + let size = Size2D::new(width, height).to_f32() * dpr; + self.send_to_embedder(EmbedderMsg::ResizeTo(size.to_i32())); } // https://drafts.csswg.org/cssom-view/#dom-window-resizeby fn ResizeBy(&self, x: i32, y: i32) { let (size, _) = self.client_window(); // Step 1 - self.ResizeTo(x + size.width.to_i32().unwrap_or(1), y + size.height.to_i32().unwrap_or(1)) + self.ResizeTo( + x + size.width.to_i32().unwrap_or(1), + y + size.height.to_i32().unwrap_or(1), + ) } // https://drafts.csswg.org/cssom-view/#dom-window-moveto fn MoveTo(&self, x: i32, y: i32) { // Step 1 //TODO determine if this operation is allowed - let point = Point2D::new(x, y); - self.upcast::<GlobalScope>() - .constellation_chan() - .send(ConstellationMsg::MoveTo(point)) - .unwrap() + let dpr = self.device_pixel_ratio(); + let point = Point2D::new(x, y).to_f32() * dpr; + let msg = EmbedderMsg::MoveTo(point.to_i32()); + self.send_to_embedder(msg); } // https://drafts.csswg.org/cssom-view/#dom-window-moveby @@ -948,8 +1289,7 @@ impl WindowMethods for Window { // https://drafts.csswg.org/cssom-view/#dom-window-devicepixelratio fn DevicePixelRatio(&self) -> Finite<f64> { - let dpr = self.window_size.get().map_or(1.0f32, |data| data.device_pixel_ratio.get()); - Finite::wrap(dpr as f64) + Finite::wrap(self.device_pixel_ratio().get() as f64) } // https://html.spec.whatwg.org/multipage/#dom-window-status @@ -962,62 +1302,181 @@ impl WindowMethods for Window { *self.status.borrow_mut() = status } - // check-tidy: no specs after this line - fn OpenURLInDefaultBrowser(&self, href: DOMString) -> ErrorResult { - let url = try!(ServoUrl::parse(&href).map_err(|e| { - Error::Type(format!("Couldn't parse URL: {}", e)) - })); - match open::that(url.as_str()) { - Ok(_) => Ok(()), - Err(e) => Err(Error::Type(format!("Couldn't open URL: {}", e))), - } - } - // https://drafts.csswg.org/cssom-view/#dom-window-matchmedia - fn MatchMedia(&self, query: DOMString) -> Root<MediaQueryList> { - let mut parser = Parser::new(&query); + fn MatchMedia(&self, query: DOMString) -> DomRoot<MediaQueryList> { + let mut input = ParserInput::new(&query); + let mut parser = Parser::new(&mut input); let url = self.get_url(); - let context = CssParserContext::new_for_cssom(&url, self.css_error_reporter(), Some(CssRuleType::Media), - LengthParsingMode::Default); - let media_query_list = media_queries::parse_media_query_list(&context, &mut parser); + let quirks_mode = self.Document().quirks_mode(); + let context = CssParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Media), + ParsingMode::DEFAULT, + quirks_mode, + self.css_error_reporter(), + None, + ); + let media_query_list = media_queries::MediaList::parse(&context, &mut parser); let document = self.Document(); let mql = MediaQueryList::new(&document, media_query_list); - self.media_query_lists.push(&*mql); + self.media_query_lists.track(&*mql); mql } - #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#fetch-method - fn Fetch(&self, input: RequestOrUSVString, init: RootedTraceableBox<RequestInit>) -> Rc<Promise> { - fetch::Fetch(&self.upcast(), input, init) + fn Fetch( + &self, + input: RequestOrUSVString, + init: RootedTraceableBox<RequestInit>, + comp: InRealm, + ) -> Rc<Promise> { + fetch::Fetch(&self.upcast(), input, init, comp) } - fn TestRunner(&self) -> Root<TestRunner> { + fn TestRunner(&self) -> DomRoot<TestRunner> { self.test_runner.or_init(|| TestRunner::new(self.upcast())) } + + fn RunningAnimationCount(&self) -> u32 { + self.document + .get() + .map_or(0, |d| d.animations().running_animation_count() as u32) + } + + // https://html.spec.whatwg.org/multipage/#dom-name + fn SetName(&self, name: DOMString) { + if let Some(proxy) = self.undiscarded_window_proxy() { + proxy.set_name(name); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-name + fn Name(&self) -> DOMString { + match self.undiscarded_window_proxy() { + Some(proxy) => proxy.get_name(), + None => "".into(), + } + } + + // https://html.spec.whatwg.org/multipage/#dom-origin + fn Origin(&self) -> USVString { + USVString(self.origin().immutable().ascii_serialization()) + } + + // https://w3c.github.io/selection-api/#dom-window-getselection + fn GetSelection(&self) -> Option<DomRoot<Selection>> { + self.document.get().and_then(|d| d.GetSelection()) + } + + // https://dom.spec.whatwg.org/#dom-window-event + #[allow(unsafe_code)] + fn Event(&self, cx: JSContext) -> JSVal { + rooted!(in(*cx) let mut rval = UndefinedValue()); + if let Some(ref event) = *self.current_event.borrow() { + unsafe { + event + .reflector() + .get_jsobject() + .to_jsval(*cx, rval.handle_mut()); + } + } + rval.get() + } + + fn IsSecureContext(&self) -> bool { + self.upcast::<GlobalScope>().is_secure_context() + } } impl Window { - pub fn get_runnable_wrapper(&self) -> RunnableWrapper { - RunnableWrapper { - cancelled: Some(self.ignore_further_async_events.borrow().clone()), - } + pub(crate) fn set_current_event(&self, event: Option<&Event>) -> Option<DomRoot<Event>> { + let current = self + .current_event + .borrow() + .as_ref() + .map(|e| DomRoot::from_ref(&**e)); + *self.current_event.borrow_mut() = event.map(|e| Dom::from_ref(e)); + current + } + + /// https://html.spec.whatwg.org/multipage/#window-post-message-steps + fn post_message_impl( + &self, + target_origin: &USVString, + source_origin: ImmutableOrigin, + source: &Window, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + // Step 1-2, 6-8. + let data = structuredclone::write(cx, message, Some(transfer))?; + + // Step 3-5. + let target_origin = match target_origin.0[..].as_ref() { + "*" => None, + "/" => Some(source_origin.clone()), + url => match ServoUrl::parse(&url) { + Ok(url) => Some(url.origin().clone()), + Err(_) => return Err(Error::Syntax), + }, + }; + + // Step 9. + self.post_message(target_origin, source_origin, &*source.window_proxy(), data); + Ok(()) + } + + // https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet + pub fn paint_worklet(&self) -> DomRoot<Worklet> { + self.paint_worklet.or_init(|| self.new_paint_worklet()) + } + + pub fn get_navigation_start(&self) -> u64 { + self.navigation_start_precise.get() + } + + pub fn has_document(&self) -> bool { + self.document.get().is_some() } /// Cancels all the tasks associated with that window. /// - /// This sets the current `ignore_further_async_events` sentinel value to + /// This sets the current `task_manager.task_cancellers` sentinel value to /// `true` and replaces it with a brand new one for future tasks. pub fn cancel_all_tasks(&self) { - let cancelled = mem::replace(&mut *self.ignore_further_async_events.borrow_mut(), Default::default()); - cancelled.store(true, Ordering::Relaxed); + let mut ignore_flags = self.task_manager.task_cancellers.borrow_mut(); + for task_source_name in TaskSourceName::all() { + let flag = ignore_flags + .entry(task_source_name) + .or_insert(Default::default()); + let cancelled = mem::replace(&mut *flag, Default::default()); + cancelled.store(true, Ordering::SeqCst); + } + } + + /// Cancels all the tasks from a given task source. + /// This sets the current sentinel value to + /// `true` and replaces it with a brand new one for future tasks. + pub fn cancel_all_tasks_from_source(&self, task_source_name: TaskSourceName) { + let mut ignore_flags = self.task_manager.task_cancellers.borrow_mut(); + let flag = ignore_flags + .entry(task_source_name) + .or_insert(Default::default()); + let cancelled = mem::replace(&mut *flag, Default::default()); + cancelled.store(true, Ordering::SeqCst); } pub fn clear_js_runtime(&self) { - // We tear down the active document, which causes all the attached - // nodes to dispose of their layout data. This messages the layout - // thread, informing it that it can safely free the memory. - self.Document().upcast::<Node>().teardown(); + self.upcast::<GlobalScope>() + .remove_web_messaging_and_dedicated_workers_infra(); + + // Clean up any active promises + // https://github.com/servo/servo/issues/15318 + if let Some(custom_elements) = self.custom_element_registry.get() { + custom_elements.teardown(); + } // The above code may not catch all DOM objects (e.g. DOM // objects removed from the tree that haven't been collected @@ -1035,20 +1494,31 @@ impl Window { self.current_state.set(WindowState::Zombie); *self.js_runtime.borrow_mut() = None; - self.browsing_context.set(None); - self.ignore_further_async_events.borrow().store(true, Ordering::SeqCst); + + // If this is the currently active pipeline, + // nullify the window_proxy. + if let Some(proxy) = self.window_proxy.get() { + let pipeline_id = self.upcast::<GlobalScope>().pipeline_id(); + if let Some(currently_active) = proxy.currently_active() { + if currently_active == pipeline_id { + self.window_proxy.set(None); + } + } + } + + if let Some(performance) = self.performance.get() { + performance.clear_and_disable_performance_entry_buffer(); + } + self.ignore_all_tasks(); } - /// https://drafts.csswg.org/cssom-view/#dom-window-scroll + /// <https://drafts.csswg.org/cssom-view/#dom-window-scroll> pub fn scroll(&self, x_: f64, y_: f64, behavior: ScrollBehavior) { // Step 3 let xfinite = if x_.is_finite() { x_ } else { 0.0f64 }; let yfinite = if y_.is_finite() { y_ } else { 0.0f64 }; - // Step 4 - if self.window_size.get().is_none() { - return; - } + // TODO Step 4 - determine if a window has a viewport // Step 5 //TODO remove scrollbar width @@ -1065,12 +1535,12 @@ impl Window { let content_size = e.upcast::<Node>().bounding_content_box_or_zero(); let content_height = content_size.size.height.to_f64_px(); let content_width = content_size.size.width.to_f64_px(); - (xfinite.max(0.0f64).min(content_width - width), - yfinite.max(0.0f64).min(content_height - height)) + ( + xfinite.min(content_width - width).max(0.0f64), + yfinite.min(content_height - height).max(0.0f64), + ) }, - None => { - (xfinite.max(0.0f64), yfinite.max(0.0f64)) - } + None => (xfinite.max(0.0f64), yfinite.max(0.0f64)), }; // Step 10 @@ -1083,39 +1553,39 @@ impl Window { //let document = self.Document(); // Step 12 let global_scope = self.upcast::<GlobalScope>(); - self.perform_a_scroll(x.to_f32().unwrap_or(0.0f32), - y.to_f32().unwrap_or(0.0f32), - global_scope.pipeline_id().root_scroll_node(), - behavior, - None); - } - - /// https://drafts.csswg.org/cssom-view/#perform-a-scroll - pub fn perform_a_scroll(&self, - x: f32, - y: f32, - scroll_root_id: ClipId, - behavior: ScrollBehavior, - element: Option<&Element>) { - //TODO Step 1 - let point = Point2D::new(x, y); - let smooth = match behavior { - ScrollBehavior::Auto => { - element.map_or(false, |_element| { - // TODO check computed scroll-behaviour CSS property - true - }) - } - ScrollBehavior::Instant => false, - ScrollBehavior::Smooth => true - }; - - // TODO (farodin91): Raise an event to stop the current_viewport + let x = x.to_f32().unwrap_or(0.0f32); + let y = y.to_f32().unwrap_or(0.0f32); self.update_viewport_for_scroll(x, y); - - let global_scope = self.upcast::<GlobalScope>(); - let message = ConstellationMsg::ScrollFragmentPoint(scroll_root_id, point, smooth); - global_scope.constellation_chan().send(message).unwrap(); + self.perform_a_scroll( + x, + y, + global_scope.pipeline_id().root_scroll_id(), + behavior, + None, + ); + } + + /// <https://drafts.csswg.org/cssom-view/#perform-a-scroll> + pub fn perform_a_scroll( + &self, + x: f32, + y: f32, + scroll_id: ExternalScrollId, + _behavior: ScrollBehavior, + _element: Option<&Element>, + ) { + // TODO Step 1 + // TODO(mrobinson, #18709): Add smooth scrolling support to WebRender so that we can + // properly process ScrollBehavior here. + match self.layout_chan() { + Some(chan) => chan + .send(Msg::UpdateScrollStateFromScript(ScrollState { + scroll_id, + scroll_offset: Vector2D::new(-x, -y), + })) + .unwrap(), + None => warn!("Layout channel unavailable"), + } } pub fn update_viewport_for_scroll(&self, x: f32, y: f32) { @@ -1124,19 +1594,31 @@ impl Window { self.current_viewport.set(new_viewport) } - pub fn client_window(&self) -> (Size2D<u32>, Point2D<i32>) { - let (send, recv) = ipc::channel::<(Size2D<u32>, Point2D<i32>)>().unwrap(); - self.upcast::<GlobalScope>() - .constellation_chan() - .send(ConstellationMsg::GetClientWindow(send)) - .unwrap(); - recv.recv().unwrap_or((Size2D::zero(), Point2D::zero())) + pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> { + self.window_size.get().device_pixel_ratio } - /// Advances the layout animation clock by `delta` milliseconds, and then - /// forces a reflow if `tick` is true. - pub fn advance_animation_clock(&self, delta: i32, tick: bool) { - self.layout_chan.send(Msg::AdvanceClockMs(delta, tick)).unwrap(); + fn client_window(&self) -> (Size2D<u32, CSSPixel>, Point2D<i32, CSSPixel>) { + let timer_profile_chan = self.global().time_profiler_chan().clone(); + let (send, recv) = + ProfiledIpc::channel::<(DeviceIntSize, DeviceIntPoint)>(timer_profile_chan).unwrap(); + self.send_to_constellation(ScriptMsg::GetClientWindow(send)); + let (size, point) = recv.recv().unwrap_or((Size2D::zero(), Point2D::zero())); + let dpr = self.device_pixel_ratio(); + ( + (size.to_f32() / dpr).to_u32(), + (point.to_f32() / dpr).to_i32(), + ) + } + + /// Prepares to tick animations and then does a reflow which also advances the + /// layout animation clock. + #[allow(unsafe_code)] + pub fn advance_animation_clock(&self, delta_ms: i32) { + let pipeline_id = self.upcast::<GlobalScope>().pipeline_id(); + self.Document() + .advance_animation_timeline_for_testing(delta_ms as f64 / 1000.); + ScriptThread::handle_tick_all_animations_for_testing(pipeline_id); } /// Reflows the page unconditionally if possible and not suppressed. This @@ -1149,32 +1631,45 @@ impl Window { /// off-main-thread layout. /// /// Returns true if layout actually happened, false otherwise. - pub fn force_reflow(&self, - goal: ReflowGoal, - query_type: ReflowQueryType, - reason: ReflowReason) -> bool { + #[allow(unsafe_code)] + pub fn force_reflow( + &self, + reflow_goal: ReflowGoal, + reason: ReflowReason, + condition: Option<ReflowTriggerCondition>, + ) -> bool { + self.Document().ensure_safe_to_run_script_or_layout(); // Check if we need to unsuppress reflow. Note that this needs to be // *before* any early bailouts, or reflow might never be unsuppresed! match reason { - ReflowReason::FirstLoad | - ReflowReason::RefreshTick => self.suppress_reflow.set(false), + ReflowReason::FirstLoad | ReflowReason::RefreshTick => self.suppress_reflow.set(false), _ => (), } - // If there is no window size, we have nothing to do. - let window_size = match self.window_size.get() { - Some(window_size) => window_size, - None => return false, - }; - - let for_display = query_type == ReflowQueryType::NoQuery; + let for_display = reflow_goal == ReflowGoal::Full; + let pipeline_id = self.upcast::<GlobalScope>().pipeline_id(); if for_display && self.suppress_reflow.get() { - debug!("Suppressing reflow pipeline {} for goal {:?} reason {:?} before FirstLoad or RefreshTick", - self.upcast::<GlobalScope>().pipeline_id(), goal, reason); + debug!( + "Suppressing reflow pipeline {} for reason {:?} before FirstLoad or RefreshTick", + pipeline_id, reason + ); return false; } - debug!("script: performing reflow for goal {:?} reason {:?}", goal, reason); + if condition != Some(ReflowTriggerCondition::PaintPostponed) { + debug!( + "Invalidating layout cache due to reflow condition {:?}", + condition + ); + // Invalidate any existing cached layout values. + self.layout_marker.borrow().set(false); + // Create a new layout caching token. + *self.layout_marker.borrow_mut() = Rc::new(Cell::new(true)); + } else { + debug!("Not invalidating cached layout values for paint-only reflow."); + } + + debug!("script: performing reflow for reason {:?}", reason); let marker = if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) { Some(TimelineMarker::start("Reflow".to_owned())) @@ -1183,51 +1678,76 @@ impl Window { }; // Layout will let us know when it's done. - let (join_chan, join_port) = channel(); + let (join_chan, join_port) = unbounded(); // On debug mode, print the reflow event information. - if opts::get().relayout_event { - debug_reflow_events(self.upcast::<GlobalScope>().pipeline_id(), &goal, &query_type, &reason); + if self.relayout_event { + debug_reflow_events(pipeline_id, &reflow_goal, &reason); } let document = self.Document(); - let stylesheets_changed = document.get_and_reset_stylesheets_changed_since_reflow(); + + let stylesheets_changed = document.flush_stylesheets_for_reflow(); + + // If this reflow is for display, ensure webgl canvases are composited with + // up-to-date contents. + if for_display { + document.flush_dirty_webgpu_canvases(); + document.flush_dirty_webgl_canvases(); + } + + let pending_restyles = document.drain_pending_restyles(); + + let dirty_root = document + .take_dirty_root() + .filter(|_| !stylesheets_changed) + .or_else(|| document.GetDocumentElement()) + .map(|root| root.upcast::<Node>().to_trusted_node_address()); // Send new document and relevant styles to layout. + let needs_display = reflow_goal.needs_display(); let reflow = ScriptReflow { reflow_info: Reflow { - goal: goal, page_clip_rect: self.page_clip_rect.get(), }, - document: self.Document().upcast::<Node>().to_trusted_node_address(), - document_stylesheets: document.stylesheets(), - stylesheets_changed: stylesheets_changed, - window_size: window_size, + document: document.upcast::<Node>().to_trusted_node_address(), + dirty_root, + stylesheets_changed, + window_size: self.window_size.get(), + origin: self.origin().immutable().clone(), + reflow_goal, script_join_chan: join_chan, - query_type: query_type, - dom_count: self.Document().dom_count(), + dom_count: document.dom_count(), + pending_restyles, + animation_timeline_value: document.current_animation_timeline_value(), + animations: document.animations().sets.clone(), }; - self.layout_chan.send(Msg::Reflow(reflow)).unwrap(); + match self.layout_chan() { + Some(layout_chan) => layout_chan + .send(Msg::Reflow(reflow)) + .expect("Layout thread disconnected"), + None => return false, + }; debug!("script: layout forked"); - match join_port.try_recv() { - Err(Empty) => { - info!("script: waiting on layout"); - join_port.recv().unwrap(); - } - Ok(_) => {} - Err(Disconnected) => { + let complete = match join_port.try_recv() { + Err(TryRecvError::Empty) => { + debug!("script: waiting on layout"); + join_port.recv().unwrap() + }, + Ok(reflow_complete) => reflow_complete, + Err(TryRecvError::Disconnected) => { panic!("Layout thread failed while script was waiting for a result."); - } - } + }, + }; debug!("script: layout joined"); // Pending reflows require display, so only reset the pending reflow count if this reflow // was to be displayed. - if goal == ReflowGoal::ForDisplay { + if needs_display { self.pending_reflow_count.set(0); } @@ -1235,12 +1755,9 @@ impl Window { self.emit_timeline_marker(marker.end()); } - let pending_images = self.layout_rpc.pending_images(); - for image in pending_images { + for image in complete.pending_images { let id = image.id; - let js_runtime = self.js_runtime.borrow(); - let js_runtime = js_runtime.as_ref().unwrap(); - let node = from_untrusted_node_address(js_runtime.rt(), image.node); + let node = unsafe { from_untrusted_node_address(image.node) }; if let PendingImageState::Unrequested(ref url) = image.state { fetch_image_for_layout(url.clone(), &*node, id, self.image_cache.clone()); @@ -1248,18 +1765,28 @@ impl Window { let mut images = self.pending_layout_images.borrow_mut(); let nodes = images.entry(id).or_insert(vec![]); - if nodes.iter().find(|n| &***n as *const _ == &*node as *const _).is_none() { - let (responder, responder_listener) = ipc::channel().unwrap(); - let pipeline = self.upcast::<GlobalScope>().pipeline_id(); + if nodes + .iter() + .find(|n| &***n as *const _ == &*node as *const _) + .is_none() + { + let (responder, responder_listener) = + ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); let image_cache_chan = self.image_cache_chan.clone(); - ROUTER.add_route(responder_listener.to_opaque(), box move |message| { - let _ = image_cache_chan.send((pipeline, message.to().unwrap())); - }); - self.image_cache.add_listener(id, ImageResponder::new(responder, id)); - nodes.push(JS::from_ref(&*node)); + ROUTER.add_route( + responder_listener.to_opaque(), + Box::new(move |message| { + let _ = image_cache_chan.send((pipeline_id, message.to().unwrap())); + }), + ); + self.image_cache + .add_listener(id, ImageResponder::new(responder, id)); + nodes.push(Dom::from_ref(&*node)); } } + document.update_animations_post_reflow(); + true } @@ -1276,25 +1803,33 @@ impl Window { /// that layout might hold if the first layout hasn't happened yet (which /// may happen in the only case a query reflow may bail out, that is, if the /// viewport size is not present). See #11223 for an example of that. - pub fn reflow(&self, - goal: ReflowGoal, - query_type: ReflowQueryType, - reason: ReflowReason) -> bool { - let for_display = query_type == ReflowQueryType::NoQuery; + pub fn reflow(&self, reflow_goal: ReflowGoal, reason: ReflowReason) -> bool { + self.Document().ensure_safe_to_run_script_or_layout(); + let for_display = reflow_goal == ReflowGoal::Full; let mut issued_reflow = false; - if !for_display || self.Document().needs_reflow() { - issued_reflow = self.force_reflow(goal, query_type, reason); + let condition = self.Document().needs_reflow(); + if !for_display || condition.is_some() { + issued_reflow = self.force_reflow(reflow_goal, reason, condition); - // If window_size is `None`, we don't reflow, so the document stays - // dirty. Otherwise, we shouldn't need a reflow immediately after a + // We shouldn't need a reflow immediately after a // reflow, except if we're waiting for a deferred paint. - assert!(!self.Document().needs_reflow() || - (!for_display && self.Document().needs_paint()) || - self.window_size.get().is_none() || - self.suppress_reflow.get()); + let condition = self.Document().needs_reflow(); + assert!( + { + condition.is_none() || + (!for_display && + condition == Some(ReflowTriggerCondition::PaintPostponed)) || + self.suppress_reflow.get() + }, + "condition was {:?}", + condition + ); } else { - debug!("Document doesn't need reflow - skipping it (reason {:?})", reason); + debug!( + "Document doesn't need reflow - skipping it (reason {:?})", + reason + ); } // If writing a screenshot, check if the script has reached a state @@ -1305,194 +1840,176 @@ impl Window { // When all these conditions are met, notify the constellation // that this pipeline is ready to write the image (from the script thread // perspective at least). - if (opts::get().output_file.is_some() || - opts::get().exit_after_load || - opts::get().webdriver_port.is_some()) && for_display { + if self.prepare_for_screenshot && for_display { let document = self.Document(); // Checks if the html element has reftest-wait attribute present. // See http://testthewebforward.org/docs/reftests.html let html_element = document.GetDocumentElement(); let reftest_wait = html_element.map_or(false, |elem| { - elem.has_class(&Atom::from("reftest-wait")) + elem.has_class(&atom!("reftest-wait"), CaseSensitivity::CaseSensitive) }); - let ready_state = document.ReadyState(); - + let has_sent_idle_message = self.has_sent_idle_message.get(); + let is_ready_state_complete = document.ReadyState() == DocumentReadyState::Complete; let pending_images = self.pending_layout_images.borrow().is_empty(); - if ready_state == DocumentReadyState::Complete && !reftest_wait && pending_images { - let global_scope = self.upcast::<GlobalScope>(); - let event = ConstellationMsg::SetDocumentState(global_scope.pipeline_id(), DocumentState::Idle); - global_scope.constellation_chan().send(event).unwrap(); + + if !has_sent_idle_message && is_ready_state_complete && !reftest_wait && pending_images + { + let event = ScriptMsg::SetDocumentState(DocumentState::Idle); + self.send_to_constellation(event); + self.has_sent_idle_message.set(true); } } issued_reflow } - pub fn layout(&self) -> &LayoutRPC { + pub fn layout_reflow(&self, query_msg: QueryMsg) -> bool { + if self.layout_is_busy.load(Ordering::Relaxed) { + let url = self.get_url().into_string(); + self.time_profiler_chan() + .send(ProfilerMsg::BlockedLayoutQuery(url)); + } + + self.reflow( + ReflowGoal::LayoutQuery(query_msg, time::precise_time_ns()), + ReflowReason::Query, + ) + } + + pub fn resolved_font_style_query(&self, node: &Node, value: String) -> Option<ServoArc<Font>> { + let id = PropertyId::Shorthand(ShorthandId::Font); + if !self.layout_reflow(QueryMsg::ResolvedFontStyleQuery( + node.to_trusted_node_address(), + id, + value, + )) { + return None; + } + self.layout_rpc.resolved_font_style() + } + + pub fn layout(&self) -> &dyn LayoutRPC { &*self.layout_rpc } - pub fn content_box_query(&self, content_box_request: TrustedNodeAddress) -> Option<Rect<Au>> { - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::ContentBoxQuery(content_box_request), - ReflowReason::Query) { + pub fn content_box_query(&self, node: &Node) -> Option<UntypedRect<Au>> { + if !self.layout_reflow(QueryMsg::ContentBoxQuery(node.to_opaque())) { return None; } let ContentBoxResponse(rect) = self.layout_rpc.content_box(); rect } - pub fn content_boxes_query(&self, content_boxes_request: TrustedNodeAddress) -> Vec<Rect<Au>> { - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::ContentBoxesQuery(content_boxes_request), - ReflowReason::Query) { + pub fn content_boxes_query(&self, node: &Node) -> Vec<UntypedRect<Au>> { + if !self.layout_reflow(QueryMsg::ContentBoxesQuery(node.to_opaque())) { return vec![]; } let ContentBoxesResponse(rects) = self.layout_rpc.content_boxes(); rects } - pub fn client_rect_query(&self, node_geometry_request: TrustedNodeAddress) -> Rect<i32> { - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::NodeGeometryQuery(node_geometry_request), - ReflowReason::Query) { + pub fn client_rect_query(&self, node: &Node) -> UntypedRect<i32> { + if !self.layout_reflow(QueryMsg::ClientRectQuery(node.to_opaque())) { return Rect::zero(); } self.layout_rpc.node_geometry().client_rect } - pub fn hit_test_query(&self, - client_point: Point2D<f32>, - update_cursor: bool) - -> Option<UntrustedNodeAddress> { - let translated_point = - Point2D::new(client_point.x + self.PageXOffset() as f32, - client_point.y + self.PageYOffset() as f32); - - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::HitTestQuery(translated_point, - client_point, - update_cursor), - ReflowReason::Query) { - return None - } - - self.layout_rpc.hit_test().node_address - } - - pub fn scroll_area_query(&self, node: TrustedNodeAddress) -> Rect<i32> { - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::NodeScrollGeometryQuery(node), - ReflowReason::Query) { + pub fn scroll_area_query(&self, node: &Node) -> UntypedRect<i32> { + if !self.layout_reflow(QueryMsg::NodeScrollGeometryQuery(node.to_opaque())) { return Rect::zero(); } self.layout_rpc.node_scroll_area().client_rect } - pub fn overflow_query(&self, - node: TrustedNodeAddress) -> Point2D<overflow_x::computed_value::T> { - // NB: This is only called if the document is fully active, and the only - // reason to bail out from a query is if there's no viewport, so this - // *must* issue a reflow. - assert!(self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::NodeOverflowQuery(node), - ReflowReason::Query)); - - self.layout_rpc.node_overflow().0.unwrap() + pub fn scroll_offset_query(&self, node: &Node) -> Vector2D<f32, LayoutPixel> { + if let Some(scroll_offset) = self.scroll_offsets.borrow().get(&node.to_opaque()) { + return *scroll_offset; + } + Vector2D::new(0.0, 0.0) } - pub fn scroll_offset_query(&self, node: &Node) -> Point2D<f32> { - let mut node = Root::from_ref(node); - loop { - if let Some(scroll_offset) = self.scroll_offsets - .borrow() - .get(&node.to_untrusted_node_address()) { - return *scroll_offset - } - node = match node.GetParentNode() { - Some(node) => node, - None => break, - } - } - let offset = self.current_viewport.get().origin; - Point2D::new(offset.x.to_f32_px(), offset.y.to_f32_px()) - } - - // https://drafts.csswg.org/cssom-view/#dom-element-scroll - pub fn scroll_node(&self, - node: TrustedNodeAddress, - x_: f64, - y_: f64, - behavior: ScrollBehavior) { - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::NodeScrollRootIdQuery(node), - ReflowReason::Query) { + // https://drafts.csswg.org/cssom-view/#element-scrolling-members + pub fn scroll_node(&self, node: &Node, x_: f64, y_: f64, behavior: ScrollBehavior) { + if !self.layout_reflow(QueryMsg::NodeScrollIdQuery(node.to_trusted_node_address())) { return; } - let NodeScrollRootIdResponse(scroll_root_id) = self.layout_rpc.node_scroll_root_id(); + + // The scroll offsets are immediatly updated since later calls + // to topScroll and others may access the properties before + // webrender has a chance to update the offsets. + self.scroll_offsets + .borrow_mut() + .insert(node.to_opaque(), Vector2D::new(x_ as f32, y_ as f32)); + + let NodeScrollIdResponse(scroll_id) = self.layout_rpc.node_scroll_id(); // Step 12 - self.perform_a_scroll(x_.to_f32().unwrap_or(0.0f32), - y_.to_f32().unwrap_or(0.0f32), - scroll_root_id, - behavior, - None); - } - - pub fn resolved_style_query(&self, - element: TrustedNodeAddress, - pseudo: Option<PseudoElement>, - property: PropertyId) -> DOMString { - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::ResolvedStyleQuery(element, pseudo, property), - ReflowReason::Query) { + self.perform_a_scroll( + x_.to_f32().unwrap_or(0.0f32), + y_.to_f32().unwrap_or(0.0f32), + scroll_id, + behavior, + None, + ); + } + + pub fn resolved_style_query( + &self, + element: TrustedNodeAddress, + pseudo: Option<PseudoElement>, + property: PropertyId, + ) -> DOMString { + if !self.layout_reflow(QueryMsg::ResolvedStyleQuery(element, pseudo, property)) { return DOMString::new(); } let ResolvedStyleResponse(resolved) = self.layout_rpc.resolved_style(); DOMString::from(resolved) } - pub fn offset_parent_query(&self, node: TrustedNodeAddress) -> (Option<Root<Element>>, Rect<Au>) { - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::OffsetParentQuery(node), - ReflowReason::Query) { + pub fn inner_window_dimensions_query( + &self, + browsing_context: BrowsingContextId, + ) -> Option<Size2D<f32, CSSPixel>> { + if !self.layout_reflow(QueryMsg::InnerWindowDimensionsQuery(browsing_context)) { + return None; + } + self.layout_rpc.inner_window_dimensions() + } + + #[allow(unsafe_code)] + pub fn offset_parent_query(&self, node: &Node) -> (Option<DomRoot<Element>>, UntypedRect<Au>) { + if !self.layout_reflow(QueryMsg::OffsetParentQuery(node.to_opaque())) { return (None, Rect::zero()); } + // FIXME(nox): Layout can reply with a garbage value which doesn't + // actually correspond to an element, that's unsound. let response = self.layout_rpc.offset_parent(); - let js_runtime = self.js_runtime.borrow(); - let js_runtime = js_runtime.as_ref().unwrap(); let element = response.node_address.and_then(|parent_node_address| { - let node = from_untrusted_node_address(js_runtime.rt(), parent_node_address); - Root::downcast(node) + let node = unsafe { from_untrusted_node_address(parent_node_address) }; + DomRoot::downcast(node) }); (element, response.rect) } - pub fn margin_style_query(&self, node: TrustedNodeAddress) -> MarginStyleResponse { - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::MarginStyleQuery(node), - ReflowReason::Query) { - return MarginStyleResponse::empty(); - } - self.layout_rpc.margin_style() - } - - pub fn text_index_query(&self, node: TrustedNodeAddress, mouse_x: i32, mouse_y: i32) -> TextIndexResponse { - if !self.reflow(ReflowGoal::ForScriptQuery, - ReflowQueryType::TextIndexQuery(node, mouse_x, mouse_y), - ReflowReason::Query) { + pub fn text_index_query( + &self, + node: &Node, + point_in_node: UntypedPoint2D<f32>, + ) -> TextIndexResponse { + if !self.layout_reflow(QueryMsg::TextIndexQuery(node.to_opaque(), point_in_node)) { return TextIndexResponse(None); } self.layout_rpc.text_index() } #[allow(unsafe_code)] - pub fn init_browsing_context(&self, browsing_context: &BrowsingContext) { - assert!(self.browsing_context.get().is_none()); - self.browsing_context.set(Some(&browsing_context)); + pub fn init_window_proxy(&self, window_proxy: &WindowProxy) { + assert!(self.window_proxy.get().is_none()); + self.window_proxy.set(Some(&window_proxy)); } #[allow(unsafe_code)] @@ -1500,60 +2017,110 @@ impl Window { assert!(self.document.get().is_none()); assert!(document.window() == self); self.document.set(Some(&document)); - if !opts::get().unminify_js { + if !self.unminify_js { return; } - // Create a folder for the document host to store unminified scripts. - if let Some(&Host::Domain(ref host)) = document.url().origin().host() { - let mut path = env::current_dir().unwrap(); - path.push("unminified-js"); - path.push(host); - let _ = fs::remove_dir_all(&path); - match fs::create_dir_all(&path) { - Ok(_) => { - *self.unminified_js_dir.borrow_mut() = Some(path.into_os_string().into_string().unwrap()); - debug!("Created folder for {:?} unminified scripts {:?}", host, self.unminified_js_dir.borrow()); - }, - Err(_) => warn!("Could not create unminified dir for {:?}", host), - } - } + // Set a path for the document host to store unminified scripts. + let mut path = env::current_dir().unwrap(); + path.push("unminified-js"); + *self.unminified_js_dir.borrow_mut() = Some(path.into_os_string().into_string().unwrap()); } /// Commence a new URL load which will either replace this window or scroll to a fragment. - pub fn load_url(&self, url: ServoUrl, replace: bool, force_reload: bool, - referrer_policy: Option<ReferrerPolicy>) { + /// + /// https://html.spec.whatwg.org/multipage/#navigating-across-documents + pub fn load_url( + &self, + replace: HistoryEntryReplacement, + force_reload: bool, + load_data: LoadData, + ) { let doc = self.Document(); - let referrer_policy = referrer_policy.or(doc.get_referrer_policy()); - - // https://html.spec.whatwg.org/multipage/#navigating-across-documents - if !force_reload && url.as_url()[..Position::AfterQuery] == - doc.url().as_url()[..Position::AfterQuery] { - // Step 5 - if let Some(fragment) = url.fragment() { - doc.check_and_scroll_fragment(fragment); - doc.set_url(url.clone()); - return + // TODO: Important re security. See https://github.com/servo/servo/issues/23373 + // Step 3: check that the source browsing-context is "allowed to navigate" this window. + if !force_reload && + load_data.url.as_url()[..Position::AfterQuery] == + doc.url().as_url()[..Position::AfterQuery] + { + // Step 6 + if let Some(fragment) = load_data.url.fragment() { + self.send_to_constellation(ScriptMsg::NavigatedToFragment( + load_data.url.clone(), + replace, + )); + doc.check_and_scroll_fragment(fragment); + let this = Trusted::new(self); + let old_url = doc.url().into_string(); + let new_url = load_data.url.clone().into_string(); + let task = task!(hashchange_event: move || { + let this = this.root(); + let event = HashChangeEvent::new( + &this, + atom!("hashchange"), + false, + false, + old_url, + new_url); + event.upcast::<Event>().fire(this.upcast::<EventTarget>()); + }); + // FIXME(nox): Why are errors silenced here? + let _ = self.script_chan.send(CommonScriptMsg::Task( + ScriptThreadEventCategory::DomEvent, + Box::new( + self.task_manager + .task_canceller(TaskSourceName::DOMManipulation) + .wrap_task(task), + ), + Some(self.pipeline_id()), + TaskSourceName::DOMManipulation, + )); + doc.set_url(load_data.url.clone()); + return; + } + } + + let pipeline_id = self.upcast::<GlobalScope>().pipeline_id(); + + // Step 4 and 5 + let window_proxy = self.window_proxy(); + if let Some(active) = window_proxy.currently_active() { + if pipeline_id == active { + if doc.is_prompting_or_unloading() { + return; } + } } - self.main_thread_script_chan().send( - MainThreadScriptMsg::Navigate(self.upcast::<GlobalScope>().pipeline_id(), - LoadData::new(url, referrer_policy, Some(doc.url())), - replace)).unwrap(); + // Step 8 + if doc.prompt_to_unload(false) { + let window_proxy = self.window_proxy(); + if window_proxy.parent().is_some() { + // Step 10 + // If browsingContext is a nested browsing context, + // then put it in the delaying load events mode. + window_proxy.start_delaying_load_events_mode(); + } + // TODO: step 11, navigationType. + // Step 12, 13 + ScriptThread::navigate( + window_proxy.browsing_context_id(), + pipeline_id, + load_data, + replace, + ); + }; } pub fn handle_fire_timer(&self, timer_id: TimerEventId) { self.upcast::<GlobalScope>().fire_timer(timer_id); - self.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::Timer); + self.reflow(ReflowGoal::Full, ReflowReason::Timer); } pub fn set_window_size(&self, size: WindowSizeData) { - self.window_size.set(Some(size)); + self.window_size.set(size); } - pub fn window_size(&self) -> Option<WindowSizeData> { + pub fn window_size(&self) -> WindowSizeData { self.window_size.get() } @@ -1561,8 +2128,12 @@ impl Window { self.Document().url() } - pub fn layout_chan(&self) -> &Sender<Msg> { - &self.layout_chan + pub fn layout_chan(&self) -> Option<&Sender<Msg>> { + if self.is_alive() { + Some(&self.layout_chan) + } else { + None + } } pub fn windowproxy_handler(&self) -> WindowProxyHandler { @@ -1574,7 +2145,8 @@ impl Window { } pub fn add_pending_reflow(&self) { - self.pending_reflow_count.set(self.pending_reflow_count.get() + 1); + self.pending_reflow_count + .set(self.pending_reflow_count.get() + 1); } pub fn set_resize_event(&self, event: WindowSizeData, event_type: WindowSizeType) { @@ -1587,22 +2159,23 @@ impl Window { event } - pub fn set_page_clip_rect_with_new_viewport(&self, viewport: Rect<f32>) -> bool { + pub fn set_page_clip_rect_with_new_viewport(&self, viewport: UntypedRect<f32>) -> bool { let rect = f32_rect_to_au_rect(viewport.clone()); self.current_viewport.set(rect); // We use a clipping rectangle that is five times the size of the of the viewport, // so that we don't collect display list items for areas too far outside the viewport, // but also don't trigger reflows every time the viewport changes. static VIEWPORT_EXPANSION: f32 = 2.0; // 2 lengths on each side plus original length is 5 total. - let proposed_clip_rect = f32_rect_to_au_rect( - viewport.inflate(viewport.size.width * VIEWPORT_EXPANSION, - viewport.size.height * VIEWPORT_EXPANSION)); + let proposed_clip_rect = f32_rect_to_au_rect(viewport.inflate( + viewport.size.width * VIEWPORT_EXPANSION, + viewport.size.height * VIEWPORT_EXPANSION, + )); let clip_rect = self.page_clip_rect.get(); if proposed_clip_rect == clip_rect { return false; } - let had_clip_rect = clip_rect != max_rect(); + let had_clip_rect = clip_rect != MaxRect::max_rect(); if had_clip_rect && !should_move_clip_rect(clip_rect, viewport) { return false; } @@ -1610,22 +2183,17 @@ impl Window { self.page_clip_rect.set(proposed_clip_rect); // If we didn't have a clip rect, the previous display doesn't need rebuilding - // because it was built for infinite clip (max_rect()). + // because it was built for infinite clip (MaxRect::amax_rect()). had_clip_rect } - // https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts - pub fn IndexedGetter(&self, _index: u32, _found: &mut bool) -> Option<Root<Window>> { - None - } - pub fn suspend(&self) { // Suspend timer events. self.upcast::<GlobalScope>().suspend(); // Set the window proxy to be a cross-origin window. - if self.browsing_context().currently_active() == Some(self.global().pipeline_id()) { - self.browsing_context().unset_currently_active(); + if self.window_proxy().currently_active() == Some(self.global().pipeline_id()) { + self.window_proxy().unset_currently_active(); } // A hint to the JS runtime that now would be a good time to @@ -1640,7 +2208,7 @@ impl Window { self.upcast::<GlobalScope>().resume(); // Set the window proxy to be this object. - self.browsing_context().set_currently_active(self); + self.window_proxy().set_currently_active(self); // Push the document title to the compositor since we are // activating this document due to a navigation. @@ -1658,11 +2226,15 @@ impl Window { sender.send(Some(marker)).unwrap(); } - pub fn set_devtools_timeline_markers(&self, - markers: Vec<TimelineMarkerType>, - reply: IpcSender<Option<TimelineMarker>>) { + pub fn set_devtools_timeline_markers( + &self, + markers: Vec<TimelineMarkerType>, + reply: IpcSender<Option<TimelineMarker>>, + ) { *self.devtools_marker_sender.borrow_mut() = Some(reply); - self.devtools_markers.borrow_mut().extend(markers.into_iter()); + self.devtools_markers + .borrow_mut() + .extend(markers.into_iter()); } pub fn drop_devtools_timeline_markers(&self, markers: Vec<TimelineMarkerType>) { @@ -1685,39 +2257,37 @@ impl Window { // https://html.spec.whatwg.org/multipage/#top-level-browsing-context pub fn is_top_level(&self) -> bool { - match self.parent_info { - Some((_, FrameType::IFrame)) => false, - _ => true, - } - } - - /// Returns whether this window is mozbrowser. - pub fn is_mozbrowser(&self) -> bool { - PREFS.is_mozbrowser_enabled() && self.parent_info().is_none() - } - - /// Returns whether mozbrowser is enabled and `obj` has been created - /// in a top-level `Window` global. - #[allow(unsafe_code)] - pub unsafe fn global_is_mozbrowser(_: *mut JSContext, obj: HandleObject) -> bool { - GlobalScope::from_object(obj.get()) - .downcast::<Window>() - .map_or(false, |window| window.is_mozbrowser()) - } - - #[allow(unsafe_code)] - pub fn dispatch_mozbrowser_event(&self, event: MozBrowserEvent) { - assert!(PREFS.is_mozbrowser_enabled()); - let custom_event = build_mozbrowser_custom_event(&self, event); - custom_event.upcast::<Event>().fire(self.upcast()); + self.parent_info.is_none() } + /// Evaluate media query lists and report changes + /// <https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes> pub fn evaluate_media_queries_and_report_changes(&self) { - self.media_query_lists.evaluate_and_report_changes(); + rooted_vec!(let mut mql_list); + self.media_query_lists.for_each(|mql| { + if let MediaQueryListMatchState::Changed(_) = mql.evaluate_changes() { + // Recording list of changed Media Queries + mql_list.push(Dom::from_ref(&*mql)); + } + }); + // Sending change events for all changed Media Queries + for mql in mql_list.iter() { + let event = MediaQueryListEvent::new( + &mql.global(), + atom!("change"), + false, + false, + mql.Media(), + mql.Matches(), + ); + event.upcast::<Event>().fire(mql.upcast::<EventTarget>()); + } + self.Document().react_to_environment_changes(); } /// Slow down/speed up timers based on visibility. pub fn alter_resource_utilization(&self, visible: bool) { + self.visible.set(visible); if visible { self.upcast::<GlobalScope>().speed_up_timers(); } else { @@ -1725,118 +2295,235 @@ impl Window { } } + pub fn visible(&self) -> bool { + self.visible.get() + } + pub fn unminified_js_dir(&self) -> Option<String> { self.unminified_js_dir.borrow().clone() } + + pub fn local_script_source(&self) -> &Option<String> { + &self.local_script_source + } + + pub fn set_navigation_start(&self) { + let current_time = time::get_time(); + let now = (current_time.sec * 1000 + current_time.nsec as i64 / 1000000) as u64; + self.navigation_start.set(now); + self.navigation_start_precise.set(time::precise_time_ns()); + } + + pub fn send_to_embedder(&self, msg: EmbedderMsg) { + self.send_to_constellation(ScriptMsg::ForwardToEmbedder(msg)); + } + + pub fn send_to_constellation(&self, msg: ScriptMsg) { + self.upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(msg) + .unwrap(); + } + + pub fn webrender_document(&self) -> DocumentId { + self.webrender_document + } + + pub fn in_immersive_xr_session(&self) -> bool { + self.navigator + .get() + .as_ref() + .and_then(|nav| nav.xr()) + .map_or(false, |xr| xr.pending_or_active_session()) + } } impl Window { #[allow(unsafe_code)] - pub fn new(runtime: Rc<Runtime>, - script_chan: MainThreadScriptChan, - dom_task_source: DOMManipulationTaskSource, - user_task_source: UserInteractionTaskSource, - network_task_source: NetworkingTaskSource, - history_task_source: HistoryTraversalTaskSource, - file_task_source: FileReadingTaskSource, - image_cache_chan: Sender<ImageCacheMsg>, - image_cache: Arc<ImageCache>, - resource_threads: ResourceThreads, - bluetooth_thread: IpcSender<BluetoothRequest>, - mem_profiler_chan: MemProfilerChan, - time_profiler_chan: TimeProfilerChan, - devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, - constellation_chan: IpcSender<ConstellationMsg>, - control_chan: IpcSender<ConstellationControlMsg>, - scheduler_chan: IpcSender<TimerSchedulerMsg>, - timer_event_chan: IpcSender<TimerEvent>, - layout_chan: Sender<Msg>, - id: PipelineId, - parent_info: Option<(PipelineId, FrameType)>, - window_size: Option<WindowSizeData>, - origin: MutableOrigin, - webvr_thread: Option<IpcSender<WebVRMsg>>) - -> Root<Window> { - let layout_rpc: Box<LayoutRPC + Send> = { - let (rpc_send, rpc_recv) = channel(); + pub fn new( + runtime: Rc<Runtime>, + script_chan: MainThreadScriptChan, + task_manager: TaskManager, + image_cache_chan: Sender<ImageCacheMsg>, + image_cache: Arc<dyn ImageCache>, + resource_threads: ResourceThreads, + bluetooth_thread: IpcSender<BluetoothRequest>, + mem_profiler_chan: MemProfilerChan, + time_profiler_chan: TimeProfilerChan, + devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, + constellation_chan: ScriptToConstellationChan, + control_chan: IpcSender<ConstellationControlMsg>, + scheduler_chan: IpcSender<TimerSchedulerMsg>, + layout_chan: Sender<Msg>, + pipelineid: PipelineId, + parent_info: Option<PipelineId>, + window_size: WindowSizeData, + origin: MutableOrigin, + creator_url: ServoUrl, + navigation_start: u64, + navigation_start_precise: u64, + webgl_chan: Option<WebGLChan>, + webxr_registry: webxr_api::Registry, + microtask_queue: Rc<MicrotaskQueue>, + webrender_document: DocumentId, + webrender_api_sender: WebrenderIpcSender, + layout_is_busy: Arc<AtomicBool>, + relayout_event: bool, + prepare_for_screenshot: bool, + unminify_js: bool, + local_script_source: Option<String>, + userscripts_path: Option<String>, + is_headless: bool, + replace_surrogates: bool, + user_agent: Cow<'static, str>, + player_context: WindowGLContext, + event_loop_waker: Option<Box<dyn EventLoopWaker>>, + gpu_id_hub: Arc<ParkMutex<Identities>>, + inherited_secure_context: Option<bool>, + ) -> DomRoot<Self> { + let layout_rpc: Box<dyn LayoutRPC + Send> = { + let (rpc_send, rpc_recv) = unbounded(); layout_chan.send(Msg::GetRPC(rpc_send)).unwrap(); rpc_recv.recv().unwrap() }; let error_reporter = CSSErrorReporter { - pipelineid: id, + pipelineid, script_chan: Arc::new(Mutex::new(control_chan)), }; - let current_time = time::get_time(); - let win = box Window { - globalscope: - GlobalScope::new_inherited( - id, - devtools_chan, - mem_profiler_chan, - time_profiler_chan, - constellation_chan, - scheduler_chan, - resource_threads, - timer_event_chan, - origin), - script_chan: script_chan, - dom_manipulation_task_source: dom_task_source, - user_interaction_task_source: user_task_source, - networking_task_source: network_task_source, - history_traversal_task_source: history_task_source, - file_reading_task_source: file_task_source, - image_cache_chan: image_cache_chan, - image_cache: image_cache.clone(), + let win = Box::new(Self { + globalscope: GlobalScope::new_inherited( + pipelineid, + devtools_chan, + mem_profiler_chan, + time_profiler_chan, + constellation_chan, + scheduler_chan, + resource_threads, + origin, + Some(creator_url), + microtask_queue, + is_headless, + user_agent, + gpu_id_hub, + inherited_secure_context, + ), + script_chan, + task_manager, + image_cache_chan, + image_cache, navigator: Default::default(), + location: Default::default(), history: Default::default(), - browsing_context: Default::default(), + custom_element_registry: Default::default(), + window_proxy: Default::default(), document: Default::default(), performance: Default::default(), - navigation_start: (current_time.sec * 1000 + current_time.nsec as i64 / 1000000) as u64, - navigation_start_precise: time::precise_time_ns() as f64, + navigation_start: Cell::new(navigation_start), + navigation_start_precise: Cell::new(navigation_start_precise), screen: Default::default(), session_storage: Default::default(), local_storage: Default::default(), - status: DOMRefCell::new(DOMString::new()), - parent_info: parent_info, + status: DomRefCell::new(DOMString::new()), + parent_info, dom_static: GlobalStaticData::new(), - js_runtime: DOMRefCell::new(Some(runtime.clone())), - bluetooth_thread: bluetooth_thread, + js_runtime: DomRefCell::new(Some(runtime.clone())), + bluetooth_thread, bluetooth_extra_permission_data: BluetoothExtraPermissionData::new(), - page_clip_rect: Cell::new(max_rect()), - resize_event: Cell::new(None), - layout_chan: layout_chan, - layout_rpc: layout_rpc, + page_clip_rect: Cell::new(MaxRect::max_rect()), + resize_event: Default::default(), + layout_chan, + layout_rpc, window_size: Cell::new(window_size), current_viewport: Cell::new(Rect::zero()), suppress_reflow: Cell::new(true), - pending_reflow_count: Cell::new(0), + pending_reflow_count: Default::default(), current_state: Cell::new(WindowState::Alive), - devtools_marker_sender: DOMRefCell::new(None), - devtools_markers: DOMRefCell::new(HashSet::new()), - webdriver_script_chan: DOMRefCell::new(None), - ignore_further_async_events: Default::default(), - error_reporter: error_reporter, - scroll_offsets: DOMRefCell::new(HashMap::new()), - media_query_lists: WeakMediaQueryListVec::new(), + devtools_marker_sender: Default::default(), + devtools_markers: Default::default(), + webdriver_script_chan: Default::default(), + error_reporter, + scroll_offsets: Default::default(), + media_query_lists: DOMTracker::new(), test_runner: Default::default(), - webvr_thread: webvr_thread, - permission_state_invocation_results: DOMRefCell::new(HashMap::new()), - pending_layout_images: DOMRefCell::new(HashMap::new()), - unminified_js_dir: DOMRefCell::new(None), - }; + webgl_chan, + webxr_registry, + pending_layout_images: Default::default(), + unminified_js_dir: Default::default(), + local_script_source, + test_worklet: Default::default(), + paint_worklet: Default::default(), + webrender_document, + exists_mut_observer: Cell::new(false), + webrender_api_sender, + has_sent_idle_message: Cell::new(false), + layout_is_busy, + relayout_event, + prepare_for_screenshot, + unminify_js, + userscripts_path, + replace_surrogates, + player_context, + event_loop_waker, + visible: Cell::new(true), + layout_marker: DomRefCell::new(Rc::new(Cell::new(true))), + current_event: DomRefCell::new(None), + }); - unsafe { - WindowBinding::Wrap(runtime.cx(), win) + unsafe { WindowBinding::Wrap(JSContext::from_ptr(runtime.cx()), win) } + } + + pub fn pipeline_id(&self) -> PipelineId { + self.upcast::<GlobalScope>().pipeline_id() + } + + /// Create a new cached instance of the given value. + pub fn cache_layout_value<T>(&self, value: T) -> LayoutValue<T> + where + T: Copy + JSTraceable + MallocSizeOf, + { + LayoutValue::new(self.layout_marker.borrow().clone(), value) + } +} + +/// An instance of a value associated with a particular snapshot of layout. This stored +/// value can only be read as long as the associated layout marker that is considered +/// valid. It will automatically become unavailable when the next layout operation is +/// performed. +#[derive(JSTraceable, MallocSizeOf)] +pub struct LayoutValue<T: JSTraceable + MallocSizeOf> { + #[ignore_malloc_size_of = "Rc is hard"] + is_valid: Rc<Cell<bool>>, + value: T, +} + +impl<T: Copy + JSTraceable + MallocSizeOf> LayoutValue<T> { + fn new(marker: Rc<Cell<bool>>, value: T) -> Self { + LayoutValue { + is_valid: marker, + value, } } + + /// Retrieve the stored value if it is still valid. + pub fn get(&self) -> Result<T, ()> { + if self.is_valid.get() { + return Ok(self.value); + } + Err(()) + } } -fn should_move_clip_rect(clip_rect: Rect<Au>, new_viewport: Rect<f32>) -> bool { - let clip_rect = Rect::new(Point2D::new(clip_rect.origin.x.to_f32_px(), - clip_rect.origin.y.to_f32_px()), - Size2D::new(clip_rect.size.width.to_f32_px(), - clip_rect.size.height.to_f32_px())); +fn should_move_clip_rect(clip_rect: UntypedRect<Au>, new_viewport: UntypedRect<f32>) -> bool { + let clip_rect = UntypedRect::new( + Point2D::new( + clip_rect.origin.x.to_f32_px(), + clip_rect.origin.y.to_f32_px(), + ), + Size2D::new( + clip_rect.size.width.to_f32_px(), + clip_rect.size.height.to_f32_px(), + ), + ); // We only need to move the clip rect if the viewport is getting near the edge of // our preexisting clip rect. We use half of the size of the viewport as a heuristic @@ -1845,111 +2532,131 @@ fn should_move_clip_rect(clip_rect: Rect<Au>, new_viewport: Rect<f32>) -> bool { let viewport_scroll_margin = new_viewport.size * VIEWPORT_SCROLL_MARGIN_SIZE; (clip_rect.origin.x - new_viewport.origin.x).abs() <= viewport_scroll_margin.width || - (clip_rect.max_x() - new_viewport.max_x()).abs() <= viewport_scroll_margin.width || - (clip_rect.origin.y - new_viewport.origin.y).abs() <= viewport_scroll_margin.height || - (clip_rect.max_y() - new_viewport.max_y()).abs() <= viewport_scroll_margin.height + (clip_rect.max_x() - new_viewport.max_x()).abs() <= viewport_scroll_margin.width || + (clip_rect.origin.y - new_viewport.origin.y).abs() <= viewport_scroll_margin.height || + (clip_rect.max_y() - new_viewport.max_y()).abs() <= viewport_scroll_margin.height } -fn debug_reflow_events(id: PipelineId, goal: &ReflowGoal, query_type: &ReflowQueryType, reason: &ReflowReason) { - let mut debug_msg = format!("**** pipeline={}", id); - debug_msg.push_str(match *goal { - ReflowGoal::ForDisplay => "\tForDisplay", - ReflowGoal::ForScriptQuery => "\tForScriptQuery", - }); - - debug_msg.push_str(match *query_type { - ReflowQueryType::NoQuery => "\tNoQuery", - ReflowQueryType::ContentBoxQuery(_n) => "\tContentBoxQuery", - ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery", - ReflowQueryType::HitTestQuery(..) => "\tHitTestQuery", - ReflowQueryType::NodesFromPoint(..) => "\tNodesFromPoint", - ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery", - ReflowQueryType::NodeOverflowQuery(_n) => "\tNodeOverFlowQuery", - ReflowQueryType::NodeScrollGeometryQuery(_n) => "\tNodeScrollGeometryQuery", - ReflowQueryType::NodeScrollRootIdQuery(_n) => "\tNodeScrollRootIdQuery", - ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery", - ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery", - ReflowQueryType::MarginStyleQuery(_n) => "\tMarginStyleQuery", - ReflowQueryType::TextIndexQuery(..) => "\tTextIndexQuery", - }); - - debug_msg.push_str(match *reason { - ReflowReason::CachedPageNeededReflow => "\tCachedPageNeededReflow", - ReflowReason::RefreshTick => "\tRefreshTick", - ReflowReason::FirstLoad => "\tFirstLoad", - ReflowReason::KeyEvent => "\tKeyEvent", - ReflowReason::MouseEvent => "\tMouseEvent", - ReflowReason::Query => "\tQuery", - ReflowReason::Timer => "\tTimer", - ReflowReason::Viewport => "\tViewport", - ReflowReason::WindowResize => "\tWindowResize", - ReflowReason::DOMContentLoaded => "\tDOMContentLoaded", - ReflowReason::DocumentLoaded => "\tDocumentLoaded", - ReflowReason::StylesheetLoaded => "\tStylesheetLoaded", - ReflowReason::ImageLoaded => "\tImageLoaded", - ReflowReason::RequestAnimationFrame => "\tRequestAnimationFrame", - ReflowReason::WebFontLoaded => "\tWebFontLoaded", - ReflowReason::FramedContentChanged => "\tFramedContentChanged", - ReflowReason::IFrameLoadEvent => "\tIFrameLoadEvent", - ReflowReason::MissingExplicitReflow => "\tMissingExplicitReflow", - ReflowReason::ElementStateChanged => "\tElementStateChanged", - }); - - println!("{}", debug_msg); +fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &ReflowReason) { + let goal_string = match *reflow_goal { + ReflowGoal::Full => "\tFull", + ReflowGoal::TickAnimations => "\tTickAnimations", + ReflowGoal::LayoutQuery(ref query_msg, _) => match query_msg { + &QueryMsg::ContentBoxQuery(_n) => "\tContentBoxQuery", + &QueryMsg::ContentBoxesQuery(_n) => "\tContentBoxesQuery", + &QueryMsg::NodesFromPointQuery(..) => "\tNodesFromPointQuery", + &QueryMsg::ClientRectQuery(_n) => "\tClientRectQuery", + &QueryMsg::NodeScrollGeometryQuery(_n) => "\tNodeScrollGeometryQuery", + &QueryMsg::NodeScrollIdQuery(_n) => "\tNodeScrollIdQuery", + &QueryMsg::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery", + &QueryMsg::ResolvedFontStyleQuery(..) => "\nResolvedFontStyleQuery", + &QueryMsg::OffsetParentQuery(_n) => "\tOffsetParentQuery", + &QueryMsg::StyleQuery => "\tStyleQuery", + &QueryMsg::TextIndexQuery(..) => "\tTextIndexQuery", + &QueryMsg::ElementInnerTextQuery(_) => "\tElementInnerTextQuery", + &QueryMsg::InnerWindowDimensionsQuery(_) => "\tInnerWindowDimensionsQuery", + }, + }; + + println!("**** pipeline={}\t{}\t{:?}", id, goal_string, reason); } -struct PostMessageHandler { - destination: Trusted<Window>, - origin: Option<ImmutableOrigin>, - message: StructuredCloneData, -} +impl Window { + // https://html.spec.whatwg.org/multipage/#dom-window-postmessage step 7. + pub fn post_message( + &self, + target_origin: Option<ImmutableOrigin>, + source_origin: ImmutableOrigin, + source: &WindowProxy, + data: StructuredSerializedData, + ) { + let this = Trusted::new(self); + let source = Trusted::new(source); + let task = task!(post_serialised_message: move || { + let this = this.root(); + let source = source.root(); + let document = this.Document(); + + // Step 7.1. + if let Some(ref target_origin) = target_origin { + if !target_origin.same_origin(document.origin()) { + return; + } + } -impl PostMessageHandler { - fn new(window: &Window, - origin: Option<ImmutableOrigin>, - message: StructuredCloneData) -> PostMessageHandler { - PostMessageHandler { - destination: Trusted::new(window), - origin: origin, - message: message, - } + // Steps 7.2.-7.5. + let cx = this.get_cx(); + let obj = this.reflector().get_jsobject(); + let _ac = JSAutoRealm::new(*cx, obj.get()); + rooted!(in(*cx) let mut message_clone = UndefinedValue()); + if let Ok(ports) = structuredclone::read(this.upcast(), data, message_clone.handle_mut()) { + // Step 7.6, 7.7 + MessageEvent::dispatch_jsval( + this.upcast(), + this.upcast(), + message_clone.handle(), + Some(&source_origin.ascii_serialization()), + Some(&*source), + ports, + ); + } else { + // Step 4, fire messageerror. + MessageEvent::dispatch_error( + this.upcast(), + this.upcast(), + ); + } + }); + // FIXME(nox): Why are errors silenced here? + // TODO(#12718): Use the "posted message task source". + // TODO: When switching to the right task source, update the task_canceller call too. + let _ = self.script_chan.send(CommonScriptMsg::Task( + ScriptThreadEventCategory::DomEvent, + Box::new( + self.task_manager + .task_canceller(TaskSourceName::DOMManipulation) + .wrap_task(task), + ), + Some(self.pipeline_id()), + TaskSourceName::DOMManipulation, + )); } } -impl Runnable for PostMessageHandler { - // https://html.spec.whatwg.org/multipage/#dom-window-postmessage steps 10-12. - fn handler(self: Box<PostMessageHandler>) { - let this = *self; - let window = this.destination.root(); - - // Step 10. - let doc = window.Document(); - if let Some(source) = this.origin { - if !source.same_origin(doc.origin()) { - return; - } - } - - let cx = window.get_cx(); - let globalhandle = window.reflector().get_jsobject(); - let _ac = JSAutoCompartment::new(cx, globalhandle.get()); - - rooted!(in(cx) let mut message = UndefinedValue()); - this.message.read(window.upcast(), message.handle_mut()); - - // Step 11-12. - // TODO(#12719): set the other attributes. - MessageEvent::dispatch_jsval(window.upcast(), - window.upcast(), - message.handle()); - } +#[derive(Clone, MallocSizeOf)] +pub struct CSSErrorReporter { + pub pipelineid: PipelineId, + // Arc+Mutex combo is necessary to make this struct Sync, + // which is necessary to fulfill the bounds required by the + // uses of the ParseErrorReporter trait. + #[ignore_malloc_size_of = "Arc is defined in libstd"] + pub script_chan: Arc<Mutex<IpcSender<ConstellationControlMsg>>>, } +unsafe_no_jsmanaged_fields!(CSSErrorReporter); + +impl ParseErrorReporter for CSSErrorReporter { + fn report_error(&self, url: &ServoUrl, location: SourceLocation, error: ContextualParseError) { + if log_enabled!(log::Level::Info) { + info!( + "Url:\t{}\n{}:{} {}", + url.as_str(), + location.line, + location.column, + error + ) + } -impl Window { - pub fn post_message(&self, origin: Option<ImmutableOrigin>, data: StructuredCloneData) { - let runnable = PostMessageHandler::new(self, origin, data); - let msg = CommonScriptMsg::RunnableMsg(ScriptThreadEventCategory::DomEvent, box runnable); - // TODO(#12718): Use the "posted message task source". - let _ = self.script_chan.send(msg); + //TODO: report a real filename + let _ = self + .script_chan + .lock() + .unwrap() + .send(ConstellationControlMsg::ReportCSSError( + self.pipelineid, + url.to_string(), + location.line, + location.column, + error.to_string(), + )); } } diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs new file mode 100644 index 00000000000..0f92e4b3367 --- /dev/null +++ b/components/script/dom/windowproxy.rs @@ -0,0 +1,1244 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::conversions::{root_from_handleobject, ToJSValConvertible}; +use crate::dom::bindings::error::{throw_dom_exception, Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::proxyhandler::fill_property_descriptor; +use crate::dom::bindings::reflector::{DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::bindings::utils::{get_array_index_from_id, AsVoidPtr, WindowProxyHandler}; +use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow; +use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use crate::realms::{enter_realm, AlreadyInRealm, InRealm}; +use crate::script_runtime::JSContext as SafeJSContext; +use crate::script_thread::ScriptThread; +use dom_struct::dom_struct; +use embedder_traits::EmbedderMsg; +use indexmap::map::IndexMap; +use ipc_channel::ipc; +use js::glue::{CreateWrapperProxyHandler, ProxyTraps}; +use js::glue::{GetProxyPrivate, GetProxyReservedSlot, SetProxyReservedSlot}; +use js::jsapi::Handle as RawHandle; +use js::jsapi::HandleId as RawHandleId; +use js::jsapi::HandleObject as RawHandleObject; +use js::jsapi::HandleValue as RawHandleValue; +use js::jsapi::MutableHandle as RawMutableHandle; +use js::jsapi::MutableHandleObject as RawMutableHandleObject; +use js::jsapi::MutableHandleValue as RawMutableHandleValue; +use js::jsapi::{JSAutoRealm, JSContext, JSErrNum, JSFreeOp, JSObject}; +use js::jsapi::{JSTracer, JS_DefinePropertyById, JSPROP_ENUMERATE, JSPROP_READONLY}; +use js::jsapi::{JS_ForwardGetPropertyTo, JS_ForwardSetPropertyTo}; +use js::jsapi::{JS_GetOwnPropertyDescriptorById, JS_IsExceptionPending}; +use js::jsapi::{JS_HasOwnPropertyById, JS_HasPropertyById}; +use js::jsapi::{ObjectOpResult, PropertyDescriptor}; +use js::jsval::{JSVal, NullValue, PrivateValue, UndefinedValue}; +use js::rust::get_object_class; +use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy}; +use js::rust::{Handle, MutableHandle}; +use js::JSCLASS_IS_GLOBAL; +use msg::constellation_msg::BrowsingContextId; +use msg::constellation_msg::PipelineId; +use msg::constellation_msg::TopLevelBrowsingContextId; +use net_traits::request::Referrer; +use script_traits::{ + AuxiliaryBrowsingContextLoadInfo, HistoryEntryReplacement, LoadData, LoadOrigin, +}; +use script_traits::{NewLayoutInfo, ScriptMsg}; +use servo_url::{ImmutableOrigin, ServoUrl}; +use std::cell::Cell; +use std::ptr; +use style::attr::parse_integer; + +#[dom_struct] +// NOTE: the browsing context for a window is managed in two places: +// here, in script, but also in the constellation. The constellation +// manages the session history, which in script is accessed through +// History objects, messaging the constellation. +pub struct WindowProxy { + /// The JS WindowProxy object. + /// Unlike other reflectors, we mutate this field because + /// we have to brain-transplant the reflector when the WindowProxy + /// changes Window. + reflector: Reflector, + + /// The id of the browsing context. + /// In the case that this is a nested browsing context, this is the id + /// of the container. + browsing_context_id: BrowsingContextId, + + // https://html.spec.whatwg.org/multipage/#opener-browsing-context + opener: Option<BrowsingContextId>, + + /// The frame id of the top-level ancestor browsing context. + /// In the case that this is a top-level window, this is our id. + top_level_browsing_context_id: TopLevelBrowsingContextId, + + /// The name of the browsing context (sometimes, but not always, + /// equal to the name of a container element) + name: DomRefCell<DOMString>, + /// The pipeline id of the currently active document. + /// May be None, when the currently active document is in another script thread. + /// We do not try to keep the pipeline id for documents in other threads, + /// as this would require the constellation notifying many script threads about + /// the change, which could be expensive. + currently_active: Cell<Option<PipelineId>>, + + /// Has the browsing context been discarded? + discarded: Cell<bool>, + + /// Has the browsing context been disowned? + disowned: Cell<bool>, + + /// https://html.spec.whatwg.org/multipage/#is-closing + is_closing: Cell<bool>, + + /// The containing iframe element, if this is a same-origin iframe + frame_element: Option<Dom<Element>>, + + /// The parent browsing context's window proxy, if this is a nested browsing context + parent: Option<Dom<WindowProxy>>, + + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + delaying_load_events_mode: Cell<bool>, + + /// The creator browsing context's base url. + creator_base_url: Option<ServoUrl>, + + /// The creator browsing context's url. + creator_url: Option<ServoUrl>, + + /// The creator browsing context's origin. + creator_origin: Option<ImmutableOrigin>, +} + +impl WindowProxy { + pub fn new_inherited( + browsing_context_id: BrowsingContextId, + top_level_browsing_context_id: TopLevelBrowsingContextId, + currently_active: Option<PipelineId>, + frame_element: Option<&Element>, + parent: Option<&WindowProxy>, + opener: Option<BrowsingContextId>, + creator: CreatorBrowsingContextInfo, + ) -> WindowProxy { + let name = frame_element.map_or(DOMString::new(), |e| { + e.get_string_attribute(&local_name!("name")) + }); + WindowProxy { + reflector: Reflector::new(), + browsing_context_id: browsing_context_id, + top_level_browsing_context_id: top_level_browsing_context_id, + name: DomRefCell::new(name), + currently_active: Cell::new(currently_active), + discarded: Cell::new(false), + disowned: Cell::new(false), + is_closing: Cell::new(false), + frame_element: frame_element.map(Dom::from_ref), + parent: parent.map(Dom::from_ref), + delaying_load_events_mode: Cell::new(false), + opener, + creator_base_url: creator.base_url, + creator_url: creator.url, + creator_origin: creator.origin, + } + } + + #[allow(unsafe_code)] + pub fn new( + window: &Window, + browsing_context_id: BrowsingContextId, + top_level_browsing_context_id: TopLevelBrowsingContextId, + frame_element: Option<&Element>, + parent: Option<&WindowProxy>, + opener: Option<BrowsingContextId>, + creator: CreatorBrowsingContextInfo, + ) -> DomRoot<WindowProxy> { + unsafe { + let WindowProxyHandler(handler) = window.windowproxy_handler(); + assert!(!handler.is_null()); + + let cx = window.get_cx(); + let window_jsobject = window.reflector().get_jsobject(); + assert!(!window_jsobject.get().is_null()); + assert_ne!( + ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL), + 0 + ); + let _ac = JSAutoRealm::new(*cx, window_jsobject.get()); + + // Create a new window proxy. + rooted!(in(*cx) let js_proxy = NewWindowProxy(*cx, window_jsobject, handler)); + assert!(!js_proxy.is_null()); + + // Create a new browsing context. + let current = Some(window.global().pipeline_id()); + let window_proxy = Box::new(WindowProxy::new_inherited( + browsing_context_id, + top_level_browsing_context_id, + current, + frame_element, + parent, + opener, + creator, + )); + + // The window proxy owns the browsing context. + // When we finalize the window proxy, it drops the browsing context it owns. + SetProxyReservedSlot( + js_proxy.get(), + 0, + &PrivateValue((&*window_proxy).as_void_ptr()), + ); + + // Notify the JS engine about the new window proxy binding. + SetWindowProxy(*cx, window_jsobject, js_proxy.handle()); + + // Set the reflector. + debug!( + "Initializing reflector of {:p} to {:p}.", + window_proxy, + js_proxy.get() + ); + window_proxy.reflector.set_jsobject(js_proxy.get()); + DomRoot::from_ref(&*Box::into_raw(window_proxy)) + } + } + + #[allow(unsafe_code)] + pub fn new_dissimilar_origin( + global_to_clone_from: &GlobalScope, + browsing_context_id: BrowsingContextId, + top_level_browsing_context_id: TopLevelBrowsingContextId, + parent: Option<&WindowProxy>, + opener: Option<BrowsingContextId>, + creator: CreatorBrowsingContextInfo, + ) -> DomRoot<WindowProxy> { + unsafe { + let handler = CreateWrapperProxyHandler(&XORIGIN_PROXY_HANDLER); + assert!(!handler.is_null()); + + let cx = global_to_clone_from.get_cx(); + + // Create a new browsing context. + let window_proxy = Box::new(WindowProxy::new_inherited( + browsing_context_id, + top_level_browsing_context_id, + None, + None, + parent, + opener, + creator, + )); + + // Create a new dissimilar-origin window. + let window = DissimilarOriginWindow::new(global_to_clone_from, &*window_proxy); + let window_jsobject = window.reflector().get_jsobject(); + assert!(!window_jsobject.get().is_null()); + assert_ne!( + ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL), + 0 + ); + let _ac = JSAutoRealm::new(*cx, window_jsobject.get()); + + // Create a new window proxy. + rooted!(in(*cx) let js_proxy = NewWindowProxy(*cx, window_jsobject, handler)); + assert!(!js_proxy.is_null()); + + // The window proxy owns the browsing context. + // When we finalize the window proxy, it drops the browsing context it owns. + SetProxyReservedSlot( + js_proxy.get(), + 0, + &PrivateValue((&*window_proxy).as_void_ptr()), + ); + + // Notify the JS engine about the new window proxy binding. + SetWindowProxy(*cx, window_jsobject, js_proxy.handle()); + + // Set the reflector. + debug!( + "Initializing reflector of {:p} to {:p}.", + window_proxy, + js_proxy.get() + ); + window_proxy.reflector.set_jsobject(js_proxy.get()); + DomRoot::from_ref(&*Box::into_raw(window_proxy)) + } + } + + // https://html.spec.whatwg.org/multipage/#auxiliary-browsing-context + fn create_auxiliary_browsing_context( + &self, + name: DOMString, + noopener: bool, + ) -> Option<DomRoot<WindowProxy>> { + let (chan, port) = ipc::channel().unwrap(); + let window = self + .currently_active + .get() + .and_then(|id| ScriptThread::find_document(id)) + .and_then(|doc| Some(DomRoot::from_ref(doc.window()))) + .unwrap(); + let msg = EmbedderMsg::AllowOpeningBrowser(chan); + window.send_to_embedder(msg); + if port.recv().unwrap() { + let new_top_level_browsing_context_id = TopLevelBrowsingContextId::new(); + let new_browsing_context_id = + BrowsingContextId::from(new_top_level_browsing_context_id); + let new_pipeline_id = PipelineId::new(); + let document = self + .currently_active + .get() + .and_then(|id| ScriptThread::find_document(id)) + .expect("A WindowProxy creating an auxiliary to have an active document"); + + let blank_url = ServoUrl::parse("about:blank").ok().unwrap(); + let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + blank_url, + None, + document.global().get_referrer(), + document.get_referrer_policy(), + None, // Doesn't inherit secure context + ); + let load_info = AuxiliaryBrowsingContextLoadInfo { + load_data: load_data.clone(), + opener_pipeline_id: self.currently_active.get().unwrap(), + new_browsing_context_id: new_browsing_context_id, + new_top_level_browsing_context_id: new_top_level_browsing_context_id, + new_pipeline_id: new_pipeline_id, + }; + + let (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap(); + let new_layout_info = NewLayoutInfo { + parent_info: None, + new_pipeline_id: new_pipeline_id, + browsing_context_id: new_browsing_context_id, + top_level_browsing_context_id: new_top_level_browsing_context_id, + opener: Some(self.browsing_context_id), + load_data: load_data, + pipeline_port: pipeline_receiver, + window_size: window.window_size(), + }; + let constellation_msg = ScriptMsg::ScriptNewAuxiliary(load_info, pipeline_sender); + window.send_to_constellation(constellation_msg); + ScriptThread::process_attach_layout(new_layout_info, document.origin().clone()); + let msg = EmbedderMsg::BrowserCreated(new_top_level_browsing_context_id); + window.send_to_embedder(msg); + // TODO: if noopener is false, copy the sessionStorage storage area of the creator origin. + // See step 14 of https://html.spec.whatwg.org/multipage/#creating-a-new-browsing-context + let auxiliary = + ScriptThread::find_document(new_pipeline_id).and_then(|doc| doc.browsing_context()); + if let Some(proxy) = auxiliary { + if name.to_lowercase() != "_blank" { + proxy.set_name(name); + } + if noopener { + proxy.disown(); + } + return Some(proxy); + } + } + None + } + + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + pub fn is_delaying_load_events_mode(&self) -> bool { + self.delaying_load_events_mode.get() + } + + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + pub fn start_delaying_load_events_mode(&self) { + self.delaying_load_events_mode.set(true); + } + + /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode + pub fn stop_delaying_load_events_mode(&self) { + self.delaying_load_events_mode.set(false); + if let Some(document) = self.document() { + if !document.loader().events_inhibited() { + ScriptThread::mark_document_with_no_blocked_loads(&document); + } + } + } + + // https://html.spec.whatwg.org/multipage/#disowned-its-opener + pub fn disown(&self) { + self.disowned.set(true); + } + + /// https://html.spec.whatwg.org/multipage/#dom-window-close + /// Step 3.1, set BCs `is_closing` to true. + pub fn close(&self) { + self.is_closing.set(true); + } + + /// https://html.spec.whatwg.org/multipage/#is-closing + pub fn is_closing(&self) -> bool { + self.is_closing.get() + } + + /// https://html.spec.whatwg.org/multipage/#creator-base-url + pub fn creator_base_url(&self) -> Option<ServoUrl> { + self.creator_base_url.clone() + } + + pub fn has_creator_base_url(&self) -> bool { + self.creator_base_url.is_some() + } + + /// https://html.spec.whatwg.org/multipage/#creator-url + pub fn creator_url(&self) -> Option<ServoUrl> { + self.creator_url.clone() + } + + pub fn has_creator_url(&self) -> bool { + self.creator_base_url.is_some() + } + + /// https://html.spec.whatwg.org/multipage/#creator-origin + pub fn creator_origin(&self) -> Option<ImmutableOrigin> { + self.creator_origin.clone() + } + + pub fn has_creator_origin(&self) -> bool { + self.creator_origin.is_some() + } + + #[allow(unsafe_code)] + // https://html.spec.whatwg.org/multipage/#dom-opener + pub fn opener(&self, cx: *mut JSContext, in_realm_proof: InRealm) -> JSVal { + if self.disowned.get() { + return NullValue(); + } + let opener_id = match self.opener { + Some(opener_browsing_context_id) => opener_browsing_context_id, + None => return NullValue(), + }; + let parent_browsing_context = self.parent.as_deref(); + let opener_proxy = match ScriptThread::find_window_proxy(opener_id) { + Some(window_proxy) => window_proxy, + None => { + let sender_pipeline_id = self.currently_active().unwrap(); + match ScriptThread::get_top_level_for_browsing_context( + sender_pipeline_id, + opener_id, + ) { + Some(opener_top_id) => { + let global_to_clone_from = + unsafe { GlobalScope::from_context(cx, in_realm_proof) }; + let creator = + CreatorBrowsingContextInfo::from(parent_browsing_context, None); + WindowProxy::new_dissimilar_origin( + &*global_to_clone_from, + opener_id, + opener_top_id, + None, + None, + creator, + ) + }, + None => return NullValue(), + } + }, + }; + if opener_proxy.is_browsing_context_discarded() { + return NullValue(); + } + rooted!(in(cx) let mut val = UndefinedValue()); + unsafe { opener_proxy.to_jsval(cx, val.handle_mut()) }; + return val.get(); + } + + // https://html.spec.whatwg.org/multipage/#window-open-steps + pub fn open( + &self, + url: USVString, + target: DOMString, + features: DOMString, + ) -> Fallible<Option<DomRoot<WindowProxy>>> { + // Step 4. + let non_empty_target = match target.as_ref() { + "" => DOMString::from("_blank"), + _ => target, + }; + // Step 5 + let tokenized_features = tokenize_open_features(features); + // Step 7-9 + let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer"); + let noopener = if noreferrer { + true + } else { + parse_open_feature_boolean(&tokenized_features, "noopener") + }; + // Step 10, 11 + let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) { + (Some(chosen), new) => (chosen, new), + (None, _) => return Ok(None), + }; + // TODO Step 12, set up browsing context features. + let target_document = match chosen.document() { + Some(target_document) => target_document, + None => return Ok(None), + }; + let target_window = target_document.window(); + // Step 13, and 14.4, will have happened elsewhere, + // since we've created a new browsing context and loaded it with about:blank. + if !url.is_empty() { + let existing_document = self + .currently_active + .get() + .and_then(|id| ScriptThread::find_document(id)) + .unwrap(); + // Step 14.1 + let url = match existing_document.url().join(&url) { + Ok(url) => url, + Err(_) => return Err(Error::Syntax), + }; + // Step 14.3 + let referrer = if noreferrer { + Referrer::NoReferrer + } else { + target_window.upcast::<GlobalScope>().get_referrer() + }; + // Step 14.5 + let referrer_policy = target_document.get_referrer_policy(); + let pipeline_id = target_window.upcast::<GlobalScope>().pipeline_id(); + let secure = target_window.upcast::<GlobalScope>().is_secure_context(); + let load_data = LoadData::new( + LoadOrigin::Script(existing_document.origin().immutable().clone()), + url, + Some(pipeline_id), + referrer, + referrer_policy, + Some(secure), + ); + let replacement_flag = if new { + HistoryEntryReplacement::Enabled + } else { + HistoryEntryReplacement::Disabled + }; + target_window.load_url(replacement_flag, false, load_data); + } + if noopener { + // Step 15 (Dis-owning has been done in create_auxiliary_browsing_context). + return Ok(None); + } + // Step 17. + return Ok(target_document.browsing_context()); + } + + // https://html.spec.whatwg.org/multipage/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name + pub fn choose_browsing_context( + &self, + name: DOMString, + noopener: bool, + ) -> (Option<DomRoot<WindowProxy>>, bool) { + match name.to_lowercase().as_ref() { + "" | "_self" => { + // Step 3. + (Some(DomRoot::from_ref(self)), false) + }, + "_parent" => { + // Step 4 + if let Some(parent) = self.parent() { + return (Some(DomRoot::from_ref(parent)), false); + } + (None, false) + }, + "_top" => { + // Step 5 + (Some(DomRoot::from_ref(self.top())), false) + }, + "_blank" => (self.create_auxiliary_browsing_context(name, noopener), true), + _ => { + // Step 6. + // TODO: expand the search to all 'familiar' bc, + // including auxiliaries familiar by way of their opener. + // See https://html.spec.whatwg.org/multipage/#familiar-with + match ScriptThread::find_window_proxy_by_name(&name) { + Some(proxy) => (Some(proxy), false), + None => (self.create_auxiliary_browsing_context(name, noopener), true), + } + }, + } + } + + pub fn is_auxiliary(&self) -> bool { + self.opener.is_some() + } + + pub fn discard_browsing_context(&self) { + self.discarded.set(true); + } + + pub fn is_browsing_context_discarded(&self) -> bool { + self.discarded.get() + } + + pub fn browsing_context_id(&self) -> BrowsingContextId { + self.browsing_context_id + } + + pub fn top_level_browsing_context_id(&self) -> TopLevelBrowsingContextId { + self.top_level_browsing_context_id + } + + pub fn frame_element(&self) -> Option<&Element> { + self.frame_element.as_deref() + } + + pub fn document(&self) -> Option<DomRoot<Document>> { + self.currently_active + .get() + .and_then(|id| ScriptThread::find_document(id)) + } + + pub fn parent(&self) -> Option<&WindowProxy> { + self.parent.as_deref() + } + + pub fn top(&self) -> &WindowProxy { + let mut result = self; + while let Some(parent) = result.parent() { + result = parent; + } + result + } + + #[allow(unsafe_code)] + /// Change the Window that this WindowProxy resolves to. + // TODO: support setting the window proxy to a dummy value, + // to handle the case when the active document is in another script thread. + fn set_window(&self, window: &GlobalScope, traps: &ProxyTraps) { + unsafe { + debug!("Setting window of {:p}.", self); + let handler = CreateWrapperProxyHandler(traps); + assert!(!handler.is_null()); + + let cx = window.get_cx(); + let window_jsobject = window.reflector().get_jsobject(); + let old_js_proxy = self.reflector.get_jsobject(); + assert!(!window_jsobject.get().is_null()); + assert_ne!( + ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL), + 0 + ); + let _ac = enter_realm(&*window); + + // The old window proxy no longer owns this browsing context. + SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut())); + + // Brain transpant the window proxy. Brain transplantation is + // usually done to move a window proxy between compartments, but + // that's not what we are doing here. We need to do this just + // because we want to replace the wrapper's `ProxyTraps`, but we + // don't want to update its identity. + rooted!(in(*cx) let new_js_proxy = NewWindowProxy(*cx, window_jsobject, handler)); + debug!( + "Transplanting proxy from {:p} to {:p}.", + old_js_proxy.get(), + new_js_proxy.get() + ); + rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle())); + debug!("Transplanted proxy is {:p}.", new_js_proxy.get()); + + // Transfer ownership of this browsing context from the old window proxy to the new one. + SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(self.as_void_ptr())); + + // Notify the JS engine about the new window proxy binding. + SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle()); + + // Update the reflector. + debug!( + "Setting reflector of {:p} to {:p}.", + self, + new_js_proxy.get() + ); + self.reflector.rootable().set(new_js_proxy.get()); + } + } + + pub fn set_currently_active(&self, window: &Window) { + let globalscope = window.upcast::<GlobalScope>(); + let dest_pipeline_id = globalscope.pipeline_id(); + if let Some(pipeline_id) = self.currently_active() { + if pipeline_id == dest_pipeline_id { + return debug!( + "Attempt to set the currently active window to the currently active window." + ); + } + } + self.set_window(&*globalscope, &PROXY_HANDLER); + self.currently_active.set(Some(globalscope.pipeline_id())); + } + + pub fn unset_currently_active(&self) { + if self.currently_active().is_none() { + return debug!("Attempt to unset the currently active window on a windowproxy that does not have one."); + } + let globalscope = self.global(); + let window = DissimilarOriginWindow::new(&*globalscope, self); + self.set_window(&*window.upcast(), &XORIGIN_PROXY_HANDLER); + self.currently_active.set(None); + } + + pub fn currently_active(&self) -> Option<PipelineId> { + self.currently_active.get() + } + + pub fn get_name(&self) -> DOMString { + self.name.borrow().clone() + } + + pub fn set_name(&self, name: DOMString) { + *self.name.borrow_mut() = name; + } +} + +/// A browsing context can have a creator browsing context, the browsing context that +/// was responsible for its creation. If a browsing context has a parent browsing context, +/// then that is its creator browsing context. Otherwise, if the browsing context has an +/// opener browsing context, then that is its creator browsing context. Otherwise, the +/// browsing context has no creator browsing context. +/// +/// If a browsing context A has a creator browsing context, then the Document that was the +/// active document of that creator browsing context at the time A was created is the creator +/// Document. +/// +/// See: https://html.spec.whatwg.org/multipage/#creating-browsing-contexts +#[derive(Debug, Deserialize, Serialize)] +pub struct CreatorBrowsingContextInfo { + /// Creator document URL. + url: Option<ServoUrl>, + + /// Creator document base URL. + base_url: Option<ServoUrl>, + + /// Creator document origin. + origin: Option<ImmutableOrigin>, +} + +impl CreatorBrowsingContextInfo { + pub fn from( + parent: Option<&WindowProxy>, + opener: Option<&WindowProxy>, + ) -> CreatorBrowsingContextInfo { + let creator = match (parent, opener) { + (Some(parent), _) => parent.document(), + (None, Some(opener)) => opener.document(), + (None, None) => None, + }; + + let base_url = creator.as_deref().map(|document| document.base_url()); + let url = creator.as_deref().map(|document| document.url()); + let origin = creator + .as_deref() + .map(|document| document.origin().immutable().clone()); + + CreatorBrowsingContextInfo { + base_url, + url, + origin, + } + } +} + +// https://html.spec.whatwg.org/multipage/#concept-window-open-features-tokenize +fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> { + let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c); + // Step 1 + let mut tokenized_features = IndexMap::new(); + // Step 2 + let mut iter = features.chars(); + let mut cur = iter.next(); + + // Step 3 + while cur != None { + // Step 3.1 & 3.2 + let mut name = String::new(); + let mut value = String::new(); + // Step 3.3 + while let Some(cur_char) = cur { + if !is_feature_sep(cur_char) { + break; + } + cur = iter.next(); + } + // Step 3.4 + while let Some(cur_char) = cur { + if is_feature_sep(cur_char) { + break; + } + name.push(cur_char.to_ascii_lowercase()); + cur = iter.next(); + } + // Step 3.5 + let normalized_name = String::from(match name.as_ref() { + "screenx" => "left", + "screeny" => "top", + "innerwidth" => "width", + "innerheight" => "height", + _ => name.as_ref(), + }); + // Step 3.6 + while let Some(cur_char) = cur { + if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) { + break; + } + cur = iter.next(); + } + // Step 3.7 + if cur.is_some() && is_feature_sep(cur.unwrap()) { + // Step 3.7.1 + while let Some(cur_char) = cur { + if !is_feature_sep(cur_char) || cur_char == ',' { + break; + } + cur = iter.next(); + } + // Step 3.7.2 + while let Some(cur_char) = cur { + if is_feature_sep(cur_char) { + break; + } + value.push(cur_char.to_ascii_lowercase()); + cur = iter.next(); + } + } + // Step 3.8 + if !name.is_empty() { + tokenized_features.insert(normalized_name, value); + } + } + // Step 4 + tokenized_features +} + +// https://html.spec.whatwg.org/multipage/#concept-window-open-features-parse-boolean +fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool { + if let Some(value) = tokenized_features.get(name) { + // Step 1 & 2 + if value == "" || value == "yes" { + return true; + } + // Step 3 & 4 + if let Ok(int) = parse_integer(value.chars()) { + return int != 0; + } + } + // Step 5 + return false; +} + +// This is only called from extern functions, +// there's no use using the lifetimed handles here. +// https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts +#[allow(unsafe_code, non_snake_case)] +unsafe fn GetSubframeWindowProxy( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, +) -> Option<(DomRoot<WindowProxy>, u32)> { + let index = get_array_index_from_id(cx, Handle::from_raw(id)); + if let Some(index) = index { + let mut slot = UndefinedValue(); + GetProxyPrivate(*proxy, &mut slot); + rooted!(in(cx) let target = slot.to_object()); + if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) { + let browsing_context_id = win.window_proxy().browsing_context_id(); + let (result_sender, result_receiver) = ipc::channel().unwrap(); + + let _ = win + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(ScriptMsg::GetChildBrowsingContextId( + browsing_context_id, + index as usize, + result_sender, + )); + return result_receiver + .recv() + .ok() + .and_then(|maybe_bcid| maybe_bcid) + .and_then(ScriptThread::find_window_proxy) + .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32)); + } else if let Ok(win) = + root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx) + { + let browsing_context_id = win.window_proxy().browsing_context_id(); + let (result_sender, result_receiver) = ipc::channel().unwrap(); + + let _ = win.global().script_to_constellation_chan().send( + ScriptMsg::GetChildBrowsingContextId( + browsing_context_id, + index as usize, + result_sender, + ), + ); + return result_receiver + .recv() + .ok() + .and_then(|maybe_bcid| maybe_bcid) + .and_then(ScriptThread::find_window_proxy) + .map(|proxy| (proxy, JSPROP_READONLY as u32)); + } + } + + None +} + +#[allow(unsafe_code, non_snake_case)] +unsafe extern "C" fn getOwnPropertyDescriptor( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + mut desc: RawMutableHandle<PropertyDescriptor>, +) -> bool { + let window = GetSubframeWindowProxy(cx, proxy, id); + if let Some((window, attrs)) = window { + rooted!(in(cx) let mut val = UndefinedValue()); + window.to_jsval(cx, val.handle_mut()); + desc.value = val.get(); + fill_property_descriptor(MutableHandle::from_raw(desc), proxy.get(), attrs); + return true; + } + + let mut slot = UndefinedValue(); + GetProxyPrivate(proxy.get(), &mut slot); + rooted!(in(cx) let target = slot.to_object()); + if !JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc) { + return false; + } + + assert!(desc.obj.is_null() || desc.obj == target.get()); + if desc.obj == target.get() { + desc.obj = proxy.get(); + } + + true +} + +#[allow(unsafe_code, non_snake_case)] +unsafe extern "C" fn defineProperty( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + desc: RawHandle<PropertyDescriptor>, + res: *mut ObjectOpResult, +) -> bool { + if get_array_index_from_id(cx, Handle::from_raw(id)).is_some() { + // Spec says to Reject whether this is a supported index or not, + // since we have no indexed setter or indexed creator. That means + // throwing in strict mode (FIXME: Bug 828137), doing nothing in + // non-strict mode. + (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t; + return true; + } + + let mut slot = UndefinedValue(); + GetProxyPrivate(*proxy.ptr, &mut slot); + rooted!(in(cx) let target = slot.to_object()); + JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) +} + +#[allow(unsafe_code)] +unsafe extern "C" fn has( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + bp: *mut bool, +) -> bool { + let window = GetSubframeWindowProxy(cx, proxy, id); + if window.is_some() { + *bp = true; + return true; + } + + let mut slot = UndefinedValue(); + GetProxyPrivate(*proxy.ptr, &mut slot); + rooted!(in(cx) let target = slot.to_object()); + let mut found = false; + if !JS_HasPropertyById(cx, target.handle().into(), id, &mut found) { + return false; + } + + *bp = found; + true +} + +#[allow(unsafe_code)] +unsafe extern "C" fn get( + cx: *mut JSContext, + proxy: RawHandleObject, + receiver: RawHandleValue, + id: RawHandleId, + vp: RawMutableHandleValue, +) -> bool { + let window = GetSubframeWindowProxy(cx, proxy, id); + if let Some((window, _attrs)) = window { + window.to_jsval(cx, MutableHandle::from_raw(vp)); + return true; + } + + let mut slot = UndefinedValue(); + GetProxyPrivate(*proxy.ptr, &mut slot); + rooted!(in(cx) let target = slot.to_object()); + JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) +} + +#[allow(unsafe_code)] +unsafe extern "C" fn set( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + v: RawHandleValue, + receiver: RawHandleValue, + res: *mut ObjectOpResult, +) -> bool { + if get_array_index_from_id(cx, Handle::from_raw(id)).is_some() { + // Reject (which means throw if and only if strict) the set. + (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t; + return true; + } + + let mut slot = UndefinedValue(); + GetProxyPrivate(*proxy.ptr, &mut slot); + rooted!(in(cx) let target = slot.to_object()); + JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) +} + +#[allow(unsafe_code)] +unsafe extern "C" fn get_prototype_if_ordinary( + _: *mut JSContext, + _: RawHandleObject, + is_ordinary: *mut bool, + _: RawMutableHandleObject, +) -> bool { + // Window's [[GetPrototypeOf]] trap isn't the ordinary definition: + // + // https://html.spec.whatwg.org/multipage/#windowproxy-getprototypeof + // + // We nonetheless can implement it with a static [[Prototype]], because + // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply + // all non-ordinary behavior. + // + // But from a spec point of view, it's the exact same object in both cases -- + // only the observer's changed. So this getPrototypeIfOrdinary trap on the + // non-wrapper object *must* report non-ordinary, even if static [[Prototype]] + // usually means ordinary. + *is_ordinary = false; + return true; +} + +static PROXY_HANDLER: ProxyTraps = ProxyTraps { + enter: None, + getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor), + defineProperty: Some(defineProperty), + ownPropertyKeys: None, + delete_: None, + enumerate: None, + getPrototypeIfOrdinary: Some(get_prototype_if_ordinary), + preventExtensions: None, + isExtensible: None, + has: Some(has), + get: Some(get), + set: Some(set), + call: None, + construct: None, + hasOwn: None, + getOwnEnumerablePropertyKeys: None, + nativeCall: None, + hasInstance: None, + objectClassIs: None, + className: None, + fun_toString: None, + boxedValue_unbox: None, + defaultValue: None, + trace: Some(trace), + finalize: Some(finalize), + objectMoved: None, + isCallable: None, + isConstructor: None, +}; + +#[allow(unsafe_code)] +pub fn new_window_proxy_handler() -> WindowProxyHandler { + unsafe { WindowProxyHandler(CreateWrapperProxyHandler(&PROXY_HANDLER)) } +} + +// The proxy traps for cross-origin windows. +// These traps often throw security errors, and only pass on calls to methods +// defined in the DissimilarOriginWindow IDL. + +#[allow(unsafe_code)] +unsafe fn throw_security_error(cx: *mut JSContext, realm: InRealm) -> bool { + if !JS_IsExceptionPending(cx) { + let safe_context = SafeJSContext::from_ptr(cx); + let global = GlobalScope::from_context(cx, realm); + throw_dom_exception(safe_context, &*global, Error::Security); + } + false +} + +#[allow(unsafe_code)] +unsafe extern "C" fn has_xorigin( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + bp: *mut bool, +) -> bool { + let mut slot = UndefinedValue(); + GetProxyPrivate(*proxy.ptr, &mut slot); + rooted!(in(cx) let target = slot.to_object()); + let mut found = false; + JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found); + if found { + *bp = true; + true + } else { + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + throw_security_error(cx, InRealm::Already(&in_realm_proof)) + } +} + +#[allow(unsafe_code)] +unsafe extern "C" fn get_xorigin( + cx: *mut JSContext, + proxy: RawHandleObject, + receiver: RawHandleValue, + id: RawHandleId, + vp: RawMutableHandleValue, +) -> bool { + let mut found = false; + has_xorigin(cx, proxy, id, &mut found); + found && get(cx, proxy, receiver, id, vp) +} + +#[allow(unsafe_code)] +unsafe extern "C" fn set_xorigin( + cx: *mut JSContext, + _: RawHandleObject, + _: RawHandleId, + _: RawHandleValue, + _: RawHandleValue, + _: *mut ObjectOpResult, +) -> bool { + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + throw_security_error(cx, InRealm::Already(&in_realm_proof)) +} + +#[allow(unsafe_code)] +unsafe extern "C" fn delete_xorigin( + cx: *mut JSContext, + _: RawHandleObject, + _: RawHandleId, + _: *mut ObjectOpResult, +) -> bool { + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + throw_security_error(cx, InRealm::Already(&in_realm_proof)) +} + +#[allow(unsafe_code, non_snake_case)] +unsafe extern "C" fn getOwnPropertyDescriptor_xorigin( + cx: *mut JSContext, + proxy: RawHandleObject, + id: RawHandleId, + desc: RawMutableHandle<PropertyDescriptor>, +) -> bool { + let mut found = false; + has_xorigin(cx, proxy, id, &mut found); + found && getOwnPropertyDescriptor(cx, proxy, id, desc) +} + +#[allow(unsafe_code, non_snake_case)] +unsafe extern "C" fn defineProperty_xorigin( + cx: *mut JSContext, + _: RawHandleObject, + _: RawHandleId, + _: RawHandle<PropertyDescriptor>, + _: *mut ObjectOpResult, +) -> bool { + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + throw_security_error(cx, InRealm::Already(&in_realm_proof)) +} + +#[allow(unsafe_code, non_snake_case)] +unsafe extern "C" fn preventExtensions_xorigin( + cx: *mut JSContext, + _: RawHandleObject, + _: *mut ObjectOpResult, +) -> bool { + let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx)); + throw_security_error(cx, InRealm::Already(&in_realm_proof)) +} + +static XORIGIN_PROXY_HANDLER: ProxyTraps = ProxyTraps { + enter: None, + getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin), + defineProperty: Some(defineProperty_xorigin), + ownPropertyKeys: None, + delete_: Some(delete_xorigin), + enumerate: None, + getPrototypeIfOrdinary: None, + preventExtensions: Some(preventExtensions_xorigin), + isExtensible: None, + has: Some(has_xorigin), + get: Some(get_xorigin), + set: Some(set_xorigin), + call: None, + construct: None, + hasOwn: Some(has_xorigin), + getOwnEnumerablePropertyKeys: None, + nativeCall: None, + hasInstance: None, + objectClassIs: None, + className: None, + fun_toString: None, + boxedValue_unbox: None, + defaultValue: None, + trace: Some(trace), + finalize: Some(finalize), + objectMoved: None, + isCallable: None, + isConstructor: None, +}; + +// How WindowProxy objects are garbage collected. + +#[allow(unsafe_code)] +unsafe extern "C" fn finalize(_fop: *mut JSFreeOp, obj: *mut JSObject) { + let mut slot = UndefinedValue(); + GetProxyReservedSlot(obj, 0, &mut slot); + let this = slot.to_private() as *mut WindowProxy; + if this.is_null() { + // GC during obj creation or after transplanting. + return; + } + let jsobject = (*this).reflector.get_jsobject().get(); + debug!( + "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.", + this, jsobject, obj + ); + let _ = Box::from_raw(this); +} + +#[allow(unsafe_code)] +unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) { + let mut slot = UndefinedValue(); + GetProxyReservedSlot(obj, 0, &mut slot); + let this = slot.to_private() as *const WindowProxy; + if this.is_null() { + // GC during obj creation or after transplanting. + return; + } + (*this).trace(trc); +} diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs index 1fb354189e3..563a04679ea 100644 --- a/components/script/dom/worker.rs +++ b/components/script/dom/worker.rs @@ -1,37 +1,42 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg}; -use dom::abstractworker::{SharedRt, SimpleWorkerErrorHandler}; -use dom::abstractworker::WorkerScriptMsg; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::WorkerBinding; -use dom::bindings::codegen::Bindings::WorkerBinding::WorkerMethods; -use dom::bindings::error::{Error, ErrorResult, Fallible, ErrorInfo}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::bindings::structuredclone::StructuredCloneData; -use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; -use dom::errorevent::ErrorEvent; -use dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::messageevent::MessageEvent; -use dom::workerglobalscope::prepare_workerscope_init; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::abstractworker::SimpleWorkerErrorHandler; +use crate::dom::abstractworker::WorkerScriptMsg; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::PostMessageOptions; +use crate::dom::bindings::codegen::Bindings::WorkerBinding::{WorkerMethods, WorkerOptions}; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::dedicatedworkerglobalscope::{ + DedicatedWorkerGlobalScope, DedicatedWorkerScriptMsg, +}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageevent::MessageEvent; +use crate::dom::window::Window; +use crate::dom::workerglobalscope::prepare_workerscope_init; +use crate::realms::enter_realm; +use crate::script_runtime::JSContext; +use crate::task::TaskOnce; +use crossbeam_channel::{unbounded, Sender}; +use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, WorkerId}; use dom_struct::dom_struct; use ipc_channel::ipc; -use js::jsapi::{HandleValue, JSAutoCompartment, JSContext, NullHandleValue}; +use js::jsapi::{Heap, JSObject, JS_RequestInterruptCallback}; use js::jsval::UndefinedValue; -use script_thread::Runnable; -use script_traits::WorkerScriptLoadOrigin; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; +use script_traits::{StructuredSerializedData, WorkerScriptLoadOrigin}; use std::cell::Cell; -use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::{Sender, channel}; +use std::sync::Arc; +use uuid::Uuid; pub type TrustedWorkerAddress = Trusted<Worker>; @@ -39,46 +44,47 @@ pub type TrustedWorkerAddress = Trusted<Worker>; #[dom_struct] pub struct Worker { eventtarget: EventTarget, - #[ignore_heap_size_of = "Defined in std"] + #[ignore_malloc_size_of = "Defined in std"] /// Sender to the Receiver associated with the DedicatedWorkerGlobalScope /// this Worker created. - sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, + sender: Sender<DedicatedWorkerScriptMsg>, + #[ignore_malloc_size_of = "Arc"] closing: Arc<AtomicBool>, - #[ignore_heap_size_of = "Defined in rust-mozjs"] - runtime: Arc<Mutex<Option<SharedRt>>>, terminated: Cell<bool>, } impl Worker { - fn new_inherited(sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, - closing: Arc<AtomicBool>) -> Worker { + fn new_inherited(sender: Sender<DedicatedWorkerScriptMsg>, closing: Arc<AtomicBool>) -> Worker { Worker { eventtarget: EventTarget::new_inherited(), sender: sender, closing: closing, - runtime: Arc::new(Mutex::new(None)), terminated: Cell::new(false), } } - pub fn new(global: &GlobalScope, - sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>, - closing: Arc<AtomicBool>) -> Root<Worker> { - reflect_dom_object(box Worker::new_inherited(sender, closing), - global, - WorkerBinding::Wrap) + pub fn new( + global: &GlobalScope, + sender: Sender<DedicatedWorkerScriptMsg>, + closing: Arc<AtomicBool>, + ) -> DomRoot<Worker> { + reflect_dom_object(Box::new(Worker::new_inherited(sender, closing)), global) } // https://html.spec.whatwg.org/multipage/#dom-worker - #[allow(unsafe_code)] - pub fn Constructor(global: &GlobalScope, script_url: DOMString) -> Fallible<Root<Worker>> { + #[allow(unsafe_code, non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + script_url: USVString, + worker_options: &WorkerOptions, + ) -> Fallible<DomRoot<Worker>> { // Step 2-4. let worker_url = match global.api_base_url().join(&script_url) { Ok(url) => url, Err(_) => return Err(Error::Syntax), }; - let (sender, receiver) = channel(); + let (sender, receiver) = unbounded(); let closing = Arc::new(AtomicBool::new(false)); let worker = Worker::new(global, sender.clone(), closing.clone()); let worker_ref = Trusted::new(&*worker); @@ -86,42 +92,74 @@ impl Worker { let worker_load_origin = WorkerScriptLoadOrigin { referrer_url: None, referrer_policy: None, - pipeline_id: Some(global.pipeline_id()), + pipeline_id: global.pipeline_id(), }; + let browsing_context = global + .downcast::<Window>() + .map(|w| w.window_proxy().browsing_context_id()) + .or_else(|| { + global + .downcast::<DedicatedWorkerGlobalScope>() + .and_then(|w| w.browsing_context()) + }); + let (devtools_sender, devtools_receiver) = ipc::channel().unwrap(); - let worker_id = global.get_next_worker_id(); + let worker_id = WorkerId(Uuid::new_v4()); if let Some(ref chan) = global.devtools_chan() { let pipeline_id = global.pipeline_id(); - let title = format!("Worker for {}", worker_url); + let title = format!("Worker for {}", worker_url); + if let Some(browsing_context) = browsing_context { let page_info = DevtoolsPageInfo { title: title, url: worker_url.clone(), }; - let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal((pipeline_id, Some(worker_id)), - devtools_sender.clone(), - page_info)); + let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal( + (browsing_context, pipeline_id, Some(worker_id)), + devtools_sender.clone(), + page_info, + )); + } } - let init = prepare_workerscope_init(global, Some(devtools_sender)); - - DedicatedWorkerGlobalScope::run_worker_scope( - init, worker_url, devtools_receiver, worker.runtime.clone(), worker_ref, - global.script_chan(), sender, receiver, worker_load_origin, closing); + let init = prepare_workerscope_init(global, Some(devtools_sender), Some(worker_id)); + + let (control_sender, control_receiver) = unbounded(); + let (context_sender, context_receiver) = unbounded(); + + let join_handle = DedicatedWorkerGlobalScope::run_worker_scope( + init, + worker_url, + devtools_receiver, + worker_ref, + global.script_chan(), + sender, + receiver, + worker_load_origin, + String::from(&*worker_options.name), + worker_options.type_, + closing.clone(), + global.image_cache(), + browsing_context, + global.wgpu_id_hub(), + control_receiver, + context_sender, + ); + + let context = context_receiver + .recv() + .expect("Couldn't receive a context for worker."); + + global.track_worker(closing, join_handle, control_sender, context); Ok(worker) } - pub fn is_closing(&self) -> bool { - self.closing.load(Ordering::SeqCst) - } - pub fn is_terminated(&self) -> bool { self.terminated.get() } - pub fn handle_message(address: TrustedWorkerAddress, - data: StructuredCloneData) { + pub fn handle_message(address: TrustedWorkerAddress, data: StructuredSerializedData) { let worker = address.root(); if worker.is_terminated() { @@ -130,10 +168,14 @@ impl Worker { let global = worker.global(); let target = worker.upcast(); - let _ac = JSAutoCompartment::new(global.get_cx(), target.reflector().get_jsobject().get()); - rooted!(in(global.get_cx()) let mut message = UndefinedValue()); - data.read(&global, message.handle_mut()); - MessageEvent::dispatch_jsval(target, &global, message.handle()); + let _ac = enter_realm(target); + rooted!(in(*global.get_cx()) let mut message = UndefinedValue()); + if let Ok(ports) = structuredclone::read(&global, data, message.handle_mut()) { + MessageEvent::dispatch_jsval(target, &global, message.handle(), None, None, ports); + } else { + // Step 4 of the "port post message steps" of the implicit messageport, fire messageerror. + MessageEvent::dispatch_error(target, &global); + } } pub fn dispatch_simple_error(address: TrustedWorkerAddress) { @@ -141,41 +183,59 @@ impl Worker { worker.upcast().fire_event(atom!("error")); } - #[allow(unsafe_code)] - fn dispatch_error(&self, error_info: ErrorInfo) { - let global = self.global(); - let event = ErrorEvent::new(&global, - atom!("error"), - EventBubbles::DoesNotBubble, - EventCancelable::Cancelable, - error_info.message.as_str().into(), - error_info.filename.as_str().into(), - error_info.lineno, - error_info.column, - unsafe { NullHandleValue }); - - let event_status = event.upcast::<Event>().fire(self.upcast::<EventTarget>()); - if event_status == EventStatus::Canceled { - return; - } + /// https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage + fn post_message_impl( + &self, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + let data = structuredclone::write(cx, message, Some(transfer))?; + let address = Trusted::new(self); - global.report_an_error(error_info, unsafe { NullHandleValue }); + // NOTE: step 9 of https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage + // indicates that a nonexistent communication channel should result in a silent error. + let _ = self.sender.send(DedicatedWorkerScriptMsg::CommonWorker( + address, + WorkerScriptMsg::DOMMessage { + origin: self.global().origin().immutable().clone(), + data, + }, + )); + Ok(()) } } impl WorkerMethods for Worker { - #[allow(unsafe_code)] - // https://html.spec.whatwg.org/multipage/#dom-worker-postmessage - unsafe fn PostMessage(&self, cx: *mut JSContext, message: HandleValue) -> ErrorResult { - let data = try!(StructuredCloneData::write(cx, message)); - let address = Trusted::new(self); + /// https://html.spec.whatwg.org/multipage/#dom-worker-postmessage + fn PostMessage( + &self, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + self.post_message_impl(cx, message, transfer) + } - // NOTE: step 9 of https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage - // indicates that a nonexistent communication channel should result in a silent error. - let _ = self.sender.send((address, WorkerScriptMsg::DOMMessage(data))); - Ok(()) + /// https://html.spec.whatwg.org/multipage/#dom-worker-postmessage + fn PostMessage_( + &self, + cx: JSContext, + message: HandleValue, + options: RootedTraceableBox<PostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .transfer + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); + self.post_message_impl(cx, message, guard) } + #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#terminate-a-worker fn Terminate(&self) { // Step 1 @@ -187,64 +247,23 @@ impl WorkerMethods for Worker { self.terminated.set(true); // Step 3 - if let Some(runtime) = *self.runtime.lock().unwrap() { - runtime.request_interrupt(); - } + let cx = self.global().get_cx(); + unsafe { JS_RequestInterruptCallback(*cx) }; } // https://html.spec.whatwg.org/multipage/#handler-worker-onmessage event_handler!(message, GetOnmessage, SetOnmessage); + // https://html.spec.whatwg.org/multipage/#handler-worker-onmessageerror + event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); + // https://html.spec.whatwg.org/multipage/#handler-workerglobalscope-onerror event_handler!(error, GetOnerror, SetOnerror); } -pub struct WorkerMessageHandler { - addr: TrustedWorkerAddress, - data: StructuredCloneData, -} - -impl WorkerMessageHandler { - pub fn new(addr: TrustedWorkerAddress, data: StructuredCloneData) -> WorkerMessageHandler { - WorkerMessageHandler { - addr: addr, - data: data, - } - } -} - -impl Runnable for WorkerMessageHandler { - fn handler(self: Box<WorkerMessageHandler>) { - let this = *self; - Worker::handle_message(this.addr, this.data); - } -} - -impl Runnable for SimpleWorkerErrorHandler<Worker> { +impl TaskOnce for SimpleWorkerErrorHandler<Worker> { #[allow(unrooted_must_root)] - fn handler(self: Box<SimpleWorkerErrorHandler<Worker>>) { - let this = *self; - Worker::dispatch_simple_error(this.addr); - } -} - -pub struct WorkerErrorHandler { - address: Trusted<Worker>, - error_info: ErrorInfo, -} - -impl WorkerErrorHandler { - pub fn new(address: Trusted<Worker>, error_info: ErrorInfo) -> WorkerErrorHandler { - WorkerErrorHandler { - address: address, - error_info: error_info, - } - } -} - -impl Runnable for WorkerErrorHandler { - fn handler(self: Box<Self>) { - let this = *self; - this.address.root().dispatch_error(this.error_info); + fn run_once(self) { + Worker::dispatch_simple_error(self.addr); } } diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index fd1f03db1e3..6092b3c4c3e 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -1,66 +1,90 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{ + ImageBitmapOptions, ImageBitmapSource, +}; +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit; +use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; +use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType; +use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods; +use crate::dom::bindings::codegen::UnionTypes::{RequestOrUSVString, StringOrFunction}; +use crate::dom::bindings::error::{report_pending_exception, Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::settings_stack::AutoEntryScript; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::crypto::Crypto; +use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; +use crate::dom::globalscope::GlobalScope; +use crate::dom::identityhub::Identities; +use crate::dom::performance::Performance; +use crate::dom::promise::Promise; +use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope; +use crate::dom::window::{base64_atob, base64_btoa}; +use crate::dom::workerlocation::WorkerLocation; +use crate::dom::workernavigator::WorkerNavigator; +use crate::fetch; +use crate::realms::{enter_realm, InRealm}; +use crate::script_runtime::JSContext; +use crate::script_runtime::{get_reports, CommonScriptMsg, Runtime, ScriptChan, ScriptPort}; +use crate::task::TaskCanceller; +use crate::task_source::dom_manipulation::DOMManipulationTaskSource; +use crate::task_source::file_reading::FileReadingTaskSource; +use crate::task_source::networking::NetworkingTaskSource; +use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; +use crate::task_source::port_message::PortMessageQueue; +use crate::task_source::remote_event::RemoteEventTaskSource; +use crate::task_source::timer::TimerTaskSource; +use crate::task_source::websocket::WebsocketTaskSource; +use crate::timers::{IsInterval, TimerCallback}; +use crossbeam_channel::Receiver; use devtools_traits::{DevtoolScriptControlMsg, WorkerId}; -use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; -use dom::bindings::codegen::Bindings::FunctionBinding::Function; -use dom::bindings::codegen::Bindings::RequestBinding::RequestInit; -use dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods; -use dom::bindings::codegen::UnionTypes::RequestOrUSVString; -use dom::bindings::error::{Error, ErrorResult, Fallible, report_pending_exception}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::DomObject; -use dom::bindings::settings_stack::AutoEntryScript; -use dom::bindings::str::DOMString; -use dom::bindings::trace::RootedTraceableBox; -use dom::crypto::Crypto; -use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; -use dom::globalscope::GlobalScope; -use dom::promise::Promise; -use dom::serviceworkerglobalscope::ServiceWorkerGlobalScope; -use dom::window::{base64_atob, base64_btoa}; -use dom::workerlocation::WorkerLocation; -use dom::workernavigator::WorkerNavigator; use dom_struct::dom_struct; -use fetch; use ipc_channel::ipc::IpcSender; -use js::jsapi::{HandleValue, JSAutoCompartment, JSContext, JSRuntime}; use js::jsval::UndefinedValue; use js::panic::maybe_resume_unwind; -use js::rust::Runtime; -use microtask::{MicrotaskQueue, Microtask}; -use net_traits::{IpcSend, load_whole_resource}; -use net_traits::request::{CredentialsMode, Destination, RequestInit as NetRequestInit, Type as RequestType}; -use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort}; -use script_thread::RunnableWrapper; -use script_traits::{TimerEvent, TimerEventId}; +use js::rust::{HandleValue, ParentRuntime}; +use msg::constellation_msg::{PipelineId, PipelineNamespace}; +use net_traits::request::{ + CredentialsMode, Destination, ParserMetadata, RequestBuilder as NetRequestInit, +}; +use net_traits::IpcSend; +use parking_lot::Mutex; use script_traits::WorkerGlobalScopeInit; use servo_url::{MutableOrigin, ServoUrl}; use std::default::Default; use std::rc::Rc; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::Receiver; -use task_source::file_reading::FileReadingTaskSource; -use task_source::networking::NetworkingTaskSource; -use timers::{IsInterval, TimerCallback}; - -pub fn prepare_workerscope_init(global: &GlobalScope, - devtools_sender: Option<IpcSender<DevtoolScriptControlMsg>>) -> WorkerGlobalScopeInit { +use std::sync::Arc; +use time::precise_time_ns; +use uuid::Uuid; + +pub fn prepare_workerscope_init( + global: &GlobalScope, + devtools_sender: Option<IpcSender<DevtoolScriptControlMsg>>, + worker_id: Option<WorkerId>, +) -> WorkerGlobalScopeInit { let init = WorkerGlobalScopeInit { - resource_threads: global.resource_threads().clone(), - mem_profiler_chan: global.mem_profiler_chan().clone(), - to_devtools_sender: global.devtools_chan().cloned(), - time_profiler_chan: global.time_profiler_chan().clone(), - from_devtools_sender: devtools_sender, - constellation_chan: global.constellation_chan().clone(), - scheduler_chan: global.scheduler_chan().clone(), - worker_id: global.get_next_worker_id(), - pipeline_id: global.pipeline_id(), - origin: global.origin().immutable().clone(), - }; + resource_threads: global.resource_threads().clone(), + mem_profiler_chan: global.mem_profiler_chan().clone(), + to_devtools_sender: global.devtools_chan().cloned(), + time_profiler_chan: global.time_profiler_chan().clone(), + from_devtools_sender: devtools_sender, + script_to_constellation_chan: global.script_to_constellation_chan().clone(), + scheduler_chan: global.scheduler_chan().clone(), + worker_id: worker_id.unwrap_or_else(|| WorkerId(Uuid::new_v4())), + pipeline_id: global.pipeline_id(), + origin: global.origin().immutable().clone(), + creation_url: global.creation_url().clone(), + is_headless: global.is_headless(), + user_agent: global.get_user_agent(), + inherited_secure_context: Some(global.is_secure_context()), + }; init } @@ -70,60 +94,95 @@ pub fn prepare_workerscope_init(global: &GlobalScope, pub struct WorkerGlobalScope { globalscope: GlobalScope, + worker_name: DOMString, + worker_type: WorkerType, + worker_id: WorkerId, - worker_url: ServoUrl, - #[ignore_heap_size_of = "Arc"] - closing: Option<Arc<AtomicBool>>, - #[ignore_heap_size_of = "Defined in js"] - runtime: Runtime, - location: MutNullableJS<WorkerLocation>, - navigator: MutNullableJS<WorkerNavigator>, - - #[ignore_heap_size_of = "Defined in ipc-channel"] + worker_url: DomRefCell<ServoUrl>, + #[ignore_malloc_size_of = "Arc"] + closing: Arc<AtomicBool>, + #[ignore_malloc_size_of = "Defined in js"] + runtime: DomRefCell<Option<Runtime>>, + location: MutNullableDom<WorkerLocation>, + navigator: MutNullableDom<WorkerNavigator>, + + #[ignore_malloc_size_of = "Defined in ipc-channel"] /// Optional `IpcSender` for sending the `DevtoolScriptControlMsg` /// to the server from within the worker from_devtools_sender: Option<IpcSender<DevtoolScriptControlMsg>>, - #[ignore_heap_size_of = "Defined in std"] + #[ignore_malloc_size_of = "Defined in std"] /// This `Receiver` will be ignored later if the corresponding /// `IpcSender` doesn't exist from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, - microtask_queue: MicrotaskQueue, + navigation_start_precise: u64, + performance: MutNullableDom<Performance>, } impl WorkerGlobalScope { - pub fn new_inherited(init: WorkerGlobalScopeInit, - worker_url: ServoUrl, - runtime: Runtime, - from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, - timer_event_chan: IpcSender<TimerEvent>, - closing: Option<Arc<AtomicBool>>) - -> WorkerGlobalScope { - WorkerGlobalScope { - globalscope: - GlobalScope::new_inherited( - init.pipeline_id, - init.to_devtools_sender, - init.mem_profiler_chan, - init.time_profiler_chan, - init.constellation_chan, - init.scheduler_chan, - init.resource_threads, - timer_event_chan, - MutableOrigin::new(init.origin)), + pub fn new_inherited( + init: WorkerGlobalScopeInit, + worker_name: DOMString, + worker_type: WorkerType, + worker_url: ServoUrl, + runtime: Runtime, + from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, + closing: Arc<AtomicBool>, + gpu_id_hub: Arc<Mutex<Identities>>, + ) -> Self { + // Install a pipeline-namespace in the current thread. + PipelineNamespace::auto_install(); + Self { + globalscope: GlobalScope::new_inherited( + init.pipeline_id, + init.to_devtools_sender, + init.mem_profiler_chan, + init.time_profiler_chan, + init.script_to_constellation_chan, + init.scheduler_chan, + init.resource_threads, + MutableOrigin::new(init.origin), + init.creation_url, + runtime.microtask_queue.clone(), + init.is_headless, + init.user_agent, + gpu_id_hub, + init.inherited_secure_context, + ), worker_id: init.worker_id, - worker_url: worker_url, - closing: closing, - runtime: runtime, + worker_name, + worker_type, + worker_url: DomRefCell::new(worker_url), + closing, + runtime: DomRefCell::new(Some(runtime)), location: Default::default(), navigator: Default::default(), from_devtools_sender: init.from_devtools_sender, - from_devtools_receiver: from_devtools_receiver, - microtask_queue: MicrotaskQueue::default(), + from_devtools_receiver, + navigation_start_precise: precise_time_ns(), + performance: Default::default(), } } + /// Clear various items when the worker event-loop shuts-down. + pub fn clear_js_runtime(&self) { + self.upcast::<GlobalScope>() + .remove_web_messaging_and_dedicated_workers_infra(); + + // Drop the runtime. + let runtime = self.runtime.borrow_mut().take(); + drop(runtime); + } + + pub fn runtime_handle(&self) -> ParentRuntime { + self.runtime + .borrow() + .as_ref() + .unwrap() + .prepare_for_new_child() + } + pub fn from_devtools_sender(&self) -> Option<IpcSender<DevtoolScriptControlMsg>> { self.from_devtools_sender.clone() } @@ -132,60 +191,48 @@ impl WorkerGlobalScope { &self.from_devtools_receiver } - pub fn runtime(&self) -> *mut JSRuntime { - self.runtime.rt() + #[allow(unsafe_code)] + pub fn get_cx(&self) -> JSContext { + unsafe { JSContext::from_ptr(self.runtime.borrow().as_ref().unwrap().cx()) } } - pub fn get_cx(&self) -> *mut JSContext { - self.runtime.cx() + pub fn is_closing(&self) -> bool { + self.closing.load(Ordering::SeqCst) } - pub fn is_closing(&self) -> bool { - if let Some(ref closing) = self.closing { - closing.load(Ordering::SeqCst) - } else { - false - } + pub fn get_url(&self) -> Ref<ServoUrl> { + self.worker_url.borrow() } - pub fn get_url(&self) -> &ServoUrl { - &self.worker_url + pub fn set_url(&self, url: ServoUrl) { + *self.worker_url.borrow_mut() = url; } pub fn get_worker_id(&self) -> WorkerId { self.worker_id.clone() } - pub fn get_runnable_wrapper(&self) -> RunnableWrapper { - RunnableWrapper { + pub fn task_canceller(&self) -> TaskCanceller { + TaskCanceller { cancelled: self.closing.clone(), } } - pub fn enqueue_microtask(&self, job: Microtask) { - self.microtask_queue.enqueue(job); - } - - pub fn perform_a_microtask_checkpoint(&self) { - self.microtask_queue.checkpoint(|id| { - let global = self.upcast::<GlobalScope>(); - assert_eq!(global.pipeline_id(), id); - Some(Root::from_ref(global)) - }); + pub fn pipeline_id(&self) -> PipelineId { + self.globalscope.pipeline_id() } } impl WorkerGlobalScopeMethods for WorkerGlobalScope { // https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-self - fn Self_(&self) -> Root<WorkerGlobalScope> { - Root::from_ref(self) + fn Self_(&self) -> DomRoot<WorkerGlobalScope> { + DomRoot::from_ref(self) } // https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-location - fn Location(&self) -> Root<WorkerLocation> { - self.location.or_init(|| { - WorkerLocation::new(self, self.worker_url.clone()) - }) + fn Location(&self) -> DomRoot<WorkerLocation> { + self.location + .or_init(|| WorkerLocation::new(self, self.worker_url.borrow().clone())) } // https://html.spec.whatwg.org/multipage/#handler-workerglobalscope-onerror @@ -195,38 +242,41 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { fn ImportScripts(&self, url_strings: Vec<DOMString>) -> ErrorResult { let mut urls = Vec::with_capacity(url_strings.len()); for url in url_strings { - let url = self.worker_url.join(&url); + let url = self.worker_url.borrow().join(&url); match url { Ok(url) => urls.push(url), Err(_) => return Err(Error::Syntax), }; } - rooted!(in(self.runtime.cx()) let mut rval = UndefinedValue()); + rooted!(in(self.runtime.borrow().as_ref().unwrap().cx()) let mut rval = UndefinedValue()); for url in urls { let global_scope = self.upcast::<GlobalScope>(); - let request = NetRequestInit { - url: url.clone(), - type_: RequestType::Script, - destination: Destination::Script, - credentials_mode: CredentialsMode::Include, - use_url_credentials: true, - origin: self.worker_url.clone(), - pipeline_id: Some(self.upcast::<GlobalScope>().pipeline_id()), - referrer_url: None, - referrer_policy: None, - .. NetRequestInit::default() - }; - let (url, source) = match load_whole_resource(request, - &global_scope.resource_threads().sender()) { + let request = NetRequestInit::new(url.clone(), global_scope.get_referrer()) + .destination(Destination::Script) + .credentials_mode(CredentialsMode::Include) + .parser_metadata(ParserMetadata::NotParserInserted) + .use_url_credentials(true) + .origin(global_scope.origin().immutable().clone()) + .pipeline_id(Some(self.upcast::<GlobalScope>().pipeline_id())) + .referrer_policy(None); + + let (url, source) = match fetch::load_whole_resource( + request, + &global_scope.resource_threads().sender(), + &global_scope, + ) { Err(_) => return Err(Error::Network), - Ok((metadata, bytes)) => { - (metadata.final_url, String::from_utf8(bytes).unwrap()) - } + Ok((metadata, bytes)) => (metadata.final_url, String::from_utf8(bytes).unwrap()), }; - let result = self.runtime.evaluate_script( - self.reflector().get_jsobject(), &source, url.as_str(), 1, rval.handle_mut()); + let result = self.runtime.borrow().as_ref().unwrap().evaluate_script( + self.reflector().get_jsobject(), + &source, + url.as_str(), + 1, + rval.handle_mut(), + ); maybe_resume_unwind(); @@ -235,7 +285,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { Err(_) => { println!("evaluate_script failed"); return Err(Error::JSFailed); - } + }, } } @@ -243,12 +293,12 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { } // https://html.spec.whatwg.org/multipage/#dom-worker-navigator - fn Navigator(&self) -> Root<WorkerNavigator> { + fn Navigator(&self) -> DomRoot<WorkerNavigator> { self.navigator.or_init(|| WorkerNavigator::new(self)) } // https://html.spec.whatwg.org/multipage/#dfn-Crypto - fn Crypto(&self) -> Root<Crypto> { + fn Crypto(&self) -> DomRoot<Crypto> { self.upcast::<GlobalScope>().crypto() } @@ -262,53 +312,50 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { base64_atob(atob) } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout - unsafe fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, - timeout: i32, args: Vec<HandleValue>) -> i32 { - self.upcast::<GlobalScope>().set_timeout_or_interval( - TimerCallback::FunctionTimerCallback(callback), - args, - timeout, - IsInterval::NonInterval) - } - - #[allow(unsafe_code)] - // https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout - unsafe fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, - timeout: i32, args: Vec<HandleValue>) -> i32 { + fn SetTimeout( + &self, + _cx: JSContext, + callback: StringOrFunction, + timeout: i32, + args: Vec<HandleValue>, + ) -> i32 { + let callback = match callback { + StringOrFunction::String(i) => TimerCallback::StringTimerCallback(i), + StringOrFunction::Function(i) => TimerCallback::FunctionTimerCallback(i), + }; self.upcast::<GlobalScope>().set_timeout_or_interval( - TimerCallback::StringTimerCallback(callback), + callback, args, timeout, - IsInterval::NonInterval) + IsInterval::NonInterval, + ) } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout fn ClearTimeout(&self, handle: i32) { - self.upcast::<GlobalScope>().clear_timeout_or_interval(handle); + self.upcast::<GlobalScope>() + .clear_timeout_or_interval(handle); } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval - unsafe fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, - timeout: i32, args: Vec<HandleValue>) -> i32 { - self.upcast::<GlobalScope>().set_timeout_or_interval( - TimerCallback::FunctionTimerCallback(callback), - args, - timeout, - IsInterval::Interval) - } - - #[allow(unsafe_code)] - // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval - unsafe fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, - timeout: i32, args: Vec<HandleValue>) -> i32 { + fn SetInterval( + &self, + _cx: JSContext, + callback: StringOrFunction, + timeout: i32, + args: Vec<HandleValue>, + ) -> i32 { + let callback = match callback { + StringOrFunction::String(i) => TimerCallback::StringTimerCallback(i), + StringOrFunction::Function(i) => TimerCallback::FunctionTimerCallback(i), + }; self.upcast::<GlobalScope>().set_timeout_or_interval( - TimerCallback::StringTimerCallback(callback), + callback, args, timeout, - IsInterval::Interval) + IsInterval::Interval, + ) } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval @@ -316,21 +363,72 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { self.ClearTimeout(handle); } + // https://html.spec.whatwg.org/multipage/#dom-queuemicrotask + fn QueueMicrotask(&self, callback: Rc<VoidFunction>) { + self.upcast::<GlobalScope>() + .queue_function_as_microtask(callback); + } + + // https://html.spec.whatwg.org/multipage/#dom-createimagebitmap + fn CreateImageBitmap( + &self, + image: ImageBitmapSource, + options: &ImageBitmapOptions, + ) -> Rc<Promise> { + let p = self + .upcast::<GlobalScope>() + .create_image_bitmap(image, options); + p + } + #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#fetch-method - fn Fetch(&self, input: RequestOrUSVString, init: RootedTraceableBox<RequestInit>) -> Rc<Promise> { - fetch::Fetch(self.upcast(), input, init) + fn Fetch( + &self, + input: RequestOrUSVString, + init: RootedTraceableBox<RequestInit>, + comp: InRealm, + ) -> Rc<Promise> { + fetch::Fetch(self.upcast(), input, init, comp) + } + + // https://w3c.github.io/hr-time/#the-performance-attribute + fn Performance(&self) -> DomRoot<Performance> { + self.performance.or_init(|| { + let global_scope = self.upcast::<GlobalScope>(); + Performance::new(global_scope, self.navigation_start_precise) + }) } -} + // https://html.spec.whatwg.org/multipage/#dom-origin + fn Origin(&self) -> USVString { + USVString( + self.upcast::<GlobalScope>() + .origin() + .immutable() + .ascii_serialization(), + ) + } + + // https://w3c.github.io/webappsec-secure-contexts/#dom-windoworworkerglobalscope-issecurecontext + fn IsSecureContext(&self) -> bool { + self.upcast::<GlobalScope>().is_secure_context() + } +} impl WorkerGlobalScope { #[allow(unsafe_code)] pub fn execute_script(&self, source: DOMString) { let _aes = AutoEntryScript::new(self.upcast()); - rooted!(in(self.runtime.cx()) let mut rval = UndefinedValue()); - match self.runtime.evaluate_script( - self.reflector().get_jsobject(), &source, self.worker_url.as_str(), 1, rval.handle_mut()) { + let cx = self.runtime.borrow().as_ref().unwrap().cx(); + rooted!(in(cx) let mut rval = UndefinedValue()); + match self.runtime.borrow().as_ref().unwrap().evaluate_script( + self.reflector().get_jsobject(), + &source, + self.worker_url.borrow().as_str(), + 1, + rval.handle_mut(), + ) { Ok(_) => (), Err(_) => { if self.is_closing() { @@ -340,16 +438,15 @@ impl WorkerGlobalScope { // https://github.com/servo/servo/issues/6422 println!("evaluate_script failed"); unsafe { - let _ac = JSAutoCompartment::new(self.runtime.cx(), - self.reflector().get_jsobject().get()); - report_pending_exception(self.runtime.cx(), true); + let ar = enter_realm(&*self); + report_pending_exception(cx, true, InRealm::Entered(&ar)); } } - } + }, } } - pub fn script_chan(&self) -> Box<ScriptChan + Send> { + pub fn script_chan(&self) -> Box<dyn ScriptChan + Send> { let dedicated = self.downcast::<DedicatedWorkerGlobalScope>(); let service_worker = self.downcast::<ServiceWorkerGlobalScope>(); if let Some(dedicated) = dedicated { @@ -361,15 +458,39 @@ impl WorkerGlobalScope { } } + pub fn dom_manipulation_task_source(&self) -> DOMManipulationTaskSource { + DOMManipulationTaskSource(self.script_chan(), self.pipeline_id()) + } + pub fn file_reading_task_source(&self) -> FileReadingTaskSource { - FileReadingTaskSource(self.script_chan()) + FileReadingTaskSource(self.script_chan(), self.pipeline_id()) } pub fn networking_task_source(&self) -> NetworkingTaskSource { - NetworkingTaskSource(self.script_chan()) + NetworkingTaskSource(self.script_chan(), self.pipeline_id()) + } + + pub fn performance_timeline_task_source(&self) -> PerformanceTimelineTaskSource { + PerformanceTimelineTaskSource(self.script_chan(), self.pipeline_id()) } - pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) { + pub fn port_message_queue(&self) -> PortMessageQueue { + PortMessageQueue(self.script_chan(), self.pipeline_id()) + } + + pub fn timer_task_source(&self) -> TimerTaskSource { + TimerTaskSource(self.script_chan(), self.pipeline_id()) + } + + pub fn remote_event_task_source(&self) -> RemoteEventTaskSource { + RemoteEventTaskSource(self.script_chan(), self.pipeline_id()) + } + + pub fn websocket_task_source(&self) -> WebsocketTaskSource { + WebsocketTaskSource(self.script_chan(), self.pipeline_id()) + } + + pub fn new_script_pair(&self) -> (Box<dyn ScriptChan + Send>, Box<dyn ScriptPort + Send>) { let dedicated = self.downcast::<DedicatedWorkerGlobalScope>(); if let Some(dedicated) = dedicated { return dedicated.new_script_pair(); @@ -378,27 +499,26 @@ impl WorkerGlobalScope { } } - pub fn process_event(&self, msg: CommonScriptMsg) { - let dedicated = self.downcast::<DedicatedWorkerGlobalScope>(); - let service_worker = self.downcast::<ServiceWorkerGlobalScope>(); - if let Some(dedicated) = dedicated { - return dedicated.process_event(msg); - } else if let Some(service_worker) = service_worker { - return service_worker.process_event(msg); - } else { - panic!("need to implement a sender for SharedWorker") + /// Process a single event as if it were the next event + /// in the queue for this worker event-loop. + /// Returns a boolean indicating whether further events should be processed. + pub fn process_event(&self, msg: CommonScriptMsg) -> bool { + if self.is_closing() { + return false; } - - //XXXjdm should we do a microtask checkpoint here? - } - - pub fn handle_fire_timer(&self, timer_id: TimerEventId) { - self.upcast::<GlobalScope>().fire_timer(timer_id); + match msg { + CommonScriptMsg::Task(_, task, _, _) => task.run_box(), + CommonScriptMsg::CollectReports(reports_chan) => { + let cx = self.get_cx(); + let path_seg = format!("url({})", self.get_url()); + let reports = get_reports(*cx, path_seg); + reports_chan.send(reports); + }, + } + true } pub fn close(&self) { - if let Some(ref closing) = self.closing { - closing.store(true, Ordering::SeqCst); - } + self.closing.store(true, Ordering::SeqCst); } } diff --git a/components/script/dom/workerlocation.rs b/components/script/dom/workerlocation.rs index f52e0efb69a..c3974e3e1ce 100644 --- a/components/script/dom/workerlocation.rs +++ b/components/script/dom/workerlocation.rs @@ -1,16 +1,15 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::WorkerLocationBinding; -use dom::bindings::codegen::Bindings::WorkerLocationBinding::WorkerLocationMethods; -use dom::bindings::js::Root; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; -use dom::bindings::str::{DOMString, USVString}; -use dom::urlhelper::UrlHelper; -use dom::workerglobalscope::WorkerGlobalScope; +use crate::dom::bindings::codegen::Bindings::WorkerLocationBinding::WorkerLocationMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::urlhelper::UrlHelper; +use crate::dom::workerglobalscope::WorkerGlobalScope; use dom_struct::dom_struct; -use servo_url::ServoUrl; +use servo_url::{ImmutableOrigin, ServoUrl}; // https://html.spec.whatwg.org/multipage/#worker-locations #[dom_struct] @@ -27,10 +26,14 @@ impl WorkerLocation { } } - pub fn new(global: &WorkerGlobalScope, url: ServoUrl) -> Root<WorkerLocation> { - reflect_dom_object(box WorkerLocation::new_inherited(url), - global, - WorkerLocationBinding::Wrap) + pub fn new(global: &WorkerGlobalScope, url: ServoUrl) -> DomRoot<WorkerLocation> { + reflect_dom_object(Box::new(WorkerLocation::new_inherited(url)), global) + } + + // https://html.spec.whatwg.org/multipage/#dom-workerlocation-origin + #[allow(dead_code)] + pub fn origin(&self) -> ImmutableOrigin { + self.url.origin() } } @@ -55,6 +58,11 @@ impl WorkerLocationMethods for WorkerLocation { UrlHelper::Href(&self.url) } + // https://html.spec.whatwg.org/multipage/#dom-workerlocation-origin + fn Origin(&self) -> USVString { + UrlHelper::Origin(&self.url) + } + // https://html.spec.whatwg.org/multipage/#dom-workerlocation-pathname fn Pathname(&self) -> USVString { UrlHelper::Pathname(&self.url) @@ -74,9 +82,4 @@ impl WorkerLocationMethods for WorkerLocation { fn Search(&self) -> USVString { UrlHelper::Search(&self.url) } - - // https://html.spec.whatwg.org/multipage/#dom-workerlocation-href - fn Stringifier(&self) -> DOMString { - DOMString::from(self.Href().0) - } } diff --git a/components/script/dom/workernavigator.rs b/components/script/dom/workernavigator.rs index dafa3325b38..7f7b656068f 100644 --- a/components/script/dom/workernavigator.rs +++ b/components/script/dom/workernavigator.rs @@ -1,22 +1,26 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use dom::bindings::codegen::Bindings::WorkerNavigatorBinding; -use dom::bindings::codegen::Bindings::WorkerNavigatorBinding::WorkerNavigatorMethods; -use dom::bindings::js::{MutNullableJS, Root}; -use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; -use dom::bindings::str::DOMString; -use dom::navigatorinfo; -use dom::permissions::Permissions; -use dom::workerglobalscope::WorkerGlobalScope; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::WorkerNavigatorBinding::WorkerNavigatorMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::utils::to_frozen_array; +use crate::dom::gpu::GPU; +use crate::dom::navigatorinfo; +use crate::dom::permissions::Permissions; +use crate::dom::workerglobalscope::WorkerGlobalScope; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; +use js::jsval::JSVal; // https://html.spec.whatwg.org/multipage/#workernavigator #[dom_struct] pub struct WorkerNavigator { reflector_: Reflector, - permissions: MutNullableJS<Permissions>, + permissions: MutNullableDom<Permissions>, + gpu: MutNullableDom<GPU>, } impl WorkerNavigator { @@ -24,13 +28,12 @@ impl WorkerNavigator { WorkerNavigator { reflector_: Reflector::new(), permissions: Default::default(), + gpu: Default::default(), } } - pub fn new(global: &WorkerGlobalScope) -> Root<WorkerNavigator> { - reflect_dom_object(box WorkerNavigator::new_inherited(), - global, - WorkerNavigatorBinding::Wrap) + pub fn new(global: &WorkerGlobalScope) -> DomRoot<WorkerNavigator> { + reflect_dom_object(Box::new(WorkerNavigator::new_inherited()), global) } } @@ -40,6 +43,21 @@ impl WorkerNavigatorMethods for WorkerNavigator { navigatorinfo::Product() } + // https://html.spec.whatwg.org/multipage/#dom-navigator-productsub + fn ProductSub(&self) -> DOMString { + navigatorinfo::ProductSub() + } + + // https://html.spec.whatwg.org/multipage/#dom-navigator-vendor + fn Vendor(&self) -> DOMString { + navigatorinfo::Vendor() + } + + // https://html.spec.whatwg.org/multipage/#dom-navigator-vendorsub + fn VendorSub(&self) -> DOMString { + navigatorinfo::VendorSub() + } + // https://html.spec.whatwg.org/multipage/#dom-navigator-taintenabled fn TaintEnabled(&self) -> bool { navigatorinfo::TaintEnabled() @@ -62,7 +80,7 @@ impl WorkerNavigatorMethods for WorkerNavigator { // https://html.spec.whatwg.org/multipage/#dom-navigator-useragent fn UserAgent(&self) -> DOMString { - navigatorinfo::UserAgent() + navigatorinfo::UserAgent(self.global().get_user_agent()) } // https://html.spec.whatwg.org/multipage/#dom-navigator-appversion @@ -75,8 +93,20 @@ impl WorkerNavigatorMethods for WorkerNavigator { navigatorinfo::Language() } + // https://html.spec.whatwg.org/multipage/#dom-navigator-languages + #[allow(unsafe_code)] + fn Languages(&self, cx: JSContext) -> JSVal { + to_frozen_array(&[self.Language()], cx) + } + // https://w3c.github.io/permissions/#navigator-and-workernavigator-extension - fn Permissions(&self) -> Root<Permissions> { - self.permissions.or_init(|| Permissions::new(&self.global())) + fn Permissions(&self) -> DomRoot<Permissions> { + self.permissions + .or_init(|| Permissions::new(&self.global())) + } + + // https://gpuweb.github.io/gpuweb/#dom-navigator-gpu + fn Gpu(&self) -> DomRoot<GPU> { + self.gpu.or_init(|| GPU::new(&self.global())) } } diff --git a/components/script/dom/worklet.rs b/components/script/dom/worklet.rs new file mode 100644 index 00000000000..46930bbf651 --- /dev/null +++ b/components/script/dom/worklet.rs @@ -0,0 +1,760 @@ +/* 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/. */ + +//! An implementation of Houdini worklets. +//! +//! The goal of this implementation is to maximize responsiveness of worklets, +//! and in particular to ensure that the thread performing worklet tasks +//! is never busy GCing or loading worklet code. We do this by providing a custom +//! thread pool implementation, which only performs GC or code loading on +//! a backup thread, not on the primary worklet thread. + +use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::Bindings::WorkletBinding::WorkletMethods; +use crate::dom::bindings::codegen::Bindings::WorkletBinding::WorkletOptions; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::TrustedPromise; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::{Dom, DomRoot, RootCollection, ThreadLocalStackRoots}; +use crate::dom::bindings::str::USVString; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::testworkletglobalscope::TestWorkletTask; +use crate::dom::window::Window; +use crate::dom::workletglobalscope::WorkletGlobalScope; +use crate::dom::workletglobalscope::WorkletGlobalScopeInit; +use crate::dom::workletglobalscope::WorkletGlobalScopeType; +use crate::dom::workletglobalscope::WorkletTask; +use crate::fetch::load_whole_resource; +use crate::realms::InRealm; +use crate::script_runtime::new_rt_and_cx; +use crate::script_runtime::CommonScriptMsg; +use crate::script_runtime::Runtime; +use crate::script_runtime::ScriptThreadEventCategory; +use crate::script_thread::{MainThreadScriptMsg, ScriptThread}; +use crate::task::TaskBox; +use crate::task_source::TaskSourceName; +use crossbeam_channel::{unbounded, Receiver, Sender}; +use dom_struct::dom_struct; +use js::jsapi::JSGCParamKey; +use js::jsapi::JSTracer; +use js::jsapi::JS_GetGCParameter; +use js::jsapi::{GCReason, JS_GC}; +use msg::constellation_msg::PipelineId; +use net_traits::request::Destination; +use net_traits::request::RequestBuilder; +use net_traits::request::RequestMode; +use net_traits::IpcSend; +use servo_url::ImmutableOrigin; +use servo_url::ServoUrl; +use std::cmp::max; +use std::collections::hash_map; +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::atomic::AtomicIsize; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::thread; +use style::thread_state::{self, ThreadState}; +use swapper::swapper; +use swapper::Swapper; +use uuid::Uuid; + +// Magic numbers +const WORKLET_THREAD_POOL_SIZE: u32 = 3; +const MIN_GC_THRESHOLD: u32 = 1_000_000; + +#[dom_struct] +/// <https://drafts.css-houdini.org/worklets/#worklet> +pub struct Worklet { + reflector: Reflector, + window: Dom<Window>, + worklet_id: WorkletId, + global_type: WorkletGlobalScopeType, +} + +impl Worklet { + fn new_inherited(window: &Window, global_type: WorkletGlobalScopeType) -> Worklet { + Worklet { + reflector: Reflector::new(), + window: Dom::from_ref(window), + worklet_id: WorkletId::new(), + global_type: global_type, + } + } + + pub fn new(window: &Window, global_type: WorkletGlobalScopeType) -> DomRoot<Worklet> { + debug!("Creating worklet {:?}.", global_type); + reflect_dom_object( + Box::new(Worklet::new_inherited(window, global_type)), + window, + ) + } + + pub fn worklet_id(&self) -> WorkletId { + self.worklet_id + } + + #[allow(dead_code)] + pub fn worklet_global_scope_type(&self) -> WorkletGlobalScopeType { + self.global_type + } +} + +impl WorkletMethods for Worklet { + /// <https://drafts.css-houdini.org/worklets/#dom-worklet-addmodule> + fn AddModule( + &self, + module_url: USVString, + options: &WorkletOptions, + comp: InRealm, + ) -> Rc<Promise> { + // Step 1. + let global = self.window.upcast(); + let promise = Promise::new_in_current_realm(&global, comp); + + // Step 3. + let module_url_record = match self.window.Document().base_url().join(&module_url.0) { + Ok(url) => url, + Err(err) => { + // Step 4. + debug!("URL {:?} parse error {:?}.", module_url.0, err); + promise.reject_error(Error::Syntax); + return promise; + }, + }; + debug!("Adding Worklet module {}.", module_url_record); + + // Steps 6-12 in parallel. + let pending_tasks_struct = PendingTasksStruct::new(); + let global = self.window.upcast::<GlobalScope>(); + let pool = ScriptThread::worklet_thread_pool(); + + pool.fetch_and_invoke_a_worklet_script( + global.pipeline_id(), + self.worklet_id, + self.global_type, + self.window.origin().immutable().clone(), + global.api_base_url(), + module_url_record, + options.credentials.clone(), + pending_tasks_struct, + &promise, + ); + + // Step 5. + debug!("Returning promise."); + promise + } +} + +impl Drop for Worklet { + fn drop(&mut self) { + let script_thread = ScriptThread::worklet_thread_pool(); + script_thread.exit_worklet(self.worklet_id); + } +} + +/// A guid for worklets. +#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)] +pub struct WorkletId(Uuid); + +malloc_size_of_is_0!(WorkletId); + +impl WorkletId { + fn new() -> WorkletId { + WorkletId(servo_rand::random_uuid()) + } +} + +/// <https://drafts.css-houdini.org/worklets/#pending-tasks-struct> +#[derive(Clone, Debug)] +struct PendingTasksStruct(Arc<AtomicIsize>); + +impl PendingTasksStruct { + fn new() -> PendingTasksStruct { + PendingTasksStruct(Arc::new(AtomicIsize::new( + WORKLET_THREAD_POOL_SIZE as isize, + ))) + } + + fn set_counter_to(&self, value: isize) -> isize { + self.0.swap(value, Ordering::AcqRel) + } + + fn decrement_counter_by(&self, offset: isize) -> isize { + self.0.fetch_sub(offset, Ordering::AcqRel) + } +} + +/// Worklets execute in a dedicated thread pool. +/// +/// The goal is to ensure that there is a primary worklet thread, +/// which is able to responsively execute worklet code. In particular, +/// worklet execution should not be delayed by GC, or by script +/// loading. +/// +/// To achieve this, we implement a three-thread pool, with the +/// threads cycling between three thread roles: +/// +/// * The primary worklet thread is the one available to execute +/// worklet code. +/// +/// * The hot backup thread may peform GC, but otherwise is expected +/// to take over the primary role. +/// +/// * The cold backup thread may peform script loading and other +/// long-running tasks. +/// +/// In the implementation, we use two kinds of messages: +/// +/// * Data messages are expected to be processed quickly, and include +/// the worklet tasks to be performed by the primary thread, as +/// well as requests to change role or quit execution. +/// +/// * Control messages are expected to be processed more slowly, and +/// include script loading. +/// +/// Data messages are targeted at a role, for example, task execution +/// is expected to be performed by whichever thread is currently +/// primary. Control messages are targeted at a thread, for example +/// adding a module is performed in every thread, even if they change roles +/// in the middle of module loading. +/// +/// The thread pool lives in the script thread, and is initialized +/// when a worklet adds a module. It is dropped when the script thread +/// is dropped, and asks each of the worklet threads to quit. +/// +/// The layout thread can end up blocking on the primary worklet thread +/// (e.g. when invoking a paint callback), so it is important to avoid +/// deadlock by making sure the primary worklet thread doesn't end up +/// blocking waiting on layout. In particular, since the constellation +/// can block waiting on layout, this means the primary worklet thread +/// can't block waiting on the constellation. In general, the primary +/// worklet thread shouldn't perform any blocking operations. If a worklet +/// thread needs to do anything blocking, it should send a control +/// message, to make sure that the blocking operation is performed +/// by a backup thread, not by the primary thread. + +#[derive(Clone, JSTraceable)] +pub struct WorkletThreadPool { + // Channels to send data messages to the three roles. + primary_sender: Sender<WorkletData>, + hot_backup_sender: Sender<WorkletData>, + cold_backup_sender: Sender<WorkletData>, + // Channels to send control messages to the three threads. + control_sender_0: Sender<WorkletControl>, + control_sender_1: Sender<WorkletControl>, + control_sender_2: Sender<WorkletControl>, +} + +impl Drop for WorkletThreadPool { + fn drop(&mut self) { + let _ = self.cold_backup_sender.send(WorkletData::Quit); + let _ = self.hot_backup_sender.send(WorkletData::Quit); + let _ = self.primary_sender.send(WorkletData::Quit); + } +} + +impl WorkletThreadPool { + /// Create a new thread pool and spawn the threads. + /// When the thread pool is dropped, the threads will be asked to quit. + pub fn spawn(global_init: WorkletGlobalScopeInit) -> WorkletThreadPool { + let primary_role = WorkletThreadRole::new(false, false); + let hot_backup_role = WorkletThreadRole::new(true, false); + let cold_backup_role = WorkletThreadRole::new(false, true); + let primary_sender = primary_role.sender.clone(); + let hot_backup_sender = hot_backup_role.sender.clone(); + let cold_backup_sender = cold_backup_role.sender.clone(); + let init = WorkletThreadInit { + primary_sender: primary_sender.clone(), + hot_backup_sender: hot_backup_sender.clone(), + cold_backup_sender: cold_backup_sender.clone(), + global_init: global_init, + }; + WorkletThreadPool { + primary_sender: primary_sender, + hot_backup_sender: hot_backup_sender, + cold_backup_sender: cold_backup_sender, + control_sender_0: WorkletThread::spawn(primary_role, init.clone()), + control_sender_1: WorkletThread::spawn(hot_backup_role, init.clone()), + control_sender_2: WorkletThread::spawn(cold_backup_role, init), + } + } + + /// Loads a worklet module into every worklet thread. + /// If all of the threads load successfully, the promise is resolved. + /// If any of the threads fails to load, the promise is rejected. + /// <https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script> + fn fetch_and_invoke_a_worklet_script( + &self, + pipeline_id: PipelineId, + worklet_id: WorkletId, + global_type: WorkletGlobalScopeType, + origin: ImmutableOrigin, + base_url: ServoUrl, + script_url: ServoUrl, + credentials: RequestCredentials, + pending_tasks_struct: PendingTasksStruct, + promise: &Rc<Promise>, + ) { + // Send each thread a control message asking it to load the script. + for sender in &[ + &self.control_sender_0, + &self.control_sender_1, + &self.control_sender_2, + ] { + let _ = sender.send(WorkletControl::FetchAndInvokeAWorkletScript { + pipeline_id: pipeline_id, + worklet_id: worklet_id, + global_type: global_type, + origin: origin.clone(), + base_url: base_url.clone(), + script_url: script_url.clone(), + credentials: credentials, + pending_tasks_struct: pending_tasks_struct.clone(), + promise: TrustedPromise::new(promise.clone()), + }); + } + self.wake_threads(); + } + + pub(crate) fn exit_worklet(&self, worklet_id: WorkletId) { + for sender in &[ + &self.control_sender_0, + &self.control_sender_1, + &self.control_sender_2, + ] { + let _ = sender.send(WorkletControl::ExitWorklet(worklet_id)); + } + self.wake_threads(); + } + + /// For testing. + pub 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))); + let _ = self.primary_sender.send(msg); + receiver.recv().expect("Test worklet has died?") + } + + fn wake_threads(&self) { + // If any of the threads are blocked waiting on data, wake them up. + let _ = self.cold_backup_sender.send(WorkletData::WakeUp); + let _ = self.hot_backup_sender.send(WorkletData::WakeUp); + let _ = self.primary_sender.send(WorkletData::WakeUp); + } +} + +/// The data messages sent to worklet threads +enum WorkletData { + Task(WorkletId, WorkletTask), + StartSwapRoles(Sender<WorkletData>), + FinishSwapRoles(Swapper<WorkletThreadRole>), + WakeUp, + Quit, +} + +/// The control message sent to worklet threads +enum WorkletControl { + ExitWorklet(WorkletId), + FetchAndInvokeAWorkletScript { + pipeline_id: PipelineId, + worklet_id: WorkletId, + global_type: WorkletGlobalScopeType, + origin: ImmutableOrigin, + base_url: ServoUrl, + script_url: ServoUrl, + credentials: RequestCredentials, + pending_tasks_struct: PendingTasksStruct, + promise: TrustedPromise, + }, +} + +/// A role that a worklet thread can be playing. +/// +/// These roles are used as tokens or capabilities, we track unique +/// ownership using Rust's types, and use atomic swapping to exchange +/// them between worklet threads. This ensures that each thread pool has +/// exactly one primary, one hot backup and one cold backup. +struct WorkletThreadRole { + receiver: Receiver<WorkletData>, + sender: Sender<WorkletData>, + is_hot_backup: bool, + is_cold_backup: bool, +} + +impl WorkletThreadRole { + fn new(is_hot_backup: bool, is_cold_backup: bool) -> WorkletThreadRole { + let (sender, receiver) = unbounded(); + WorkletThreadRole { + sender: sender, + receiver: receiver, + is_hot_backup: is_hot_backup, + is_cold_backup: is_cold_backup, + } + } +} + +/// Data to initialize a worklet thread. +#[derive(Clone)] +struct WorkletThreadInit { + /// Senders + primary_sender: Sender<WorkletData>, + hot_backup_sender: Sender<WorkletData>, + cold_backup_sender: Sender<WorkletData>, + + /// Data for initializing new worklet global scopes + global_init: WorkletGlobalScopeInit, +} + +/// A thread for executing worklets. +#[unrooted_must_root_lint::must_root] +struct WorkletThread { + /// Which role the thread is currently playing + role: WorkletThreadRole, + + /// The thread's receiver for control messages + control_receiver: Receiver<WorkletControl>, + + /// Senders + primary_sender: Sender<WorkletData>, + hot_backup_sender: Sender<WorkletData>, + cold_backup_sender: Sender<WorkletData>, + + /// Data for initializing new worklet global scopes + global_init: WorkletGlobalScopeInit, + + /// The global scopes created by this thread + global_scopes: HashMap<WorkletId, Dom<WorkletGlobalScope>>, + + /// A one-place buffer for control messages + control_buffer: Option<WorkletControl>, + + /// The JS runtime + runtime: Runtime, + should_gc: bool, + gc_threshold: u32, +} + +#[allow(unsafe_code)] +unsafe impl JSTraceable for WorkletThread { + unsafe fn trace(&self, trc: *mut JSTracer) { + debug!("Tracing worklet thread."); + self.global_scopes.trace(trc); + } +} + +impl WorkletThread { + /// Spawn a new worklet thread, returning the channel to send it control messages. + #[allow(unsafe_code)] + #[allow(unrooted_must_root)] + fn spawn(role: WorkletThreadRole, init: WorkletThreadInit) -> Sender<WorkletControl> { + let (control_sender, control_receiver) = unbounded(); + // TODO: name this thread + thread::spawn(move || { + // TODO: add a new IN_WORKLET thread state? + // TODO: set interrupt handler? + // TODO: configure the JS runtime (e.g. discourage GC, encourage agressive JIT) + debug!("Initializing worklet thread."); + thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER); + let roots = RootCollection::new(); + let _stack_roots = ThreadLocalStackRoots::new(&roots); + let mut thread = RootedTraceableBox::new(WorkletThread { + role: role, + control_receiver: control_receiver, + primary_sender: init.primary_sender, + hot_backup_sender: init.hot_backup_sender, + cold_backup_sender: init.cold_backup_sender, + global_init: init.global_init, + global_scopes: HashMap::new(), + control_buffer: None, + runtime: new_rt_and_cx(None), + should_gc: false, + gc_threshold: MIN_GC_THRESHOLD, + }); + thread.run(); + }); + control_sender + } + + /// The main event loop for a worklet thread + fn run(&mut self) { + loop { + // The handler for data messages + let message = self.role.receiver.recv().unwrap(); + match message { + // The whole point of this thread pool is to perform tasks! + WorkletData::Task(id, task) => { + self.perform_a_worklet_task(id, task); + }, + // To start swapping roles, get ready to perform an atomic swap, + // and block waiting for the other end to finish it. + // NOTE: the cold backup can block on the primary or the hot backup; + // the hot backup can block on the primary; + // the primary can block on nothing; + // this total ordering on thread roles is what guarantees deadlock-freedom. + WorkletData::StartSwapRoles(sender) => { + let (our_swapper, their_swapper) = swapper(); + sender + .send(WorkletData::FinishSwapRoles(their_swapper)) + .unwrap(); + let _ = our_swapper.swap(&mut self.role); + }, + // To finish swapping roles, perform the atomic swap. + // The other end should have already started the swap, so this shouldn't block. + WorkletData::FinishSwapRoles(swapper) => { + let _ = swapper.swap(&mut self.role); + }, + // Wake up! There may be control messages to process. + WorkletData::WakeUp => {}, + // Quit! + WorkletData::Quit => { + return; + }, + } + // Only process control messages if we're the cold backup, + // otherwise if there are outstanding control messages, + // try to become the cold backup. + if self.role.is_cold_backup { + if let Some(control) = self.control_buffer.take() { + self.process_control(control); + } + while let Ok(control) = self.control_receiver.try_recv() { + self.process_control(control); + } + self.gc(); + } else if self.control_buffer.is_none() { + if let Ok(control) = self.control_receiver.try_recv() { + self.control_buffer = Some(control); + let msg = WorkletData::StartSwapRoles(self.role.sender.clone()); + let _ = self.cold_backup_sender.send(msg); + } + } + // If we are tight on memory, and we're a backup then perform a gc. + // If we are tight on memory, and we're the primary then try to become the hot backup. + // Hopefully this happens soon! + if self.current_memory_usage() > self.gc_threshold { + if self.role.is_hot_backup || self.role.is_cold_backup { + self.should_gc = false; + self.gc(); + } else if !self.should_gc { + self.should_gc = true; + let msg = WorkletData::StartSwapRoles(self.role.sender.clone()); + let _ = self.hot_backup_sender.send(msg); + } + } + } + } + + /// The current memory usage of the thread + #[allow(unsafe_code)] + fn current_memory_usage(&self) -> u32 { + unsafe { JS_GetGCParameter(self.runtime.cx(), JSGCParamKey::JSGC_BYTES) } + } + + /// Perform a GC. + #[allow(unsafe_code)] + fn gc(&mut self) { + debug!( + "BEGIN GC (usage = {}, threshold = {}).", + self.current_memory_usage(), + self.gc_threshold + ); + unsafe { JS_GC(self.runtime.cx(), GCReason::API) }; + self.gc_threshold = max(MIN_GC_THRESHOLD, self.current_memory_usage() * 2); + debug!( + "END GC (usage = {}, threshold = {}).", + self.current_memory_usage(), + self.gc_threshold + ); + } + + /// Get the worklet global scope for a given worklet. + /// Creates the worklet global scope if it doesn't exist. + fn get_worklet_global_scope( + &mut self, + pipeline_id: PipelineId, + worklet_id: WorkletId, + global_type: WorkletGlobalScopeType, + base_url: ServoUrl, + ) -> DomRoot<WorkletGlobalScope> { + match self.global_scopes.entry(worklet_id) { + hash_map::Entry::Occupied(entry) => DomRoot::from_ref(entry.get()), + hash_map::Entry::Vacant(entry) => { + debug!("Creating new worklet global scope."); + let executor = WorkletExecutor::new(worklet_id, self.primary_sender.clone()); + let result = global_type.new( + &self.runtime, + pipeline_id, + base_url, + executor, + &self.global_init, + ); + entry.insert(Dom::from_ref(&*result)); + result + }, + } + } + + /// Fetch and invoke a worklet script. + /// <https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script> + fn fetch_and_invoke_a_worklet_script( + &self, + global_scope: &WorkletGlobalScope, + pipeline_id: PipelineId, + origin: ImmutableOrigin, + script_url: ServoUrl, + credentials: RequestCredentials, + pending_tasks_struct: PendingTasksStruct, + promise: TrustedPromise, + ) { + debug!("Fetching from {}.", script_url); + // Step 1. + // TODO: Settings object? + + // Step 2. + // TODO: Fetch a module graph, not just a single script. + // TODO: Fetch the script asynchronously? + // TODO: Caching. + let resource_fetcher = self.global_init.resource_threads.sender(); + let request = RequestBuilder::new( + script_url, + global_scope.upcast::<GlobalScope>().get_referrer(), + ) + .destination(Destination::Script) + .mode(RequestMode::CorsMode) + .credentials_mode(credentials.into()) + .origin(origin); + + let script = load_whole_resource( + request, + &resource_fetcher, + &global_scope.upcast::<GlobalScope>(), + ) + .ok() + .and_then(|(_, bytes)| String::from_utf8(bytes).ok()); + + // Step 4. + // NOTE: the spec parses and executes the script in separate steps, + // but our JS API doesn't separate these, so we do the steps out of order. + // Also, the spec currently doesn't allow exceptions to be propagated + // to the main script thread. + // https://github.com/w3c/css-houdini-drafts/issues/407 + let ok = script + .map(|script| global_scope.evaluate_js(&*script)) + .unwrap_or(false); + + if !ok { + // Step 3. + debug!("Failed to load script."); + let old_counter = pending_tasks_struct.set_counter_to(-1); + if old_counter > 0 { + self.run_in_script_thread(promise.reject_task(Error::Abort)); + } + } else { + // Step 5. + debug!("Finished adding script."); + let old_counter = pending_tasks_struct.decrement_counter_by(1); + if old_counter == 1 { + debug!("Resolving promise."); + let msg = MainThreadScriptMsg::WorkletLoaded(pipeline_id); + self.global_init + .to_script_thread_sender + .send(msg) + .expect("Worklet thread outlived script thread."); + self.run_in_script_thread(promise.resolve_task(())); + } + } + } + + /// Perform a task. + fn perform_a_worklet_task(&self, worklet_id: WorkletId, task: WorkletTask) { + match self.global_scopes.get(&worklet_id) { + Some(global) => global.perform_a_worklet_task(task), + None => return warn!("No such worklet as {:?}.", worklet_id), + } + } + + /// Process a control message. + fn process_control(&mut self, control: WorkletControl) { + match control { + WorkletControl::ExitWorklet(worklet_id) => { + self.global_scopes.remove(&worklet_id); + }, + WorkletControl::FetchAndInvokeAWorkletScript { + pipeline_id, + worklet_id, + global_type, + origin, + base_url, + script_url, + credentials, + pending_tasks_struct, + promise, + } => { + let global = + self.get_worklet_global_scope(pipeline_id, worklet_id, global_type, base_url); + self.fetch_and_invoke_a_worklet_script( + &*global, + pipeline_id, + origin, + script_url, + credentials, + pending_tasks_struct, + promise, + ) + }, + } + } + + /// Run a task in the main script thread. + fn run_in_script_thread<T>(&self, task: T) + where + T: TaskBox + 'static, + { + // NOTE: It's unclear which task source should be used here: + // https://drafts.css-houdini.org/worklets/#dom-worklet-addmodule + let msg = CommonScriptMsg::Task( + ScriptThreadEventCategory::WorkletEvent, + Box::new(task), + None, + TaskSourceName::DOMManipulation, + ); + let msg = MainThreadScriptMsg::Common(msg); + self.global_init + .to_script_thread_sender + .send(msg) + .expect("Worklet thread outlived script thread."); + } +} + +/// An executor of worklet tasks +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub struct WorkletExecutor { + worklet_id: WorkletId, + #[ignore_malloc_size_of = "channels are hard"] + primary_sender: Sender<WorkletData>, +} + +impl WorkletExecutor { + fn new(worklet_id: WorkletId, primary_sender: Sender<WorkletData>) -> WorkletExecutor { + WorkletExecutor { + worklet_id: worklet_id, + primary_sender: primary_sender, + } + } + + /// Schedule a worklet task to be peformed by the worklet thread pool. + pub fn schedule_a_worklet_task(&self, task: WorkletTask) { + let _ = self + .primary_sender + .send(WorkletData::Task(self.worklet_id, task)); + } +} diff --git a/components/script/dom/workletglobalscope.rs b/components/script/dom/workletglobalscope.rs new file mode 100644 index 00000000000..29d5b9b7f99 --- /dev/null +++ b/components/script/dom/workletglobalscope.rs @@ -0,0 +1,217 @@ +/* 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 crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::dom::identityhub::Identities; +use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; +use crate::dom::paintworkletglobalscope::PaintWorkletTask; +use crate::dom::testworkletglobalscope::TestWorkletGlobalScope; +use crate::dom::testworkletglobalscope::TestWorkletTask; +use crate::dom::worklet::WorkletExecutor; +use crate::script_module::ScriptFetchOptions; +use crate::script_runtime::JSContext; +use crate::script_thread::MainThreadScriptMsg; +use crossbeam_channel::Sender; +use devtools_traits::ScriptToDevtoolsControlMsg; +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSender; +use js::jsval::UndefinedValue; +use js::rust::Runtime; +use msg::constellation_msg::PipelineId; +use net_traits::image_cache::ImageCache; +use net_traits::ResourceThreads; +use parking_lot::Mutex; +use profile_traits::mem; +use profile_traits::time; +use script_traits::{Painter, ScriptMsg}; +use script_traits::{ScriptToConstellationChan, TimerSchedulerMsg}; +use servo_atoms::Atom; +use servo_url::ImmutableOrigin; +use servo_url::MutableOrigin; +use servo_url::ServoUrl; +use std::borrow::Cow; +use std::sync::Arc; + +#[dom_struct] +/// <https://drafts.css-houdini.org/worklets/#workletglobalscope> +pub struct WorkletGlobalScope { + /// The global for this worklet. + globalscope: GlobalScope, + /// The base URL for this worklet. + base_url: ServoUrl, + /// Sender back to the script thread + #[ignore_malloc_size_of = "channels are hard"] + to_script_thread_sender: Sender<MainThreadScriptMsg>, + /// Worklet task executor + executor: WorkletExecutor, +} + +impl WorkletGlobalScope { + /// Create a new stack-allocated `WorkletGlobalScope`. + pub fn new_inherited( + pipeline_id: PipelineId, + base_url: ServoUrl, + executor: WorkletExecutor, + init: &WorkletGlobalScopeInit, + ) -> Self { + let script_to_constellation_chan = ScriptToConstellationChan { + sender: init.to_constellation_sender.clone(), + pipeline_id, + }; + Self { + globalscope: GlobalScope::new_inherited( + pipeline_id, + init.devtools_chan.clone(), + init.mem_profiler_chan.clone(), + init.time_profiler_chan.clone(), + script_to_constellation_chan, + init.scheduler_chan.clone(), + init.resource_threads.clone(), + MutableOrigin::new(ImmutableOrigin::new_opaque()), + None, + Default::default(), + init.is_headless, + init.user_agent.clone(), + init.gpu_id_hub.clone(), + init.inherited_secure_context, + ), + base_url, + to_script_thread_sender: init.to_script_thread_sender.clone(), + executor, + } + } + + /// Get the JS context. + pub fn get_cx(&self) -> JSContext { + self.globalscope.get_cx() + } + + /// Evaluate a JS script in this global. + pub fn evaluate_js(&self, script: &str) -> bool { + debug!("Evaluating Dom in a worklet."); + rooted!(in (*self.globalscope.get_cx()) let mut rval = UndefinedValue()); + self.globalscope.evaluate_js_on_global_with_result( + &*script, + rval.handle_mut(), + ScriptFetchOptions::default_classic_script(&self.globalscope), + self.globalscope.api_base_url(), + ) + } + + /// Register a paint worklet to the script thread. + pub fn register_paint_worklet( + &self, + name: Atom, + properties: Vec<Atom>, + painter: Box<dyn Painter>, + ) { + self.to_script_thread_sender + .send(MainThreadScriptMsg::RegisterPaintWorklet { + pipeline_id: self.globalscope.pipeline_id(), + name, + properties, + painter, + }) + .expect("Worklet thread outlived script thread."); + } + + /// The base URL of this global. + pub fn base_url(&self) -> ServoUrl { + self.base_url.clone() + } + + /// The worklet executor. + pub fn executor(&self) -> WorkletExecutor { + self.executor.clone() + } + + /// Perform a worklet task + pub fn perform_a_worklet_task(&self, task: WorkletTask) { + match task { + WorkletTask::Test(task) => match self.downcast::<TestWorkletGlobalScope>() { + Some(global) => global.perform_a_worklet_task(task), + None => warn!("This is not a test worklet."), + }, + WorkletTask::Paint(task) => match self.downcast::<PaintWorkletGlobalScope>() { + Some(global) => global.perform_a_worklet_task(task), + None => warn!("This is not a paint worklet."), + }, + } + } +} + +/// Resources required by workletglobalscopes +#[derive(Clone)] +pub struct WorkletGlobalScopeInit { + /// Channel to the main script thread + pub to_script_thread_sender: Sender<MainThreadScriptMsg>, + /// Channel to a resource thread + pub resource_threads: ResourceThreads, + /// Channel to the memory profiler + pub mem_profiler_chan: mem::ProfilerChan, + /// Channel to the time profiler + pub time_profiler_chan: time::ProfilerChan, + /// Channel to devtools + pub devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>, + /// Messages to send to constellation + pub to_constellation_sender: IpcSender<(PipelineId, ScriptMsg)>, + /// Message to send to the scheduler + pub scheduler_chan: IpcSender<TimerSchedulerMsg>, + /// The image cache + pub image_cache: Arc<dyn ImageCache>, + /// True if in headless mode + pub is_headless: bool, + /// An optional string allowing the user agent to be set for testing + pub user_agent: Cow<'static, str>, + /// Identity manager for WebGPU resources + pub gpu_id_hub: Arc<Mutex<Identities>>, + /// Is considered secure + pub inherited_secure_context: Option<bool>, +} + +/// <https://drafts.css-houdini.org/worklets/#worklet-global-scope-type> +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)] +pub enum WorkletGlobalScopeType { + /// A servo-specific testing worklet + Test, + /// A paint worklet + Paint, +} + +impl WorkletGlobalScopeType { + /// Create a new heap-allocated `WorkletGlobalScope`. + pub fn new( + &self, + runtime: &Runtime, + pipeline_id: PipelineId, + base_url: ServoUrl, + executor: WorkletExecutor, + init: &WorkletGlobalScopeInit, + ) -> DomRoot<WorkletGlobalScope> { + match *self { + WorkletGlobalScopeType::Test => DomRoot::upcast(TestWorkletGlobalScope::new( + runtime, + pipeline_id, + base_url, + executor, + init, + )), + WorkletGlobalScopeType::Paint => DomRoot::upcast(PaintWorkletGlobalScope::new( + runtime, + pipeline_id, + base_url, + executor, + init, + )), + } + } +} + +/// A task which can be performed in the context of a worklet global. +pub enum WorkletTask { + Test(TestWorkletTask), + Paint(PaintWorkletTask), +} diff --git a/components/script/dom/xmldocument.rs b/components/script/dom/xmldocument.rs index 59c6739fb2d..f4908922ec3 100644 --- a/components/script/dom/xmldocument.rs +++ b/components/script/dom/xmldocument.rs @@ -1,23 +1,25 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use core::nonzero::NonZero; -use document_loader::DocumentLoader; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::XMLDocumentBinding::{self, XMLDocumentMethods}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::bindings::str::DOMString; -use dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; -use dom::location::Location; -use dom::node::Node; -use dom::window::Window; +use crate::document_loader::DocumentLoader; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; +use crate::dom::bindings::codegen::Bindings::XMLDocumentBinding::XMLDocumentMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument}; +use crate::dom::location::Location; +use crate::dom::node::Node; +use crate::dom::window::Window; +use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{JSContext, JSObject}; +use js::jsapi::JSObject; +use mime::Mime; use script_traits::DocumentActivity; use servo_url::{MutableOrigin, ServoUrl}; +use std::ptr::NonNull; // https://dom.spec.whatwg.org/#xmldocument #[dom_struct] @@ -26,56 +28,64 @@ pub struct XMLDocument { } impl XMLDocument { - fn new_inherited(window: &Window, - has_browsing_context: HasBrowsingContext, - url: Option<ServoUrl>, - origin: MutableOrigin, - is_html_document: IsHTMLDocument, - content_type: Option<DOMString>, - last_modified: Option<String>, - activity: DocumentActivity, - source: DocumentSource, - doc_loader: DocumentLoader) -> XMLDocument { + fn new_inherited( + window: &Window, + has_browsing_context: HasBrowsingContext, + url: Option<ServoUrl>, + origin: MutableOrigin, + is_html_document: IsHTMLDocument, + content_type: Option<Mime>, + last_modified: Option<String>, + activity: DocumentActivity, + source: DocumentSource, + doc_loader: DocumentLoader, + ) -> XMLDocument { XMLDocument { - document: Document::new_inherited(window, - has_browsing_context, - url, - origin, - is_html_document, - content_type, - last_modified, - activity, - source, - doc_loader, - None, - None), + document: Document::new_inherited( + window, + has_browsing_context, + url, + origin, + is_html_document, + content_type, + last_modified, + activity, + source, + doc_loader, + None, + None, + Default::default(), + ), } } - pub fn new(window: &Window, - has_browsing_context: HasBrowsingContext, - url: Option<ServoUrl>, - origin: MutableOrigin, - doctype: IsHTMLDocument, - content_type: Option<DOMString>, - last_modified: Option<String>, - activity: DocumentActivity, - source: DocumentSource, - doc_loader: DocumentLoader) - -> Root<XMLDocument> { + pub fn new( + window: &Window, + has_browsing_context: HasBrowsingContext, + url: Option<ServoUrl>, + origin: MutableOrigin, + doctype: IsHTMLDocument, + content_type: Option<Mime>, + last_modified: Option<String>, + activity: DocumentActivity, + source: DocumentSource, + doc_loader: DocumentLoader, + ) -> DomRoot<XMLDocument> { let doc = reflect_dom_object( - box XMLDocument::new_inherited(window, - has_browsing_context, - url, - origin, - doctype, - content_type, - last_modified, - activity, - source, - doc_loader), + Box::new(XMLDocument::new_inherited( + window, + has_browsing_context, + url, + origin, + doctype, + content_type, + last_modified, + activity, + source, + doc_loader, + )), window, - XMLDocumentBinding::Wrap); + ); { let node = doc.upcast::<Node>(); node.set_owner_doc(&doc.document); @@ -86,7 +96,7 @@ impl XMLDocument { impl XMLDocumentMethods for XMLDocument { // https://html.spec.whatwg.org/multipage/#dom-document-location - fn GetLocation(&self) -> Option<Root<Location>> { + fn GetLocation(&self) -> Option<DomRoot<Location>> { self.upcast::<Document>().GetLocation() } @@ -95,9 +105,8 @@ impl XMLDocumentMethods for XMLDocument { self.upcast::<Document>().SupportedPropertyNames() } - #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:dom-document-nameditem-filter - unsafe fn NamedGetter(&self, _cx: *mut JSContext, name: DOMString) -> Option<NonZero<*mut JSObject>> { + fn NamedGetter(&self, _cx: JSContext, name: DOMString) -> Option<NonNull<JSObject>> { self.upcast::<Document>().NamedGetter(_cx, name) } } diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 362dad8a925..772e00982be 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -1,80 +1,82 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use document_loader::DocumentLoader; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BlobBinding::BlobBinding::BlobMethods; -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::Bindings::XMLHttpRequestBinding; -use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit; -use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestMethods; -use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType; -use dom::bindings::codegen::UnionTypes::DocumentOrBodyInit; -use dom::bindings::conversions::ToJSValConvertible; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, MutNullableJS, Root}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::str::{ByteString, DOMString, USVString, is_token}; -use dom::blob::{Blob, BlobImpl}; -use dom::document::{Document, HasBrowsingContext, IsHTMLDocument}; -use dom::document::DocumentSource; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::eventtarget::EventTarget; -use dom::formdata::FormData; -use dom::globalscope::GlobalScope; -use dom::headers::is_forbidden_header_name; -use dom::htmlformelement::{encode_multipart_form_data, generate_boundary}; -use dom::node::Node; -use dom::progressevent::ProgressEvent; -use dom::servoparser::ServoParser; -use dom::urlsearchparams::URLSearchParams; -use dom::window::Window; -use dom::workerglobalscope::WorkerGlobalScope; -use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget; -use dom::xmlhttprequestupload::XMLHttpRequestUpload; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::body::{BodySource, Extractable, ExtractedBody}; +use crate::document_loader::DocumentLoader; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestMethods; +use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType; +use crate::dom::bindings::codegen::UnionTypes::DocumentOrXMLHttpRequestBodyInit; +use crate::dom::bindings::conversions::ToJSValConvertible; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::{is_token, ByteString, DOMString, USVString}; +use crate::dom::blob::{normalize_type_string, Blob}; +use crate::dom::document::DocumentSource; +use crate::dom::document::{Document, HasBrowsingContext, IsHTMLDocument}; +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_header_name}; +use crate::dom::node::Node; +use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::progressevent::ProgressEvent; +use crate::dom::readablestream::ReadableStream; +use crate::dom::servoparser::ServoParser; +use crate::dom::window::Window; +use crate::dom::workerglobalscope::WorkerGlobalScope; +use crate::dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget; +use crate::dom::xmlhttprequestupload::XMLHttpRequestUpload; +use crate::fetch::FetchCanceller; +use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; +use crate::script_runtime::JSContext; +use crate::task_source::networking::NetworkingTaskSource; +use crate::task_source::TaskSourceName; +use crate::timers::{OneshotTimerCallback, OneshotTimerHandle}; use dom_struct::dom_struct; -use encoding::all::UTF_8; -use encoding::label::encoding_from_whatwg_label; -use encoding::types::{DecoderTrap, EncoderTrap, Encoding, EncodingRef}; -use euclid::length::Length; +use encoding_rs::{Encoding, UTF_8}; +use euclid::Length; +use headers::{ContentLength, ContentType, HeaderMapExt}; use html5ever::serialize; use html5ever::serialize::SerializeOpts; -use hyper::header::{ContentLength, ContentType, ContentEncoding}; -use hyper::header::Headers; -use hyper::method::Method; -use hyper::mime::{self, Attr as MimeAttr, Mime, Value as MimeValue}; +use http::header::{self, HeaderMap, HeaderName, HeaderValue}; +use hyper::Method; use hyper_serde::Serde; use ipc_channel::ipc; use ipc_channel::router::ROUTER; -use js::jsapi::{Heap, JSContext, JS_ParseJSON}; use js::jsapi::JS_ClearPendingException; +use js::jsapi::{Heap, JSObject}; use js::jsval::{JSVal, NullValue, UndefinedValue}; -use net_traits::{FetchMetadata, FilteredMetadata}; -use net_traits::{FetchResponseListener, NetworkError, ReferrerPolicy}; -use net_traits::CoreResourceMsg::Fetch; -use net_traits::request::{CredentialsMode, Destination, RequestInit, RequestMode}; +use js::rust::wrappers::JS_ParseJSON; +use js::typedarray::{ArrayBuffer, CreateWith}; +use mime::{self, Mime, Name}; +use net_traits::request::{CredentialsMode, Destination, Referrer, RequestBuilder, RequestMode}; use net_traits::trim_http_whitespace; -use network_listener::{NetworkListener, PreInvoke}; +use net_traits::CoreResourceMsg::Fetch; +use net_traits::{FetchChannels, FetchMetadata, FilteredMetadata}; +use net_traits::{FetchResponseListener, NetworkError, ReferrerPolicy}; +use net_traits::{ResourceFetchTiming, ResourceTimingType}; +use script_traits::serializable::BlobImpl; use script_traits::DocumentActivity; use servo_atoms::Atom; -use servo_config::prefs::PREFS; use servo_url::ServoUrl; -use std::ascii::AsciiExt; use std::borrow::ToOwned; use std::cell::Cell; +use std::cmp; use std::default::Default; -use std::str; +use std::ptr; +use std::ptr::NonNull; +use std::slice; +use std::str::{self, FromStr}; use std::sync::{Arc, Mutex}; -use task_source::networking::NetworkingTaskSource; -use time; -use timers::{OneshotTimerCallback, OneshotTimerHandle}; use url::Position; -#[derive(JSTraceable, PartialEq, Copy, Clone, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] enum XMLHttpRequestState { Unsent = 0, Opened = 1, @@ -83,7 +85,7 @@ enum XMLHttpRequestState { Done = 4, } -#[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)] +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub struct GenerationId(u32); /// Closure of required data for each async network event that comprises the @@ -91,16 +93,17 @@ pub struct GenerationId(u32); struct XHRContext { xhr: TrustedXHRAddress, gen_id: GenerationId, - buf: DOMRefCell<Vec<u8>>, - sync_status: DOMRefCell<Option<ErrorResult>>, + sync_status: DomRefCell<Option<ErrorResult>>, + resource_timing: ResourceFetchTiming, + url: ServoUrl, } #[derive(Clone)] pub enum XHRProgress { /// Notify that headers have been received - HeadersReceived(GenerationId, Option<Headers>, Option<(u16, Vec<u8>)>), + HeadersReceived(GenerationId, Option<HeaderMap>, Option<(u16, Vec<u8>)>), /// Partial progress (after receiving headers), containing portion of the response - Loading(GenerationId, ByteString), + Loading(GenerationId, Vec<u8>), /// Loading is done Done(GenerationId), /// There was an error (only Error::Abort, Error::Timeout or Error::Network is used) @@ -113,7 +116,7 @@ impl XHRProgress { XHRProgress::HeadersReceived(id, _, _) | XHRProgress::Loading(id, _) | XHRProgress::Done(id) | - XHRProgress::Errored(id, _) => id + XHRProgress::Errored(id, _) => id, } } } @@ -124,50 +127,53 @@ pub struct XMLHttpRequest { ready_state: Cell<XMLHttpRequestState>, timeout: Cell<u32>, with_credentials: Cell<bool>, - upload: JS<XMLHttpRequestUpload>, - response_url: DOMRefCell<String>, + upload: Dom<XMLHttpRequestUpload>, + response_url: DomRefCell<String>, status: Cell<u16>, - status_text: DOMRefCell<ByteString>, - response: DOMRefCell<ByteString>, + status_text: DomRefCell<ByteString>, + response: DomRefCell<Vec<u8>>, response_type: Cell<XMLHttpRequestResponseType>, - response_xml: MutNullableJS<Document>, - response_blob: MutNullableJS<Blob>, - #[ignore_heap_size_of = "Defined in rust-mozjs"] + response_xml: MutNullableDom<Document>, + response_blob: MutNullableDom<Blob>, + #[ignore_malloc_size_of = "mozjs"] + response_arraybuffer: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "Defined in rust-mozjs"] response_json: Heap<JSVal>, - #[ignore_heap_size_of = "Defined in hyper"] - response_headers: DOMRefCell<Headers>, - #[ignore_heap_size_of = "Defined in hyper"] - override_mime_type: DOMRefCell<Option<Mime>>, - #[ignore_heap_size_of = "Defined in rust-encoding"] - override_charset: DOMRefCell<Option<EncodingRef>>, + #[ignore_malloc_size_of = "Defined in hyper"] + response_headers: DomRefCell<HeaderMap>, + #[ignore_malloc_size_of = "Defined in hyper"] + override_mime_type: DomRefCell<Option<Mime>>, + override_charset: DomRefCell<Option<&'static Encoding>>, // Associated concepts - #[ignore_heap_size_of = "Defined in hyper"] - request_method: DOMRefCell<Method>, - request_url: DOMRefCell<Option<ServoUrl>>, - #[ignore_heap_size_of = "Defined in hyper"] - request_headers: DOMRefCell<Headers>, + #[ignore_malloc_size_of = "Defined in hyper"] + request_method: DomRefCell<Method>, + request_url: DomRefCell<Option<ServoUrl>>, + #[ignore_malloc_size_of = "Defined in hyper"] + request_headers: DomRefCell<HeaderMap>, request_body_len: Cell<usize>, sync: Cell<bool>, upload_complete: Cell<bool>, + upload_listener: Cell<bool>, send_flag: Cell<bool>, - timeout_cancel: DOMRefCell<Option<OneshotTimerHandle>>, + timeout_cancel: DomRefCell<Option<OneshotTimerHandle>>, fetch_time: Cell<i64>, generation_id: Cell<GenerationId>, response_status: Cell<Result<(), ()>>, - referrer_url: Option<ServoUrl>, + referrer: Referrer, referrer_policy: Option<ReferrerPolicy>, + canceller: DomRefCell<FetchCanceller>, } impl XMLHttpRequest { fn new_inherited(global: &GlobalScope) -> XMLHttpRequest { //TODO - update this when referrer policy implemented for workers - let (referrer_url, referrer_policy) = if let Some(window) = global.downcast::<Window>() { + let referrer_policy = if let Some(window) = global.downcast::<Window>() { let document = window.Document(); - (Some(document.url()), document.get_referrer_policy()) + document.get_referrer_policy() } else { - (None, None) + None }; XMLHttpRequest { @@ -175,43 +181,45 @@ impl XMLHttpRequest { ready_state: Cell::new(XMLHttpRequestState::Unsent), timeout: Cell::new(0u32), with_credentials: Cell::new(false), - upload: JS::from_ref(&*XMLHttpRequestUpload::new(global)), - response_url: DOMRefCell::new(String::new()), + upload: Dom::from_ref(&*XMLHttpRequestUpload::new(global)), + response_url: DomRefCell::new(String::new()), status: Cell::new(0), - status_text: DOMRefCell::new(ByteString::new(vec!())), - response: DOMRefCell::new(ByteString::new(vec!())), + status_text: DomRefCell::new(ByteString::new(vec![])), + response: DomRefCell::new(vec![]), response_type: Cell::new(XMLHttpRequestResponseType::_empty), response_xml: Default::default(), response_blob: Default::default(), + response_arraybuffer: Heap::default(), response_json: Heap::default(), - response_headers: DOMRefCell::new(Headers::new()), - override_mime_type: DOMRefCell::new(None), - override_charset: DOMRefCell::new(None), + response_headers: DomRefCell::new(HeaderMap::new()), + override_mime_type: DomRefCell::new(None), + override_charset: DomRefCell::new(None), - request_method: DOMRefCell::new(Method::Get), - request_url: DOMRefCell::new(None), - request_headers: DOMRefCell::new(Headers::new()), + request_method: DomRefCell::new(Method::GET), + request_url: DomRefCell::new(None), + request_headers: DomRefCell::new(HeaderMap::new()), request_body_len: Cell::new(0), sync: Cell::new(false), upload_complete: Cell::new(false), + upload_listener: Cell::new(false), send_flag: Cell::new(false), - timeout_cancel: DOMRefCell::new(None), + timeout_cancel: DomRefCell::new(None), fetch_time: Cell::new(0), generation_id: Cell::new(GenerationId(0)), response_status: Cell::new(Ok(())), - referrer_url: referrer_url, + referrer: global.get_referrer(), referrer_policy: referrer_policy, + canceller: DomRefCell::new(Default::default()), } } - pub fn new(global: &GlobalScope) -> Root<XMLHttpRequest> { - reflect_dom_object(box XMLHttpRequest::new_inherited(global), - global, - XMLHttpRequestBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<XMLHttpRequest> { + reflect_dom_object(Box::new(XMLHttpRequest::new_inherited(global)), global) } // https://xhr.spec.whatwg.org/#constructors - pub fn Constructor(global: &GlobalScope) -> Fallible<Root<XMLHttpRequest>> { + #[allow(non_snake_case)] + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<XMLHttpRequest>> { Ok(XMLHttpRequest::new(global)) } @@ -219,10 +227,13 @@ impl XMLHttpRequest { self.sync.get() && self.global().is::<Window>() } - fn initiate_async_xhr(context: Arc<Mutex<XHRContext>>, - task_source: NetworkingTaskSource, - global: &GlobalScope, - init: RequestInit) { + fn initiate_async_xhr( + context: Arc<Mutex<XHRContext>>, + task_source: NetworkingTaskSource, + global: &GlobalScope, + init: RequestBuilder, + cancellation_chan: ipc::IpcReceiver<()>, + ) { impl FetchResponseListener for XHRContext { fn process_request_body(&mut self) { // todo @@ -232,8 +243,7 @@ impl XMLHttpRequest { // todo } - fn process_response(&mut self, - metadata: Result<FetchMetadata, NetworkError>) { + fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { let xhr = self.xhr.root(); let rv = xhr.process_headers_available(self.gen_id, metadata); if rv.is_err() { @@ -241,15 +251,42 @@ impl XMLHttpRequest { } } - fn process_response_chunk(&mut self, mut chunk: Vec<u8>) { - self.buf.borrow_mut().append(&mut chunk); - self.xhr.root().process_data_available(self.gen_id, self.buf.borrow().clone()); + fn process_response_chunk(&mut self, chunk: Vec<u8>) { + self.xhr.root().process_data_available(self.gen_id, chunk); } - fn process_response_eof(&mut self, response: Result<(), NetworkError>) { - let rv = self.xhr.root().process_response_complete(self.gen_id, response); + fn process_response_eof( + &mut self, + response: Result<ResourceFetchTiming, NetworkError>, + ) { + let rv = self + .xhr + .root() + .process_response_complete(self.gen_id, response.map(|_| ())); *self.sync_status.borrow_mut() = Some(rv); } + + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } + + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } + + fn submit_resource_timing(&mut self) { + network_listener::submit_timing(self) + } + } + + impl ResourceTimingListener for XHRContext { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + (InitiatorType::XMLHttpRequest, self.url.clone()) + } + + fn resource_timing_global(&self) -> DomRoot<GlobalScope> { + self.xhr.root().global() + } } impl PreInvoke for XHRContext { @@ -259,21 +296,35 @@ impl XMLHttpRequest { } let (action_sender, action_receiver) = ipc::channel().unwrap(); + let listener = NetworkListener { context: context, task_source: task_source, - wrapper: Some(global.get_runnable_wrapper()) + canceller: Some(global.task_canceller(TaskSourceName::Networking)), }; - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_fetch(message.to().unwrap()); - }); - global.core_resource_thread().send(Fetch(init, action_sender)).unwrap(); + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + listener.notify_fetch(message.to().unwrap()); + }), + ); + global + .core_resource_thread() + .send(Fetch( + init, + FetchChannels::ResponseMsg(action_sender, Some(cancellation_chan)), + )) + .unwrap(); } } impl XMLHttpRequestMethods for XMLHttpRequest { // https://xhr.spec.whatwg.org/#handler-xhr-onreadystatechange - event_handler!(readystatechange, GetOnreadystatechange, SetOnreadystatechange); + event_handler!( + readystatechange, + GetOnreadystatechange, + SetOnreadystatechange + ); // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate fn ReadyState(&self) -> u16 { @@ -287,10 +338,16 @@ impl XMLHttpRequestMethods for XMLHttpRequest { } // https://xhr.spec.whatwg.org/#the-open()-method - fn Open_(&self, method: ByteString, url: USVString, async: bool, - username: Option<USVString>, password: Option<USVString>) -> ErrorResult { + fn Open_( + &self, + method: ByteString, + url: USVString, + asynch: bool, + username: Option<USVString>, + password: Option<USVString>, + ) -> ErrorResult { // Step 1 - if let Some(window) = Root::downcast::<Window>(self.global()) { + if let Some(window) = DomRoot::downcast::<Window>(self.global()) { if !window.Document().is_fully_active() { return Err(Error::InvalidState); } @@ -306,21 +363,20 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // despite the there being a rust-http method variant for them let upper = s.to_ascii_uppercase(); match &*upper { - "DELETE" | "GET" | "HEAD" | "OPTIONS" | - "POST" | "PUT" | "CONNECT" | "TRACE" | + "DELETE" | "GET" | "HEAD" | "OPTIONS" | "POST" | "PUT" | "CONNECT" | "TRACE" | "TRACK" => upper.parse().ok(), - _ => s.parse().ok() + _ => s.parse().ok(), } }); match maybe_method { // Step 4 - Some(Method::Connect) | Some(Method::Trace) => Err(Error::Security), - Some(Method::Extension(ref t)) if &**t == "TRACK" => Err(Error::Security), + Some(Method::CONNECT) | Some(Method::TRACE) => Err(Error::Security), + Some(ref t) if t.as_str() == "TRACK" => Err(Error::Security), Some(parsed_method) => { // Step 3 if !is_token(&method) { - return Err(Error::Syntax) + return Err(Error::Syntax); } // Step 2 @@ -329,23 +385,26 @@ impl XMLHttpRequestMethods for XMLHttpRequest { let mut parsed_url = match base.join(&url.0) { Ok(parsed) => parsed, // Step 7 - Err(_) => return Err(Error::Syntax) + Err(_) => return Err(Error::Syntax), }; // Step 9 if parsed_url.host().is_some() { if let Some(user_str) = username { parsed_url.set_username(&user_str.0).unwrap(); - let password = password.as_ref().map(|pass_str| &*pass_str.0); - parsed_url.set_password(password).unwrap(); + } + if let Some(pass_str) = password { + parsed_url.set_password(Some(&pass_str.0)).unwrap(); } } // Step 10 - if !async { + if !asynch { // FIXME: This should only happen if the global environment is a document environment - if self.timeout.get() != 0 || self.response_type.get() != XMLHttpRequestResponseType::_empty { - return Err(Error::InvalidAccess) + if self.timeout.get() != 0 || + self.response_type.get() != XMLHttpRequestResponseType::_empty + { + return Err(Error::InvalidAccess); } } // Step 11 - abort existing requests @@ -360,10 +419,11 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 12 *self.request_method.borrow_mut() = parsed_method; *self.request_url.borrow_mut() = Some(parsed_url); - self.sync.set(!async); - *self.request_headers.borrow_mut() = Headers::new(); + self.sync.set(!asynch); + *self.request_headers.borrow_mut() = HeaderMap::new(); self.send_flag.set(false); - *self.status_text.borrow_mut() = ByteString::new(vec!()); + self.upload_listener.set(false); + *self.status_text.borrow_mut() = ByteString::new(vec![]); self.status.set(0); // Step 13 @@ -375,7 +435,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 3 // This includes cases where as_str() returns None, and when is_token() returns false, // both of which indicate invalid extension method names - _ => Err(Error::Syntax) + _ => Err(Error::Syntax), } } @@ -405,27 +465,31 @@ impl XMLHttpRequestMethods for XMLHttpRequest { s } }, - None => unreachable!() + None => unreachable!(), }; - debug!("SetRequestHeader: name={:?}, value={:?}", name.as_str(), str::from_utf8(value).ok()); + debug!( + "SetRequestHeader: name={:?}, value={:?}", + name.as_str(), + str::from_utf8(value).ok() + ); let mut headers = self.request_headers.borrow_mut(); - // Step 6 - let value = match headers.get_raw(name_str) { + let value = match headers.get(name_str).map(HeaderValue::as_bytes) { Some(raw) => { - debug!("SetRequestHeader: old value = {:?}", raw[0]); - let mut buf = raw[0].clone(); + let mut buf = raw.to_vec(); buf.extend_from_slice(b", "); buf.extend_from_slice(value); - debug!("SetRequestHeader: new value = {:?}", buf); buf }, None => value.into(), }; - headers.set_raw(name_str.to_owned(), vec![value]); + headers.insert( + HeaderName::from_str(name_str).unwrap(), + HeaderValue::from_bytes(&value).unwrap(), + ); Ok(()) } @@ -477,17 +541,17 @@ impl XMLHttpRequestMethods for XMLHttpRequest { _ => { self.with_credentials.set(with_credentials); Ok(()) - } + }, } } // https://xhr.spec.whatwg.org/#the-upload-attribute - fn Upload(&self) -> Root<XMLHttpRequestUpload> { - Root::from_ref(&*self.upload) + fn Upload(&self) -> DomRoot<XMLHttpRequestUpload> { + DomRoot::from_ref(&*self.upload) } // https://xhr.spec.whatwg.org/#the-send()-method - fn Send(&self, data: Option<DocumentOrBodyInit>) -> ErrorResult { + fn Send(&self, data: Option<DocumentOrXMLHttpRequestBodyInit>) -> ErrorResult { // Step 1, 2 if self.ready_state.get() != XMLHttpRequestState::Opened || self.send_flag.get() { return Err(Error::InvalidState); @@ -495,43 +559,100 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 3 let data = match *self.request_method.borrow() { - Method::Get | Method::Head => None, - _ => data + Method::GET | Method::HEAD => None, + _ => data, }; // Step 4 (first half) - let extracted_or_serialized = match data { - Some(DocumentOrBodyInit::Document(ref doc)) => { - let data = Vec::from(try!(serialize_document(&doc)).as_ref()); + let mut extracted_or_serialized = match data { + Some(DocumentOrXMLHttpRequestBodyInit::Document(ref doc)) => { + let bytes = Vec::from(serialize_document(&doc)?.as_ref()); let content_type = if doc.is_html_document() { "text/html;charset=UTF-8" } else { "application/xml;charset=UTF-8" }; - Some((data, Some(DOMString::from(content_type)))) + let total_bytes = bytes.len(); + let global = self.global(); + let stream = ReadableStream::new_from_bytes(&global, bytes); + Some(ExtractedBody { + stream, + total_bytes: Some(total_bytes), + content_type: Some(DOMString::from(content_type)), + source: BodySource::Object, + }) + }, + Some(DocumentOrXMLHttpRequestBodyInit::Blob(ref b)) => { + let extracted_body = b.extract(&self.global()).expect("Couldn't extract body."); + if !extracted_body.in_memory() && self.sync.get() { + warn!("Sync XHR with not in-memory Blob as body not supported"); + None + } else { + Some(extracted_body) + } + }, + Some(DocumentOrXMLHttpRequestBodyInit::FormData(ref formdata)) => Some( + formdata + .extract(&self.global()) + .expect("Couldn't extract body."), + ), + Some(DocumentOrXMLHttpRequestBodyInit::String(ref str)) => { + Some(str.extract(&self.global()).expect("Couldn't extract body.")) + }, + Some(DocumentOrXMLHttpRequestBodyInit::URLSearchParams(ref urlsp)) => Some( + urlsp + .extract(&self.global()) + .expect("Couldn't extract body."), + ), + Some(DocumentOrXMLHttpRequestBodyInit::ArrayBuffer(ref typedarray)) => { + let bytes = typedarray.to_vec(); + let total_bytes = bytes.len(); + let global = self.global(); + let stream = ReadableStream::new_from_bytes(&global, bytes); + Some(ExtractedBody { + stream, + total_bytes: Some(total_bytes), + content_type: None, + source: BodySource::Object, + }) + }, + Some(DocumentOrXMLHttpRequestBodyInit::ArrayBufferView(ref typedarray)) => { + let bytes = typedarray.to_vec(); + let total_bytes = bytes.len(); + let global = self.global(); + let stream = ReadableStream::new_from_bytes(&global, bytes); + Some(ExtractedBody { + stream, + total_bytes: Some(total_bytes), + content_type: None, + source: BodySource::Object, + }) }, - Some(DocumentOrBodyInit::Blob(ref b)) => Some(b.extract()), - Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(formdata.extract()), - Some(DocumentOrBodyInit::String(ref str)) => Some(str.extract()), - Some(DocumentOrBodyInit::URLSearchParams(ref urlsp)) => Some(urlsp.extract()), None => None, }; - self.request_body_len.set(extracted_or_serialized.as_ref().map_or(0, |e| e.0.len())); + self.request_body_len.set( + extracted_or_serialized + .as_ref() + .map_or(0, |e| e.total_bytes.unwrap_or(0)), + ); + + // Step 5 + // If we dont have data to upload, we dont want to emit events + let has_handlers = self.upload.upcast::<EventTarget>().has_handlers(); + self.upload_listener.set(has_handlers && data.is_some()); // todo preserved headers? - // Step 6 - self.upload_complete.set(false); // Step 7 - self.upload_complete.set(match extracted_or_serialized { - None => true, - Some(ref e) if e.0.is_empty() => true, - _ => false - }); + self.upload_complete.set(false); // Step 8 + // FIXME handle the 'timed out flag' + // Step 9 + self.upload_complete.set(extracted_or_serialized.is_none()); + // Step 10 self.send_flag.set(true); - // Step 9 + // Step 11 if !self.sync.get() { // If one of the event handlers below aborts the fetch by calling // abort or open we will need the current generation id to detect it. @@ -542,18 +663,16 @@ impl XMLHttpRequestMethods for XMLHttpRequest { return Ok(()); } // Substep 2 - if !self.upload_complete.get() { - self.dispatch_upload_progress_event(atom!("loadstart"), Some(0)); + if !self.upload_complete.get() && self.upload_listener.get() { + self.dispatch_upload_progress_event(atom!("loadstart"), Ok(Some(0))); if self.generation_id.get() != gen_id { return Ok(()); } } - } - // Step 5 + // Step 6 //TODO - set referrer_policy/referrer_url in request - let has_handlers = self.upload.upcast::<EventTarget>().has_handlers(); let credentials_mode = if self.with_credentials.get() { CredentialsMode::Include } else { @@ -565,87 +684,94 @@ impl XMLHttpRequestMethods for XMLHttpRequest { unreachable!() }; - let bypass_cross_origin_check = { - // We want to be able to do cross-origin requests in browser.html. - // If the XHR happens in a top level window and the mozbrowser - // preference is enabled, we allow bypassing the CORS check. - // This is a temporary measure until we figure out Servo privilege - // story. See https://github.com/servo/servo/issues/9582 - if let Some(win) = Root::downcast::<Window>(self.global()) { - let is_root_pipeline = win.parent_info().is_none(); - is_root_pipeline && PREFS.is_mozbrowser_enabled() - } else { - false - } - }; - - let mut request = RequestInit { - method: self.request_method.borrow().clone(), - url: self.request_url.borrow().clone().unwrap(), - headers: (*self.request_headers.borrow()).clone(), - unsafe_request: true, - // XXXManishearth figure out how to avoid this clone - body: extracted_or_serialized.as_ref().map(|e| e.0.clone()), - // XXXManishearth actually "subresource", but it doesn't exist - // https://github.com/whatwg/xhr/issues/71 - destination: Destination::None, - synchronous: self.sync.get(), - mode: RequestMode::CorsMode, - use_cors_preflight: has_handlers, - credentials_mode: credentials_mode, - use_url_credentials: use_url_credentials, - origin: self.global().get_url(), - referrer_url: self.referrer_url.clone(), - referrer_policy: self.referrer_policy.clone(), - pipeline_id: Some(self.global().pipeline_id()), - .. RequestInit::default() + let content_type = match extracted_or_serialized.as_mut() { + Some(body) => body.content_type.take(), + None => None, }; - if bypass_cross_origin_check { - request.mode = RequestMode::Navigate; - } + let mut request = RequestBuilder::new( + self.request_url.borrow().clone().unwrap(), + self.referrer.clone(), + ) + .method(self.request_method.borrow().clone()) + .headers((*self.request_headers.borrow()).clone()) + .unsafe_request(true) + // XXXManishearth figure out how to avoid this clone + .body(extracted_or_serialized.map(|e| e.into_net_request_body().0)) + // XXXManishearth actually "subresource", but it doesn't exist + // https://github.com/whatwg/xhr/issues/71 + .destination(Destination::None) + .synchronous(self.sync.get()) + .mode(RequestMode::CorsMode) + .use_cors_preflight(self.upload_listener.get()) + .credentials_mode(credentials_mode) + .use_url_credentials(use_url_credentials) + .origin(self.global().origin().immutable().clone()) + .referrer_policy(self.referrer_policy.clone()) + .pipeline_id(Some(self.global().pipeline_id())); // step 4 (second half) - match extracted_or_serialized { - Some((_, ref content_type)) => { + match content_type { + Some(content_type) => { let encoding = match data { - Some(DocumentOrBodyInit::String(_)) | Some(DocumentOrBodyInit::Document(_)) => + Some(DocumentOrXMLHttpRequestBodyInit::String(_)) | + Some(DocumentOrXMLHttpRequestBodyInit::Document(_)) => // XHR spec differs from http, and says UTF-8 should be in capitals, // instead of "utf-8", which is what Hyper defaults to. So not // using content types provided by Hyper. - Some(MimeValue::Ext("UTF-8".to_string())), + { + Some("UTF-8") + }, _ => None, }; let mut content_type_set = false; - if let Some(ref ct) = *content_type { - if !request.headers.has::<ContentType>() { - request.headers.set_raw("content-type", vec![ct.bytes().collect()]); - content_type_set = true; - } + if !request.headers.contains_key(header::CONTENT_TYPE) { + request.headers.insert( + header::CONTENT_TYPE, + HeaderValue::from_str(&content_type).unwrap(), + ); + content_type_set = true; } if !content_type_set { - let ct = request.headers.get_mut::<ContentType>(); - if let Some(mut ct) = ct { + let ct = request.headers.typed_get::<ContentType>(); + if let Some(ct) = ct { if let Some(encoding) = encoding { - for param in &mut (ct.0).2 { - if param.0 == MimeAttr::Charset { - if !param.0.as_str().eq_ignore_ascii_case(encoding.as_str()) { - *param = (MimeAttr::Charset, encoding.clone()); + let mime: Mime = ct.into(); + for param in mime.params() { + if param.0 == mime::CHARSET { + if !param.1.as_ref().eq_ignore_ascii_case(encoding) { + let new_params: Vec<(Name, Name)> = mime + .params() + .filter(|p| p.0 != mime::CHARSET) + .map(|p| (p.0, p.1)) + .collect(); + + let new_mime = format!( + "{}/{}; charset={}{}{}", + mime.type_().as_ref(), + mime.subtype().as_ref(), + encoding, + if new_params.is_empty() { "" } else { "; " }, + new_params + .iter() + .map(|p| format!("{}={}", p.0, p.1)) + .collect::<Vec<String>>() + .join("; ") + ); + let new_mime: Mime = new_mime.parse().unwrap(); + request.headers.typed_insert(ContentType::from(new_mime)) } } } } } } - - } + }, _ => (), } - debug!("request.headers = {:?}", request.headers); - self.fetch_time.set(time::now().to_timespec().sec); let rv = self.fetch(request, &self.global()); @@ -668,18 +794,24 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 2 let state = self.ready_state.get(); if (state == XMLHttpRequestState::Opened && self.send_flag.get()) || - state == XMLHttpRequestState::HeadersReceived || - state == XMLHttpRequestState::Loading { + state == XMLHttpRequestState::HeadersReceived || + state == XMLHttpRequestState::Loading + { let gen_id = self.generation_id.get(); self.process_partial_response(XHRProgress::Errored(gen_id, Error::Abort)); // If open was called in one of the handlers invoked by the // above call then we should terminate the abort sequence if self.generation_id.get() != gen_id { - return + return; } } // Step 3 - self.ready_state.set(XMLHttpRequestState::Unsent); + if self.ready_state.get() == XMLHttpRequestState::Done { + self.change_ready_state(XMLHttpRequestState::Unsent); + self.response_status.set(Err(())); + self.response.borrow_mut().clear(); + self.response_headers.borrow_mut().clear(); + } } // https://xhr.spec.whatwg.org/#the-responseurl-attribute @@ -699,35 +831,76 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // https://xhr.spec.whatwg.org/#the-getresponseheader()-method fn GetResponseHeader(&self, name: ByteString) -> Option<ByteString> { - self.filter_response_headers().iter().find(|h| { - name.eq_ignore_case(&h.name().parse().unwrap()) - }).map(|h| { - ByteString::new(h.value_string().into_bytes()) - }) + let headers = self.filter_response_headers(); + let headers = headers.get_all(HeaderName::from_str(&name.as_str()?.to_lowercase()).ok()?); + let mut first = true; + let s = headers.iter().fold(Vec::new(), |mut vec, value| { + if !first { + vec.extend(", ".as_bytes()); + } + if let Ok(v) = str::from_utf8(value.as_bytes()).map(|s| s.trim().as_bytes()) { + vec.extend(v); + first = false; + } + vec + }); + + // There was no header with that name so we never got to change that value + if first { + None + } else { + Some(ByteString::new(s)) + } } // https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method fn GetAllResponseHeaders(&self) -> ByteString { - ByteString::new(self.filter_response_headers().to_string().into_bytes()) + let headers = self.filter_response_headers(); + let keys = headers.keys(); + let v = keys.fold(Vec::new(), |mut vec, k| { + let values = headers.get_all(k); + vec.extend(k.as_str().as_bytes()); + vec.extend(": ".as_bytes()); + let mut first = true; + for value in values { + if !first { + vec.extend(", ".as_bytes()); + first = false; + } + vec.extend(value.as_bytes()); + } + vec.extend("\r\n".as_bytes()); + vec + }); + + ByteString::new(v) } // https://xhr.spec.whatwg.org/#the-overridemimetype()-method fn OverrideMimeType(&self, mime: DOMString) -> ErrorResult { // Step 1 match self.ready_state.get() { - XMLHttpRequestState::Loading | XMLHttpRequestState::Done => return Err(Error::InvalidState), + XMLHttpRequestState::Loading | XMLHttpRequestState::Done => { + return Err(Error::InvalidState); + }, _ => {}, } // Step 2 - let override_mime = try!(mime.parse::<Mime>().map_err(|_| Error::Syntax)); + let override_mime = mime.parse::<Mime>().map_err(|_| Error::Syntax)?; // Step 3 - let mime_no_params = Mime(override_mime.clone().0, override_mime.clone().1, vec![]); + let mime_str = override_mime.as_ref(); + let mime_parts: Vec<&str> = mime_str.split(";").collect(); + let mime_no_params = if mime_parts.len() > 1 { + mime_parts[0].parse().unwrap() + } else { + override_mime.clone() + }; + *self.override_mime_type.borrow_mut() = Some(mime_no_params); // Step 4 - let value = override_mime.get_param(mime::Attr::Charset); - *self.override_charset.borrow_mut() = value.and_then(|value| { - encoding_from_whatwg_label(value) - }); + let value = override_mime.get_param(mime::CHARSET); + *self.override_charset.borrow_mut() = + value.and_then(|value| Encoding::for_label(value.as_ref().as_bytes())); Ok(()) } @@ -739,7 +912,9 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // https://xhr.spec.whatwg.org/#the-responsetype-attribute fn SetResponseType(&self, response_type: XMLHttpRequestResponseType) -> ErrorResult { // Step 1 - if self.global().is::<WorkerGlobalScope>() && response_type == XMLHttpRequestResponseType::Document { + if self.global().is::<WorkerGlobalScope>() && + response_type == XMLHttpRequestResponseType::Document + { return Ok(()); } match self.ready_state.get() { @@ -754,23 +929,25 @@ impl XMLHttpRequestMethods for XMLHttpRequest { self.response_type.set(response_type); Ok(()) } - } + }, } } #[allow(unsafe_code)] // https://xhr.spec.whatwg.org/#the-response-attribute - unsafe fn Response(&self, cx: *mut JSContext) -> JSVal { - rooted!(in(cx) let mut rval = UndefinedValue()); + fn Response(&self, cx: JSContext) -> JSVal { + rooted!(in(*cx) let mut rval = UndefinedValue()); match self.response_type.get() { - XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => { + XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => unsafe { let ready_state = self.ready_state.get(); // Step 2 - if ready_state == XMLHttpRequestState::Done || ready_state == XMLHttpRequestState::Loading { - self.text_response().to_jsval(cx, rval.handle_mut()); + if ready_state == XMLHttpRequestState::Done || + ready_state == XMLHttpRequestState::Loading + { + self.text_response().to_jsval(*cx, rval.handle_mut()); } else { - // Step 1 - "".to_jsval(cx, rval.handle_mut()); + // Step 1 + "".to_jsval(*cx, rval.handle_mut()); } }, // Step 1 @@ -778,19 +955,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest { return NullValue(); }, // Step 2 - XMLHttpRequestResponseType::Document => { - self.document_response().to_jsval(cx, rval.handle_mut()); + XMLHttpRequestResponseType::Document => unsafe { + self.document_response().to_jsval(*cx, rval.handle_mut()); }, - XMLHttpRequestResponseType::Json => { - self.json_response(cx).to_jsval(cx, rval.handle_mut()); + XMLHttpRequestResponseType::Json => unsafe { + self.json_response(cx).to_jsval(*cx, rval.handle_mut()); }, - XMLHttpRequestResponseType::Blob => { - self.blob_response().to_jsval(cx, rval.handle_mut()); + XMLHttpRequestResponseType::Blob => unsafe { + self.blob_response().to_jsval(*cx, rval.handle_mut()); + }, + XMLHttpRequestResponseType::Arraybuffer => match self.arraybuffer_response(cx) { + Some(js_object) => unsafe { js_object.to_jsval(*cx, rval.handle_mut()) }, + None => return NullValue(), }, - _ => { - // XXXManishearth handle other response types - self.response.borrow().to_jsval(cx, rval.handle_mut()); - } } rval.get() } @@ -801,58 +978,58 @@ impl XMLHttpRequestMethods for XMLHttpRequest { XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => { Ok(USVString(String::from(match self.ready_state.get() { // Step 3 - XMLHttpRequestState::Loading | XMLHttpRequestState::Done => self.text_response(), + XMLHttpRequestState::Loading | XMLHttpRequestState::Done => { + self.text_response() + }, // Step 2 - _ => "".to_owned() + _ => "".to_owned(), }))) }, // Step 1 - _ => Err(Error::InvalidState) + _ => Err(Error::InvalidState), } } // https://xhr.spec.whatwg.org/#the-responsexml-attribute - fn GetResponseXML(&self) -> Fallible<Option<Root<Document>>> { - // TODO(#2823): Until [Exposed] is implemented, this attribute needs to return null - // explicitly in the worker scope. - if self.global().is::<WorkerGlobalScope>() { - return Ok(None); - } - + fn GetResponseXML(&self) -> Fallible<Option<DomRoot<Document>>> { match self.response_type.get() { XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Document => { // Step 3 if let XMLHttpRequestState::Done = self.ready_state.get() { Ok(self.document_response()) } else { - // Step 2 + // Step 2 Ok(None) } - } + }, // Step 1 - _ => { Err(Error::InvalidState) } + _ => Err(Error::InvalidState), } } } pub type TrustedXHRAddress = Trusted<XMLHttpRequest>; - impl XMLHttpRequest { fn change_ready_state(&self, rs: XMLHttpRequestState) { - assert!(self.ready_state.get() != rs); + assert_ne!(self.ready_state.get(), rs); self.ready_state.set(rs); - let event = Event::new(&self.global(), - atom!("readystatechange"), - EventBubbles::DoesNotBubble, - EventCancelable::Cancelable); - event.fire(self.upcast()); + if rs != XMLHttpRequestState::Unsent { + let event = Event::new( + &self.global(), + atom!("readystatechange"), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable, + ); + event.fire(self.upcast()); + } } - fn process_headers_available(&self, - gen_id: GenerationId, - metadata: Result<FetchMetadata, NetworkError>) - -> Result<(), Error> { + fn process_headers_available( + &self, + gen_id: GenerationId, + metadata: Result<FetchMetadata, NetworkError>, + ) -> Result<(), Error> { let metadata = match metadata { Ok(meta) => match meta { FetchMetadata::Unfiltered(m) => m, @@ -860,8 +1037,8 @@ impl XMLHttpRequest { FilteredMetadata::Basic(m) => m, FilteredMetadata::Cors(m) => m, FilteredMetadata::Opaque => return Err(Error::Network), - FilteredMetadata::OpaqueRedirect => return Err(Error::Network) - } + FilteredMetadata::OpaqueRedirect(_) => return Err(Error::Network), + }, }, Err(_) => { self.process_partial_response(XHRProgress::Errored(gen_id, Error::Network)); @@ -875,16 +1052,20 @@ impl XMLHttpRequest { self.process_partial_response(XHRProgress::HeadersReceived( gen_id, metadata.headers.map(Serde::into_inner), - metadata.status)); + metadata.status, + )); Ok(()) } fn process_data_available(&self, gen_id: GenerationId, payload: Vec<u8>) { - self.process_partial_response(XHRProgress::Loading(gen_id, ByteString::new(payload))); + self.process_partial_response(XHRProgress::Loading(gen_id, payload)); } - fn process_response_complete(&self, gen_id: GenerationId, status: Result<(), NetworkError>) - -> ErrorResult { + fn process_response_complete( + &self, + gen_id: GenerationId, + status: Result<(), NetworkError>, + ) -> ErrorResult { match status { Ok(()) => { self.process_partial_response(XHRProgress::Done(gen_id)); @@ -893,7 +1074,7 @@ impl XMLHttpRequest { Err(_) => { self.process_partial_response(XHRProgress::Errored(gen_id, Error::Network)); Err(Error::Network) - } + }, } } @@ -928,12 +1109,12 @@ impl XMLHttpRequest { // Substep 1 self.upload_complete.set(true); // Substeps 2-4 - if !self.sync.get() { - self.dispatch_upload_progress_event(atom!("progress"), None); + if !self.sync.get() && self.upload_listener.get() { + self.dispatch_upload_progress_event(atom!("progress"), Ok(None)); return_if_fetch_was_terminated!(); - self.dispatch_upload_progress_event(atom!("load"), None); + self.dispatch_upload_progress_event(atom!("load"), Ok(None)); return_if_fetch_was_terminated!(); - self.dispatch_upload_progress_event(atom!("loadend"), None); + self.dispatch_upload_progress_event(atom!("loadend"), Ok(None)); return_if_fetch_was_terminated!(); } // Part of step 13, send() (processing response) @@ -943,19 +1124,37 @@ impl XMLHttpRequest { self.status.set(code); *self.status_text.borrow_mut() = ByteString::new(reason); }); - headers.as_ref().map(|h| *self.response_headers.borrow_mut() = h.clone()); - + headers + .as_ref() + .map(|h| *self.response_headers.borrow_mut() = h.clone()); + { + let len = headers.and_then(|h| h.typed_get::<ContentLength>()); + let mut response = self.response.borrow_mut(); + response.clear(); + if let Some(len) = len { + // don't attempt to prereserve more than 4 MB of memory, + // to avoid giving servers the ability to DOS the client by + // providing arbitrarily large content-lengths. + // + // this number is arbitrary, it's basically big enough that most + // XHR requests won't hit it, but not so big that it allows for DOS + let size = cmp::min(0b100_0000000000_0000000000, len.0 as usize); + + // preallocate the buffer + response.reserve(size); + } + } // Substep 3 if !self.sync.get() { self.change_ready_state(XMLHttpRequestState::HeadersReceived); } }, - XHRProgress::Loading(_, partial_response) => { + XHRProgress::Loading(_, mut partial_response) => { // For synchronous requests, this should not fire any events, and just store data // Part of step 11, send() (processing response body) // XXXManishearth handle errors, if any (substep 2) - *self.response.borrow_mut() = partial_response; + self.response.borrow_mut().append(&mut partial_response); if !self.sync.get() { if self.ready_state.get() == XMLHttpRequestState::HeadersReceived { self.ready_state.set(XMLHttpRequestState::Loading); @@ -964,18 +1163,22 @@ impl XMLHttpRequest { &self.global(), atom!("readystatechange"), EventBubbles::DoesNotBubble, - EventCancelable::Cancelable); + EventCancelable::Cancelable, + ); event.fire(self.upcast()); return_if_fetch_was_terminated!(); self.dispatch_response_progress_event(atom!("progress")); } }, XHRProgress::Done(_) => { - assert!(self.ready_state.get() == XMLHttpRequestState::HeadersReceived || + assert!( + self.ready_state.get() == XMLHttpRequestState::HeadersReceived || self.ready_state.get() == XMLHttpRequestState::Loading || - self.sync.get()); + self.sync.get() + ); self.cancel_timeout(); + self.canceller.borrow_mut().ignore(); // Part of step 11, send() (processing response end of file) // XXXManishearth handle errors, if any (substep 2) @@ -992,6 +1195,7 @@ impl XMLHttpRequest { }, XHRProgress::Errored(_, e) => { self.cancel_timeout(); + self.canceller.borrow_mut().ignore(); self.discard_subsequent_responses(); self.send_flag.set(false); @@ -1008,36 +1212,46 @@ impl XMLHttpRequest { let upload_complete = &self.upload_complete; if !upload_complete.get() { upload_complete.set(true); - self.dispatch_upload_progress_event(Atom::from(errormsg), None); - return_if_fetch_was_terminated!(); - self.dispatch_upload_progress_event(atom!("loadend"), None); - return_if_fetch_was_terminated!(); + if self.upload_listener.get() { + self.dispatch_upload_progress_event(Atom::from(errormsg), Err(())); + return_if_fetch_was_terminated!(); + self.dispatch_upload_progress_event(atom!("loadend"), Err(())); + return_if_fetch_was_terminated!(); + } } self.dispatch_response_progress_event(Atom::from(errormsg)); return_if_fetch_was_terminated!(); self.dispatch_response_progress_event(atom!("loadend")); - } + }, } } fn terminate_ongoing_fetch(&self) { + self.canceller.borrow_mut().cancel(); let GenerationId(prev_id) = self.generation_id.get(); self.generation_id.set(GenerationId(prev_id + 1)); self.response_status.set(Ok(())); } fn dispatch_progress_event(&self, upload: bool, type_: Atom, loaded: u64, total: Option<u64>) { - let (total_length, length_computable) = if self.response_headers.borrow().has::<ContentEncoding>() { + let (total_length, length_computable) = if self + .response_headers + .borrow() + .contains_key(header::CONTENT_ENCODING) + { (0, false) } else { (total.unwrap_or(0), total.is_some()) }; - let progressevent = ProgressEvent::new(&self.global(), - type_, - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable, - length_computable, loaded, - total_length); + let progressevent = ProgressEvent::new( + &self.global(), + type_, + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + length_computable, + loaded, + total_length, + ); let target = if upload { self.upload.upcast() } else { @@ -1046,18 +1260,31 @@ impl XMLHttpRequest { progressevent.upcast::<Event>().fire(target); } - fn dispatch_upload_progress_event(&self, type_: Atom, partial_load: Option<u64>) { - // If partial_load is None, loading has completed and we can just use the value from the request body + fn dispatch_upload_progress_event(&self, type_: Atom, partial_load: Result<Option<u64>, ()>) { + // If partial_load is Ok(None), loading has completed and we can just use the value from the request body + // If an error occured, we pass 0 for both loaded and total - let total = self.request_body_len.get() as u64; - self.dispatch_progress_event(true, type_, partial_load.unwrap_or(total), Some(total)); + let request_body_len = self.request_body_len.get() as u64; + let (loaded, total) = match partial_load { + Ok(l) => match l { + Some(loaded) => (loaded, Some(request_body_len)), + None => (request_body_len, Some(request_body_len)), + }, + Err(()) => (0, None), + }; + self.dispatch_progress_event(true, type_, loaded, total); } fn dispatch_response_progress_event(&self, type_: Atom) { let len = self.response.borrow().len() as u64; - let total = self.response_headers.borrow().get::<ContentLength>().map(|x| { **x as u64 }); + let total = self + .response_headers + .borrow() + .typed_get::<ContentLength>() + .map(|v| v.0); self.dispatch_progress_event(false, type_, len, total); } + fn set_timeout(&self, duration_ms: u32) { // Sets up the object to timeout in a given number of milliseconds // This will cancel all previous timeouts @@ -1085,73 +1312,126 @@ impl XMLHttpRequest { // According to Simon, decode() should never return an error, so unwrap()ing // the result should be fine. XXXManishearth have a closer look at this later // Step 1, 2, 6 - charset.decode(&self.response.borrow(), DecoderTrap::Replace).unwrap() + let response = self.response.borrow(); + let (text, _, _) = charset.decode(&response); + text.into_owned() } // https://xhr.spec.whatwg.org/#blob-response - fn blob_response(&self) -> Root<Blob> { + fn blob_response(&self) -> DomRoot<Blob> { // Step 1 if let Some(response) = self.response_blob.get() { return response; } // Step 2 - let mime = self.final_mime_type().as_ref().map(Mime::to_string).unwrap_or("".to_owned()); + let mime = self + .final_mime_type() + .as_ref() + .map(|m| normalize_type_string(&m.to_string())) + .unwrap_or("".to_owned()); // Step 3, 4 let bytes = self.response.borrow().to_vec(); - let blob = Blob::new(&self.global(), BlobImpl::new_from_bytes(bytes), mime); + let blob = Blob::new(&self.global(), BlobImpl::new_from_bytes(bytes, mime)); self.response_blob.set(Some(&blob)); blob } - // https://xhr.spec.whatwg.org/#document-response - fn document_response(&self) -> Option<Root<Document>> { + // https://xhr.spec.whatwg.org/#arraybuffer-response + #[allow(unsafe_code)] + fn arraybuffer_response(&self, cx: JSContext) -> Option<NonNull<JSObject>> { // Step 1 + let created = self.response_arraybuffer.get(); + if let Some(nonnull) = NonNull::new(created) { + return Some(nonnull); + } + + // Step 2 + let bytes = self.response.borrow(); + rooted!(in(*cx) let mut array_buffer = ptr::null_mut::<JSObject>()); + unsafe { + ArrayBuffer::create(*cx, CreateWith::Slice(&bytes), array_buffer.handle_mut()) + .ok() + .and_then(|()| { + self.response_arraybuffer.set(array_buffer.get()); + Some(NonNull::new_unchecked(array_buffer.get())) + }) + } + } + + // https://xhr.spec.whatwg.org/#document-response + fn document_response(&self) -> Option<DomRoot<Document>> { + // Caching: if we have existing response xml, redirect it directly let response = self.response_xml.get(); if response.is_some() { - return self.response_xml.get(); + return response; + } + + // Step 1 + if self.response_status.get().is_err() { + return None; } + // Step 2 let mime_type = self.final_mime_type(); - // TODO: prescan the response to determine encoding if final charset is null + // Step 5.3, 7 let charset = self.final_charset().unwrap_or(UTF_8); - let temp_doc: Root<Document>; + let temp_doc: DomRoot<Document>; match mime_type { - Some(Mime(mime::TopLevel::Text, mime::SubLevel::Html, _)) => { - // Step 5 + Some(ref mime) if mime.type_() == mime::TEXT && mime.subtype() == mime::HTML => { + // Step 4 if self.response_type.get() == XMLHttpRequestResponseType::_empty { return None; } else { - // Step 6 + // 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(); } }, // Step 7 - Some(Mime(mime::TopLevel::Text, mime::SubLevel::Xml, _)) | - Some(Mime(mime::TopLevel::Application, mime::SubLevel::Xml, _)) | None => { temp_doc = self.handle_xml(); + // 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(Mime(_, mime::SubLevel::Ext(sub), _)) => { - if sub.ends_with("+xml") { - temp_doc = self.handle_xml(); - } else { + Some(ref mime) + if (mime.type_() == mime::TEXT && mime.subtype() == mime::XML) || + (mime.type_() == mime::APPLICATION && mime.subtype() == mime::XML) || + mime.suffix() == Some(mime::XML) => + { + temp_doc = self.handle_xml(); + // 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 4 - _ => { return None; } } - // Step 9 + // Step 8 temp_doc.set_encoding(charset); - // Step 13 + + // Step 9 to 11 + // Done by handle_text_html and handle_xml + + // Step 12 self.response_xml.set(Some(&temp_doc)); return self.response_xml.get(); } #[allow(unsafe_code)] // https://xhr.spec.whatwg.org/#json-response - fn json_response(&self, cx: *mut JSContext) -> JSVal { + fn json_response(&self, cx: JSContext) -> JSVal { // Step 1 let response_json = self.response_json.get(); if !response_json.is_null_or_undefined() { @@ -1164,16 +1444,35 @@ impl XMLHttpRequest { return NullValue(); } // Step 4 - let json_text = UTF_8.decode(&bytes, DecoderTrap::Replace).unwrap(); - let json_text: Vec<u16> = json_text.encode_utf16().collect(); + fn decode_to_utf16_with_bom_removal(bytes: &[u8], encoding: &'static Encoding) -> Vec<u16> { + let mut decoder = encoding.new_decoder_with_bom_removal(); + let capacity = decoder + .max_utf16_buffer_length(bytes.len()) + .expect("Overflow"); + let mut utf16 = Vec::with_capacity(capacity); + let extra = unsafe { slice::from_raw_parts_mut(utf16.as_mut_ptr(), capacity) }; + let last = true; + let (_, read, written, _) = decoder.decode_to_utf16(bytes, extra, last); + assert_eq!(read, bytes.len()); + unsafe { utf16.set_len(written) } + utf16 + } + // https://xhr.spec.whatwg.org/#json-response refers to + // https://infra.spec.whatwg.org/#parse-json-from-bytes which refers to + // https://encoding.spec.whatwg.org/#utf-8-decode which means + // that the encoding is always UTF-8 and the UTF-8 BOM is removed, + // if present, but UTF-16BE/LE BOM must not be honored. + let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8); // Step 5 - rooted!(in(cx) let mut rval = UndefinedValue()); + rooted!(in(*cx) let mut rval = UndefinedValue()); unsafe { - if !JS_ParseJSON(cx, - json_text.as_ptr(), - json_text.len() as u32, - rval.handle_mut()) { - JS_ClearPendingException(cx); + if !JS_ParseJSON( + *cx, + json_text.as_ptr(), + json_text.len() as u32, + rval.handle_mut(), + ) { + JS_ClearPendingException(*cx); return NullValue(); } } @@ -1182,33 +1481,29 @@ impl XMLHttpRequest { self.response_json.get() } - fn document_text_html(&self) -> Root<Document> { + fn document_text_html(&self) -> DomRoot<Document> { let charset = self.final_charset().unwrap_or(UTF_8); let wr = self.global(); - let decoded = charset.decode(&self.response.borrow(), DecoderTrap::Replace).unwrap(); + let response = self.response.borrow(); + let (decoded, _, _) = charset.decode(&response); let document = self.new_doc(IsHTMLDocument::HTMLDocument); // TODO: Disable scripting while parsing - ServoParser::parse_html_document( - &document, - DOMString::from(decoded), - wr.get_url()); + ServoParser::parse_html_document(&document, Some(DOMString::from(decoded)), wr.get_url()); document } - fn handle_xml(&self) -> Root<Document> { + fn handle_xml(&self) -> DomRoot<Document> { let charset = self.final_charset().unwrap_or(UTF_8); let wr = self.global(); - let decoded = charset.decode(&self.response.borrow(), DecoderTrap::Replace).unwrap(); + let response = self.response.borrow(); + let (decoded, _, _) = charset.decode(&response); let document = self.new_doc(IsHTMLDocument::NonHTMLDocument); // TODO: Disable scripting while parsing - ServoParser::parse_xml_document( - &document, - DOMString::from(decoded), - wr.get_url()); + ServoParser::parse_xml_document(&document, Some(DOMString::from(decoded)), wr.get_url()); document } - fn new_doc(&self, is_html_document: IsHTMLDocument) -> Root<Document> { + fn new_doc(&self, is_html_document: IsHTMLDocument) -> DomRoot<Document> { let wr = self.global(); let win = wr.as_window(); let doc = win.Document(); @@ -1216,54 +1511,31 @@ impl XMLHttpRequest { let base = wr.get_url(); let parsed_url = match base.join(&self.ResponseURL().0) { Ok(parsed) => Some(parsed), - Err(_) => None // Step 7 + Err(_) => None, // Step 7 }; - let mime_type = self.final_mime_type(); - let content_type = mime_type.map(|mime|{ - DOMString::from(format!("{}", mime)) - }); - Document::new(win, - HasBrowsingContext::No, - parsed_url, - doc.origin().clone(), - is_html_document, - content_type, - None, - DocumentActivity::Inactive, - DocumentSource::FromParser, - docloader, - None, - None) + let content_type = self.final_mime_type(); + Document::new( + win, + HasBrowsingContext::No, + parsed_url, + doc.origin().clone(), + is_html_document, + content_type, + None, + DocumentActivity::Inactive, + DocumentSource::FromParser, + docloader, + None, + None, + Default::default(), + ) } - fn filter_response_headers(&self) -> Headers { + fn filter_response_headers(&self) -> HeaderMap { // https://fetch.spec.whatwg.org/#concept-response-header-list - use hyper::error::Result; - use hyper::header::{Header, HeaderFormat}; - use hyper::header::SetCookie; - use std::fmt; - - // a dummy header so we can use headers.remove::<SetCookie2>() - #[derive(Clone, Debug, HeapSizeOf)] - struct SetCookie2; - impl Header for SetCookie2 { - fn header_name() -> &'static str { - "set-cookie2" - } - - fn parse_header(_: &[Vec<u8>]) -> Result<SetCookie2> { - unimplemented!() - } - } - impl HeaderFormat for SetCookie2 { - fn fmt_header(&self, _f: &mut fmt::Formatter) -> fmt::Result { - unimplemented!() - } - } - let mut headers = self.response_headers.borrow().clone(); - headers.remove::<SetCookie>(); - headers.remove::<SetCookie2>(); + headers.remove(header::SET_COOKIE); + headers.remove(HeaderName::from_static("set-cookie2")); // XXXManishearth additional CORS filtering goes here headers } @@ -1272,31 +1544,40 @@ impl XMLHttpRequest { self.response_status.set(Err(())); } - fn fetch(&self, - init: RequestInit, - global: &GlobalScope) -> ErrorResult { + fn fetch(&self, init: RequestBuilder, global: &GlobalScope) -> ErrorResult { let xhr = Trusted::new(self); let context = Arc::new(Mutex::new(XHRContext { xhr: xhr, gen_id: self.generation_id.get(), - buf: DOMRefCell::new(vec!()), - sync_status: DOMRefCell::new(None), + sync_status: DomRefCell::new(None), + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), + url: init.url.clone(), })); let (task_source, script_port) = if self.sync.get() { let (tx, rx) = global.new_script_pair(); - (NetworkingTaskSource(tx), Some(rx)) + (NetworkingTaskSource(tx, global.pipeline_id()), Some(rx)) } else { (global.networking_task_source(), None) }; - XMLHttpRequest::initiate_async_xhr(context.clone(), task_source, - global, init); + let cancel_receiver = self.canceller.borrow_mut().initialize(); + + XMLHttpRequest::initiate_async_xhr( + context.clone(), + task_source, + global, + init, + cancel_receiver, + ); if let Some(script_port) = script_port { loop { - global.process_event(script_port.recv().unwrap()); + if !global.process_event(script_port.recv().unwrap()) { + // We're exiting. + return Err(Error::Abort); + } let context = context.lock().unwrap(); let sync_status = context.sync_status.borrow(); if let Some(ref status) = *sync_status { @@ -1307,37 +1588,47 @@ impl XMLHttpRequest { Ok(()) } - fn final_charset(&self) -> Option<EncodingRef> { + fn final_charset(&self) -> Option<&'static Encoding> { if self.override_charset.borrow().is_some() { self.override_charset.borrow().clone() } else { - match self.response_headers.borrow().get() { - Some(&ContentType(ref mime)) => { - let value = mime.get_param(mime::Attr::Charset); - value.and_then(|value|{ - encoding_from_whatwg_label(value) - }) - } - None => { None } + match self.response_headers.borrow().typed_get::<ContentType>() { + Some(ct) => { + let mime: Mime = ct.into(); + let value = mime.get_param(mime::CHARSET); + value.and_then(|value| Encoding::for_label(value.as_ref().as_bytes())) + }, + None => None, } } } + /// <https://xhr.spec.whatwg.org/#response-mime-type> + fn response_mime_type(&self) -> Option<Mime> { + return extract_mime_type(&self.response_headers.borrow()) + .map(|mime_as_bytes| { + String::from_utf8(mime_as_bytes) + .unwrap_or_default() + .parse() + .ok() + }) + .flatten() + .or(Some(mime::TEXT_XML)); + } + + /// <https://xhr.spec.whatwg.org/#final-mime-type> fn final_mime_type(&self) -> Option<Mime> { if self.override_mime_type.borrow().is_some() { self.override_mime_type.borrow().clone() } else { - match self.response_headers.borrow().get() { - Some(&ContentType(ref mime)) => { Some(mime.clone()) }, - None => { None } - } + return self.response_mime_type(); } } } -#[derive(JSTraceable, HeapSizeOf)] +#[derive(JSTraceable, MallocSizeOf)] pub struct XHRTimeoutCallback { - #[ignore_heap_size_of = "Because it is non-owning"] + #[ignore_malloc_size_of = "Because it is non-owning"] xhr: Trusted<XMLHttpRequest>, generation_id: GenerationId, } @@ -1351,47 +1642,6 @@ impl XHRTimeoutCallback { } } -pub trait Extractable { - fn extract(&self) -> (Vec<u8>, Option<DOMString>); -} - -impl Extractable for Blob { - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - let content_type = if self.Type().as_ref().is_empty() { - None - } else { - Some(self.Type()) - }; - let bytes = self.get_bytes().unwrap_or(vec![]); - (bytes, content_type) - } -} - - -impl Extractable for DOMString { - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - (UTF_8.encode(self, EncoderTrap::Replace).unwrap(), - Some(DOMString::from("text/plain;charset=UTF-8"))) - } -} - -impl Extractable for FormData { - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - let boundary = generate_boundary(); - let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), - UTF_8 as EncodingRef); - (bytes, Some(DOMString::from(format!("multipart/form-data;boundary={}", boundary)))) - } -} - -impl Extractable for URLSearchParams { - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - // Default encoding is UTF-8. - (self.serialize(None).into_bytes(), - Some(DOMString::from("application/x-www-form-urlencoded;charset=UTF-8"))) - } -} - fn serialize_document(doc: &Document) -> Fallible<DOMString> { let mut writer = vec![]; match serialize(&mut writer, &doc.upcast::<Node>(), SerializeOpts::default()) { @@ -1400,18 +1650,6 @@ fn serialize_document(doc: &Document) -> Fallible<DOMString> { } } -impl Extractable for BodyInit { - // https://fetch.spec.whatwg.org/#concept-bodyinit-extract - fn extract(&self) -> (Vec<u8>, Option<DOMString>) { - match *self { - BodyInit::String(ref s) => s.extract(), - BodyInit::URLSearchParams(ref usp) => usp.extract(), - BodyInit::Blob(ref b) => b.extract(), - BodyInit::FormData(ref formdata) => formdata.extract(), - } - } -} - /// Returns whether `bs` is a `field-value`, as defined by /// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32). pub fn is_field_value(slice: &[u8]) -> bool { @@ -1427,7 +1665,8 @@ pub fn is_field_value(slice: &[u8]) -> bool { slice.iter().all(|&x| { // http://tools.ietf.org/html/rfc2616#section-2.2 match x { - 13 => { // CR + 13 => { + // CR if prev == PreviousCharacter::Other || prev == PreviousCharacter::SPHT { prev = PreviousCharacter::CR; true @@ -1435,7 +1674,8 @@ pub fn is_field_value(slice: &[u8]) -> bool { false } }, - 10 => { // LF + 10 => { + // LF if prev == PreviousCharacter::CR { prev = PreviousCharacter::LF; true @@ -1443,7 +1683,8 @@ pub fn is_field_value(slice: &[u8]) -> bool { false } }, - 32 => { // SP + 32 => { + // SP if prev == PreviousCharacter::LF || prev == PreviousCharacter::SPHT { prev = PreviousCharacter::SPHT; true @@ -1457,7 +1698,8 @@ pub fn is_field_value(slice: &[u8]) -> bool { false } }, - 9 => { // HT + 9 => { + // HT if prev == PreviousCharacter::LF || prev == PreviousCharacter::SPHT { prev = PreviousCharacter::SPHT; true @@ -1465,13 +1707,13 @@ pub fn is_field_value(slice: &[u8]) -> bool { false } }, - 0...31 | 127 => false, // CTLs + 0..=31 | 127 => false, // CTLs x if x > 127 => false, // non ASCII _ if prev == PreviousCharacter::Other || prev == PreviousCharacter::SPHT => { prev = PreviousCharacter::Other; true }, - _ => false // Previous character was a CR/LF but not part of the [CRLF] (SP|HT) rule + _ => false, // Previous character was a CR/LF but not part of the [CRLF] (SP|HT) rule } }) } diff --git a/components/script/dom/xmlhttprequesteventtarget.rs b/components/script/dom/xmlhttprequesteventtarget.rs index 399b2f65945..44f0fb7c1fc 100644 --- a/components/script/dom/xmlhttprequesteventtarget.rs +++ b/components/script/dom/xmlhttprequesteventtarget.rs @@ -1,10 +1,9 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; -use dom::bindings::codegen::Bindings::XMLHttpRequestEventTargetBinding::XMLHttpRequestEventTargetMethods; -use dom::eventtarget::EventTarget; +use crate::dom::bindings::codegen::Bindings::XMLHttpRequestEventTargetBinding::XMLHttpRequestEventTargetMethods; +use crate::dom::eventtarget::EventTarget; use dom_struct::dom_struct; #[dom_struct] @@ -15,7 +14,7 @@ pub struct XMLHttpRequestEventTarget { impl XMLHttpRequestEventTarget { pub fn new_inherited() -> XMLHttpRequestEventTarget { XMLHttpRequestEventTarget { - eventtarget: EventTarget::new_inherited() + eventtarget: EventTarget::new_inherited(), } } } diff --git a/components/script/dom/xmlhttprequestupload.rs b/components/script/dom/xmlhttprequestupload.rs index 02a54001388..7e7b5893395 100644 --- a/components/script/dom/xmlhttprequestupload.rs +++ b/components/script/dom/xmlhttprequestupload.rs @@ -1,17 +1,16 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::XMLHttpRequestUploadBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::globalscope::GlobalScope; -use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget; use dom_struct::dom_struct; #[dom_struct] pub struct XMLHttpRequestUpload { - eventtarget: XMLHttpRequestEventTarget + eventtarget: XMLHttpRequestEventTarget, } impl XMLHttpRequestUpload { @@ -20,9 +19,7 @@ impl XMLHttpRequestUpload { eventtarget: XMLHttpRequestEventTarget::new_inherited(), } } - pub fn new(global: &GlobalScope) -> Root<XMLHttpRequestUpload> { - reflect_dom_object(box XMLHttpRequestUpload::new_inherited(), - global, - XMLHttpRequestUploadBinding::Wrap) + pub fn new(global: &GlobalScope) -> DomRoot<XMLHttpRequestUpload> { + reflect_dom_object(Box::new(XMLHttpRequestUpload::new_inherited()), global) } } diff --git a/components/script/dom/xmlserializer.rs b/components/script/dom/xmlserializer.rs new file mode 100644 index 00000000000..2b65d061fa6 --- /dev/null +++ b/components/script/dom/xmlserializer.rs @@ -0,0 +1,56 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::node::Node; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use xml5ever::serialize::{serialize, SerializeOpts, TraversalScope}; + +#[dom_struct] +pub struct XMLSerializer { + reflector_: Reflector, + window: Dom<Window>, +} + +impl XMLSerializer { + fn new_inherited(window: &Window) -> XMLSerializer { + XMLSerializer { + reflector_: Reflector::new(), + window: Dom::from_ref(window), + } + } + + pub fn new(window: &Window) -> DomRoot<XMLSerializer> { + reflect_dom_object(Box::new(XMLSerializer::new_inherited(window)), window) + } + + #[allow(non_snake_case)] + pub fn Constructor(window: &Window) -> Fallible<DomRoot<XMLSerializer>> { + Ok(XMLSerializer::new(window)) + } +} + +impl XMLSerializerMethods for XMLSerializer { + // https://w3c.github.io/DOM-Parsing/#the-xmlserializer-interface + fn SerializeToString(&self, root: &Node) -> Fallible<DOMString> { + let mut writer = vec![]; + match serialize( + &mut writer, + &root, + SerializeOpts { + traversal_scope: TraversalScope::IncludeNode, + }, + ) { + Ok(_) => Ok(DOMString::from(String::from_utf8(writer).unwrap())), + Err(_) => Err(Error::Type(String::from( + "root must be a Node or an Attr object", + ))), + } + } +} diff --git a/components/script/dom/xrcompositionlayer.rs b/components/script/dom/xrcompositionlayer.rs new file mode 100644 index 00000000000..5a4aa85d2fa --- /dev/null +++ b/components/script/dom/xrcompositionlayer.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::xrlayer::XRLayer; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRCompositionLayer { + xr_layer: XRLayer, +} diff --git a/components/script/dom/xrcubelayer.rs b/components/script/dom/xrcubelayer.rs new file mode 100644 index 00000000000..71296ddc238 --- /dev/null +++ b/components/script/dom/xrcubelayer.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::xrcompositionlayer::XRCompositionLayer; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRCubeLayer { + composition_layer: XRCompositionLayer, +} diff --git a/components/script/dom/xrcylinderlayer.rs b/components/script/dom/xrcylinderlayer.rs new file mode 100644 index 00000000000..2abe935f525 --- /dev/null +++ b/components/script/dom/xrcylinderlayer.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::xrcompositionlayer::XRCompositionLayer; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRCylinderLayer { + composition_layer: XRCompositionLayer, +} diff --git a/components/script/dom/xrequirectlayer.rs b/components/script/dom/xrequirectlayer.rs new file mode 100644 index 00000000000..d6da61a5a9c --- /dev/null +++ b/components/script/dom/xrequirectlayer.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::xrcompositionlayer::XRCompositionLayer; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XREquirectLayer { + composition_layer: XRCompositionLayer, +} diff --git a/components/script/dom/xrframe.rs b/components/script/dom/xrframe.rs new file mode 100644 index 00000000000..914d96333b5 --- /dev/null +++ b/components/script/dom/xrframe.rs @@ -0,0 +1,178 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRFrameBinding::XRFrameMethods; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrhittestresult::XRHitTestResult; +use crate::dom::xrhittestsource::XRHitTestSource; +use crate::dom::xrjointpose::XRJointPose; +use crate::dom::xrjointspace::XRJointSpace; +use crate::dom::xrpose::XRPose; +use crate::dom::xrreferencespace::XRReferenceSpace; +use crate::dom::xrsession::{ApiPose, XRSession}; +use crate::dom::xrspace::XRSpace; +use crate::dom::xrviewerpose::XRViewerPose; +use dom_struct::dom_struct; +use std::cell::Cell; +use webxr_api::Frame; +use webxr_api::LayerId; +use webxr_api::SubImages; + +#[dom_struct] +pub struct XRFrame { + reflector_: Reflector, + session: Dom<XRSession>, + #[ignore_malloc_size_of = "defined in webxr_api"] + data: Frame, + active: Cell<bool>, + animation_frame: Cell<bool>, +} + +impl XRFrame { + fn new_inherited(session: &XRSession, data: Frame) -> XRFrame { + XRFrame { + reflector_: Reflector::new(), + session: Dom::from_ref(session), + data, + active: Cell::new(false), + animation_frame: Cell::new(false), + } + } + + pub fn new(global: &GlobalScope, session: &XRSession, data: Frame) -> DomRoot<XRFrame> { + reflect_dom_object(Box::new(XRFrame::new_inherited(session, data)), global) + } + + /// https://immersive-web.github.io/webxr/#xrframe-active + pub fn set_active(&self, active: bool) { + self.active.set(active); + } + + /// https://immersive-web.github.io/webxr/#xrframe-animationframe + pub fn set_animation_frame(&self, animation_frame: bool) { + self.animation_frame.set(animation_frame); + } + + pub fn get_pose(&self, space: &XRSpace) -> Option<ApiPose> { + space.get_pose(&self.data) + } + + pub fn get_sub_images(&self, layer_id: LayerId) -> Option<&SubImages> { + self.data + .sub_images + .iter() + .filter(|sub_images| sub_images.layer_id == layer_id) + .next() + } +} + +impl XRFrameMethods for XRFrame { + /// https://immersive-web.github.io/webxr/#dom-xrframe-session + fn Session(&self) -> DomRoot<XRSession> { + DomRoot::from_ref(&self.session) + } + + /// https://immersive-web.github.io/webxr/#dom-xrframe-getviewerpose + fn GetViewerPose( + &self, + reference: &XRReferenceSpace, + ) -> Result<Option<DomRoot<XRViewerPose>>, Error> { + if self.session != reference.upcast::<XRSpace>().session() { + return Err(Error::InvalidState); + } + + if !self.active.get() || !self.animation_frame.get() { + return Err(Error::InvalidState); + } + + let to_base = if let Some(to_base) = reference.get_base_transform(&self.data) { + to_base + } else { + return Ok(None); + }; + let viewer_pose = if let Some(pose) = self.data.pose.as_ref() { + pose + } else { + return Ok(None); + }; + Ok(Some(XRViewerPose::new( + &self.global(), + &self.session, + to_base, + viewer_pose, + ))) + } + + /// https://immersive-web.github.io/webxr/#dom-xrframe-getpose + fn GetPose( + &self, + space: &XRSpace, + relative_to: &XRSpace, + ) -> Result<Option<DomRoot<XRPose>>, Error> { + if self.session != space.session() || self.session != relative_to.session() { + return Err(Error::InvalidState); + } + if !self.active.get() { + return Err(Error::InvalidState); + } + let space = if let Some(space) = self.get_pose(space) { + space + } else { + return Ok(None); + }; + let relative_to = if let Some(r) = self.get_pose(relative_to) { + r + } else { + return Ok(None); + }; + let pose = relative_to.inverse().pre_transform(&space); + Ok(Some(XRPose::new(&self.global(), pose))) + } + + /// https://immersive-web.github.io/webxr/#dom-xrframe-getpose + fn GetJointPose( + &self, + space: &XRJointSpace, + relative_to: &XRSpace, + ) -> Result<Option<DomRoot<XRJointPose>>, Error> { + if self.session != space.upcast::<XRSpace>().session() || + self.session != relative_to.session() + { + return Err(Error::InvalidState); + } + if !self.active.get() { + return Err(Error::InvalidState); + } + let joint_frame = if let Some(frame) = space.frame(&self.data) { + frame + } else { + return Ok(None); + }; + let relative_to = if let Some(r) = self.get_pose(relative_to) { + r + } else { + return Ok(None); + }; + let pose = relative_to.inverse().pre_transform(&joint_frame.pose); + Ok(Some(XRJointPose::new( + &self.global(), + pose.cast_unit(), + Some(joint_frame.radius), + ))) + } + + /// https://immersive-web.github.io/hit-test/#dom-xrframe-gethittestresults + fn GetHitTestResults(&self, source: &XRHitTestSource) -> Vec<DomRoot<XRHitTestResult>> { + self.data + .hit_test_results + .iter() + .filter(|r| r.id == source.id()) + .map(|r| XRHitTestResult::new(&self.global(), *r, self)) + .collect() + } +} diff --git a/components/script/dom/xrhand.rs b/components/script/dom/xrhand.rs new file mode 100644 index 00000000000..a7457526242 --- /dev/null +++ b/components/script/dom/xrhand.rs @@ -0,0 +1,88 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRHandBinding::{XRHandConstants, XRHandMethods}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrinputsource::XRInputSource; +use crate::dom::xrjointspace::XRJointSpace; +use dom_struct::dom_struct; +use webxr_api::{FingerJoint, Hand, Joint}; + +#[dom_struct] +pub struct XRHand { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webxr"] + source: Dom<XRInputSource>, + #[ignore_malloc_size_of = "partially defind in webxr"] + spaces: Hand<Dom<XRJointSpace>>, +} + +impl XRHand { + fn new_inherited(source: &XRInputSource, spaces: &Hand<DomRoot<XRJointSpace>>) -> XRHand { + XRHand { + reflector_: Reflector::new(), + source: Dom::from_ref(source), + spaces: spaces.map(|j, _| j.as_ref().map(|j| Dom::from_ref(&**j))), + } + } + + pub fn new(global: &GlobalScope, source: &XRInputSource, support: Hand<()>) -> DomRoot<XRHand> { + let id = source.id(); + let session = source.session(); + let spaces = support + .map(|field, joint| field.map(|_| XRJointSpace::new(global, session, id, joint))); + reflect_dom_object(Box::new(XRHand::new_inherited(source, &spaces)), global) + } +} + +impl XRHandMethods for XRHand { + /// https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md + fn Length(&self) -> i32 { + XRHandConstants::LITTLE_PHALANX_TIP as i32 + 1 + } + + /// https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md + fn IndexedGetter(&self, joint_index: u32) -> Option<DomRoot<XRJointSpace>> { + let joint = match joint_index { + XRHandConstants::WRIST => Joint::Wrist, + XRHandConstants::THUMB_METACARPAL => Joint::ThumbMetacarpal, + XRHandConstants::THUMB_PHALANX_PROXIMAL => Joint::ThumbPhalanxProximal, + XRHandConstants::THUMB_PHALANX_DISTAL => Joint::ThumbPhalanxDistal, + XRHandConstants::THUMB_PHALANX_TIP => Joint::ThumbPhalanxTip, + XRHandConstants::INDEX_METACARPAL => Joint::Index(FingerJoint::Metacarpal), + XRHandConstants::INDEX_PHALANX_PROXIMAL => Joint::Index(FingerJoint::PhalanxProximal), + XRHandConstants::INDEX_PHALANX_INTERMEDIATE => { + Joint::Index(FingerJoint::PhalanxIntermediate) + }, + XRHandConstants::INDEX_PHALANX_DISTAL => Joint::Index(FingerJoint::PhalanxDistal), + XRHandConstants::INDEX_PHALANX_TIP => Joint::Index(FingerJoint::PhalanxTip), + XRHandConstants::MIDDLE_METACARPAL => Joint::Middle(FingerJoint::Metacarpal), + XRHandConstants::MIDDLE_PHALANX_PROXIMAL => Joint::Middle(FingerJoint::PhalanxProximal), + XRHandConstants::MIDDLE_PHALANX_INTERMEDIATE => { + Joint::Middle(FingerJoint::PhalanxIntermediate) + }, + XRHandConstants::MIDDLE_PHALANX_DISTAL => Joint::Middle(FingerJoint::PhalanxDistal), + XRHandConstants::MIDDLE_PHALANX_TIP => Joint::Middle(FingerJoint::PhalanxTip), + XRHandConstants::RING_METACARPAL => Joint::Ring(FingerJoint::Metacarpal), + XRHandConstants::RING_PHALANX_PROXIMAL => Joint::Ring(FingerJoint::PhalanxProximal), + XRHandConstants::RING_PHALANX_INTERMEDIATE => { + Joint::Ring(FingerJoint::PhalanxIntermediate) + }, + XRHandConstants::RING_PHALANX_DISTAL => Joint::Ring(FingerJoint::PhalanxDistal), + XRHandConstants::RING_PHALANX_TIP => Joint::Ring(FingerJoint::PhalanxTip), + XRHandConstants::LITTLE_METACARPAL => Joint::Little(FingerJoint::Metacarpal), + XRHandConstants::LITTLE_PHALANX_PROXIMAL => Joint::Little(FingerJoint::PhalanxProximal), + XRHandConstants::LITTLE_PHALANX_INTERMEDIATE => { + Joint::Little(FingerJoint::PhalanxIntermediate) + }, + XRHandConstants::LITTLE_PHALANX_DISTAL => Joint::Little(FingerJoint::PhalanxDistal), + XRHandConstants::LITTLE_PHALANX_TIP => Joint::Little(FingerJoint::PhalanxTip), + // XXXManishearth should this be a TypeError? + _ => return None, + }; + self.spaces.get(joint).map(|j| DomRoot::from_ref(&**j)) + } +} diff --git a/components/script/dom/xrhittestresult.rs b/components/script/dom/xrhittestresult.rs new file mode 100644 index 00000000000..be11f3dc7b6 --- /dev/null +++ b/components/script/dom/xrhittestresult.rs @@ -0,0 +1,51 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRHitTestResultBinding::XRHitTestResultMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrframe::XRFrame; +use crate::dom::xrpose::XRPose; +use crate::dom::xrspace::XRSpace; +use dom_struct::dom_struct; +use webxr_api::HitTestResult; + +#[dom_struct] +pub struct XRHitTestResult { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webxr"] + result: HitTestResult, + frame: Dom<XRFrame>, +} + +impl XRHitTestResult { + fn new_inherited(result: HitTestResult, frame: &XRFrame) -> XRHitTestResult { + XRHitTestResult { + reflector_: Reflector::new(), + result, + frame: Dom::from_ref(frame), + } + } + + pub fn new( + global: &GlobalScope, + result: HitTestResult, + frame: &XRFrame, + ) -> DomRoot<XRHitTestResult> { + reflect_dom_object( + Box::new(XRHitTestResult::new_inherited(result, frame)), + global, + ) + } +} + +impl XRHitTestResultMethods for XRHitTestResult { + // https://immersive-web.github.io/hit-test/#dom-xrhittestresult-getpose + fn GetPose(&self, base: &XRSpace) -> Option<DomRoot<XRPose>> { + let base = self.frame.get_pose(base)?; + let pose = base.inverse().pre_transform(&self.result.space); + Some(XRPose::new(&self.global(), pose.cast_unit())) + } +} diff --git a/components/script/dom/xrhittestsource.rs b/components/script/dom/xrhittestsource.rs new file mode 100644 index 00000000000..a665a9f1b71 --- /dev/null +++ b/components/script/dom/xrhittestsource.rs @@ -0,0 +1,51 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::XRHitTestSourceMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrsession::XRSession; +use dom_struct::dom_struct; +use webxr_api::HitTestId; + +#[dom_struct] +pub struct XRHitTestSource { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webxr"] + id: HitTestId, + session: Dom<XRSession>, +} + +impl XRHitTestSource { + fn new_inherited(id: HitTestId, session: &XRSession) -> XRHitTestSource { + XRHitTestSource { + reflector_: Reflector::new(), + id, + session: Dom::from_ref(session), + } + } + + pub fn new( + global: &GlobalScope, + id: HitTestId, + session: &XRSession, + ) -> DomRoot<XRHitTestSource> { + reflect_dom_object( + Box::new(XRHitTestSource::new_inherited(id, session)), + global, + ) + } + + pub fn id(&self) -> HitTestId { + self.id + } +} + +impl XRHitTestSourceMethods for XRHitTestSource { + // https://immersive-web.github.io/hit-test/#dom-xrhittestsource-cancel + fn Cancel(&self) { + self.session.with_session(|s| s.cancel_hit_test(self.id)); + } +} diff --git a/components/script/dom/xrinputsource.rs b/components/script/dom/xrinputsource.rs new file mode 100644 index 00000000000..248fb6d737a --- /dev/null +++ b/components/script/dom/xrinputsource.rs @@ -0,0 +1,132 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{ + XRHandedness, XRInputSourceMethods, XRTargetRayMode, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrhand::XRHand; +use crate::dom::xrsession::XRSession; +use crate::dom::xrspace::XRSpace; +use crate::realms::enter_realm; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use js::conversions::ToJSValConvertible; +use js::jsapi::Heap; +use js::jsval::{JSVal, UndefinedValue}; +use webxr_api::{Handedness, InputId, InputSource, TargetRayMode}; + +#[dom_struct] +pub struct XRInputSource { + reflector: Reflector, + session: Dom<XRSession>, + #[ignore_malloc_size_of = "Defined in rust-webxr"] + info: InputSource, + target_ray_space: MutNullableDom<XRSpace>, + grip_space: MutNullableDom<XRSpace>, + hand: MutNullableDom<XRHand>, + #[ignore_malloc_size_of = "mozjs"] + profiles: Heap<JSVal>, +} + +impl XRInputSource { + pub fn new_inherited(session: &XRSession, info: InputSource) -> XRInputSource { + XRInputSource { + reflector: Reflector::new(), + session: Dom::from_ref(session), + info, + target_ray_space: Default::default(), + grip_space: Default::default(), + hand: Default::default(), + profiles: Heap::default(), + } + } + + #[allow(unsafe_code)] + pub fn new( + global: &GlobalScope, + session: &XRSession, + info: InputSource, + ) -> DomRoot<XRInputSource> { + let source = reflect_dom_object( + Box::new(XRInputSource::new_inherited(session, info)), + global, + ); + + let _ac = enter_realm(&*global); + let cx = global.get_cx(); + unsafe { + rooted!(in(*cx) let mut profiles = UndefinedValue()); + source.info.profiles.to_jsval(*cx, profiles.handle_mut()); + source.profiles.set(profiles.get()); + } + source + } + + pub fn id(&self) -> InputId { + self.info.id + } + + pub fn session(&self) -> &XRSession { + &self.session + } +} + +impl XRInputSourceMethods for XRInputSource { + /// https://immersive-web.github.io/webxr/#dom-xrinputsource-handedness + fn Handedness(&self) -> XRHandedness { + match self.info.handedness { + Handedness::None => XRHandedness::None, + Handedness::Left => XRHandedness::Left, + Handedness::Right => XRHandedness::Right, + } + } + + /// https://immersive-web.github.io/webxr/#dom-xrinputsource-targetraymode + fn TargetRayMode(&self) -> XRTargetRayMode { + match self.info.target_ray_mode { + TargetRayMode::Gaze => XRTargetRayMode::Gaze, + TargetRayMode::TrackedPointer => XRTargetRayMode::Tracked_pointer, + TargetRayMode::Screen => XRTargetRayMode::Screen, + } + } + + /// https://immersive-web.github.io/webxr/#dom-xrinputsource-targetrayspace + fn TargetRaySpace(&self) -> DomRoot<XRSpace> { + self.target_ray_space.or_init(|| { + let global = self.global(); + XRSpace::new_inputspace(&global, &self.session, &self, false) + }) + } + + /// https://immersive-web.github.io/webxr/#dom-xrinputsource-gripspace + fn GetGripSpace(&self) -> Option<DomRoot<XRSpace>> { + if self.info.supports_grip { + Some(self.grip_space.or_init(|| { + let global = self.global(); + XRSpace::new_inputspace(&global, &self.session, &self, true) + })) + } else { + None + } + } + // https://immersive-web.github.io/webxr/#dom-xrinputsource-profiles + fn Profiles(&self, _cx: JSContext) -> JSVal { + self.profiles.get() + } + + // https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md + fn GetHand(&self) -> Option<DomRoot<XRHand>> { + if let Some(ref hand) = self.info.hand_support { + Some( + self.hand + .or_init(|| XRHand::new(&self.global(), &self, hand.clone())), + ) + } else { + None + } + } +} diff --git a/components/script/dom/xrinputsourcearray.rs b/components/script/dom/xrinputsourcearray.rs new file mode 100644 index 00000000000..b26f7bd3da7 --- /dev/null +++ b/components/script/dom/xrinputsourcearray.rs @@ -0,0 +1,144 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::XRInputSourceArrayBinding::XRInputSourceArrayMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrinputsource::XRInputSource; +use crate::dom::xrinputsourceschangeevent::XRInputSourcesChangeEvent; +use crate::dom::xrsession::XRSession; +use dom_struct::dom_struct; +use webxr_api::{InputId, InputSource}; + +#[dom_struct] +pub struct XRInputSourceArray { + reflector_: Reflector, + input_sources: DomRefCell<Vec<Dom<XRInputSource>>>, +} + +impl XRInputSourceArray { + fn new_inherited() -> XRInputSourceArray { + XRInputSourceArray { + reflector_: Reflector::new(), + input_sources: DomRefCell::new(vec![]), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<XRInputSourceArray> { + reflect_dom_object(Box::new(XRInputSourceArray::new_inherited()), global) + } + + pub fn add_input_sources(&self, session: &XRSession, inputs: &[InputSource]) { + let mut input_sources = self.input_sources.borrow_mut(); + let global = self.global(); + + let mut added = vec![]; + for info in inputs { + // This is quadratic, but won't be a problem for the only case + // where we add multiple input sources (the initial input sources case) + debug_assert!( + input_sources.iter().find(|i| i.id() == info.id).is_none(), + "Should never add a duplicate input id!" + ); + let input = XRInputSource::new(&global, &session, info.clone()); + input_sources.push(Dom::from_ref(&input)); + added.push(input); + } + + let event = XRInputSourcesChangeEvent::new( + &global, + atom!("inputsourceschange"), + false, + true, + session, + &added, + &[], + ); + // Release the refcell guard + drop(input_sources); + event.upcast::<Event>().fire(session.upcast()); + } + + pub fn remove_input_source(&self, session: &XRSession, id: InputId) { + let mut input_sources = self.input_sources.borrow_mut(); + let global = self.global(); + let removed = if let Some(i) = input_sources.iter().find(|i| i.id() == id) { + [DomRoot::from_ref(&**i)] + } else { + return; + }; + + let event = XRInputSourcesChangeEvent::new( + &global, + atom!("inputsourceschange"), + false, + true, + session, + &[], + &removed, + ); + input_sources.retain(|i| i.id() != id); + // release the refcell guard + drop(input_sources); + event.upcast::<Event>().fire(session.upcast()); + } + + pub fn add_remove_input_source(&self, session: &XRSession, id: InputId, info: InputSource) { + let mut input_sources = self.input_sources.borrow_mut(); + let global = self.global(); + let root; + let removed = if let Some(i) = input_sources.iter().find(|i| i.id() == id) { + root = [DomRoot::from_ref(&**i)]; + &root as &[_] + } else { + warn!("Could not find removed input source with id {:?}", id); + &[] + }; + input_sources.retain(|i| i.id() != id); + let input = XRInputSource::new(&global, &session, info); + input_sources.push(Dom::from_ref(&input)); + + let added = [input]; + + let event = XRInputSourcesChangeEvent::new( + &global, + atom!("inputsourceschange"), + false, + true, + session, + &added, + removed, + ); + // release the refcell guard + drop(input_sources); + event.upcast::<Event>().fire(session.upcast()); + } + + pub fn find(&self, id: InputId) -> Option<DomRoot<XRInputSource>> { + self.input_sources + .borrow() + .iter() + .find(|x| x.id() == id) + .map(|x| DomRoot::from_ref(&**x)) + } +} + +impl XRInputSourceArrayMethods for XRInputSourceArray { + /// https://immersive-web.github.io/webxr/#dom-xrinputsourcearray-length + fn Length(&self) -> u32 { + self.input_sources.borrow().len() as u32 + } + + /// https://immersive-web.github.io/webxr/#xrinputsourcearray + fn IndexedGetter(&self, n: u32) -> Option<DomRoot<XRInputSource>> { + self.input_sources + .borrow() + .get(n as usize) + .map(|x| DomRoot::from_ref(&**x)) + } +} diff --git a/components/script/dom/xrinputsourceevent.rs b/components/script/dom/xrinputsourceevent.rs new file mode 100644 index 00000000000..47ed6d90626 --- /dev/null +++ b/components/script/dom/xrinputsourceevent.rs @@ -0,0 +1,90 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::XRInputSourceEventBinding::{ + self, XRInputSourceEventMethods, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use crate::dom::xrframe::XRFrame; +use crate::dom::xrinputsource::XRInputSource; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct XRInputSourceEvent { + event: Event, + frame: Dom<XRFrame>, + source: Dom<XRInputSource>, +} + +impl XRInputSourceEvent { + #[allow(unrooted_must_root)] + fn new_inherited(frame: &XRFrame, source: &XRInputSource) -> XRInputSourceEvent { + XRInputSourceEvent { + event: Event::new_inherited(), + frame: Dom::from_ref(frame), + source: Dom::from_ref(source), + } + } + + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + frame: &XRFrame, + source: &XRInputSource, + ) -> DomRoot<XRInputSourceEvent> { + let trackevent = reflect_dom_object( + Box::new(XRInputSourceEvent::new_inherited(frame, source)), + global, + ); + { + let event = trackevent.upcast::<Event>(); + event.init_event(type_, bubbles, cancelable); + } + trackevent + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &XRInputSourceEventBinding::XRInputSourceEventInit, + ) -> Fallible<DomRoot<XRInputSourceEvent>> { + Ok(XRInputSourceEvent::new( + &window.global(), + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + &init.frame, + &init.inputSource, + )) + } +} + +impl XRInputSourceEventMethods for XRInputSourceEvent { + // https://immersive-web.github.io/webxr/#dom-xrinputsourceeventinit-frame + fn Frame(&self) -> DomRoot<XRFrame> { + DomRoot::from_ref(&*self.frame) + } + + // https://immersive-web.github.io/webxr/#dom-xrinputsourceeventinit-inputsource + fn InputSource(&self) -> DomRoot<XRInputSource> { + DomRoot::from_ref(&*self.source) + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/xrinputsourceschangeevent.rs b/components/script/dom/xrinputsourceschangeevent.rs new file mode 100644 index 00000000000..d23c5ea4157 --- /dev/null +++ b/components/script/dom/xrinputsourceschangeevent.rs @@ -0,0 +1,117 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::XRInputSourcesChangeEventBinding::{ + self, XRInputSourcesChangeEventMethods, +}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use crate::dom::xrinputsource::XRInputSource; +use crate::dom::xrsession::XRSession; +use crate::realms::enter_realm; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use js::conversions::ToJSValConvertible; +use js::jsapi::Heap; +use js::jsval::{JSVal, UndefinedValue}; +use servo_atoms::Atom; + +#[dom_struct] +pub struct XRInputSourcesChangeEvent { + event: Event, + session: Dom<XRSession>, + #[ignore_malloc_size_of = "mozjs"] + added: Heap<JSVal>, + #[ignore_malloc_size_of = "mozjs"] + removed: Heap<JSVal>, +} + +impl XRInputSourcesChangeEvent { + #[allow(unrooted_must_root)] + fn new_inherited(session: &XRSession) -> XRInputSourcesChangeEvent { + XRInputSourcesChangeEvent { + event: Event::new_inherited(), + session: Dom::from_ref(session), + added: Heap::default(), + removed: Heap::default(), + } + } + + #[allow(unsafe_code)] + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + session: &XRSession, + added: &[DomRoot<XRInputSource>], + removed: &[DomRoot<XRInputSource>], + ) -> DomRoot<XRInputSourcesChangeEvent> { + let changeevent = reflect_dom_object( + Box::new(XRInputSourcesChangeEvent::new_inherited(session)), + global, + ); + { + let event = changeevent.upcast::<Event>(); + event.init_event(type_, bubbles, cancelable); + } + let _ac = enter_realm(&*global); + let cx = global.get_cx(); + unsafe { + rooted!(in(*cx) let mut added_val = UndefinedValue()); + added.to_jsval(*cx, added_val.handle_mut()); + changeevent.added.set(added_val.get()); + rooted!(in(*cx) let mut removed_val = UndefinedValue()); + removed.to_jsval(*cx, removed_val.handle_mut()); + changeevent.removed.set(removed_val.get()); + } + + changeevent + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &XRInputSourcesChangeEventBinding::XRInputSourcesChangeEventInit, + ) -> DomRoot<XRInputSourcesChangeEvent> { + XRInputSourcesChangeEvent::new( + &window.global(), + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + &init.session, + &*init.added, + &*init.removed, + ) + } +} + +impl XRInputSourcesChangeEventMethods for XRInputSourcesChangeEvent { + // https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-session + fn Session(&self) -> DomRoot<XRSession> { + DomRoot::from_ref(&*self.session) + } + + // https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-added + fn Added(&self, _cx: JSContext) -> JSVal { + self.added.get() + } + + // https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-removed + fn Removed(&self, _cx: JSContext) -> JSVal { + self.removed.get() + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/xrjointpose.rs b/components/script/dom/xrjointpose.rs new file mode 100644 index 00000000000..c4b610b3327 --- /dev/null +++ b/components/script/dom/xrjointpose.rs @@ -0,0 +1,48 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRJointPoseBinding::XRJointPoseMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrpose::XRPose; +use crate::dom::xrrigidtransform::XRRigidTransform; +use crate::dom::xrsession::ApiRigidTransform; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRJointPose { + pose: XRPose, + radius: Option<f32>, +} + +impl XRJointPose { + fn new_inherited(transform: &XRRigidTransform, radius: Option<f32>) -> XRJointPose { + XRJointPose { + pose: XRPose::new_inherited(transform), + radius, + } + } + + #[allow(unsafe_code)] + pub fn new( + global: &GlobalScope, + pose: ApiRigidTransform, + radius: Option<f32>, + ) -> DomRoot<XRJointPose> { + let transform = XRRigidTransform::new(global, pose); + reflect_dom_object( + Box::new(XRJointPose::new_inherited(&transform, radius)), + global, + ) + } +} + +impl XRJointPoseMethods for XRJointPose { + /// https://immersive-web.github.io/webxr/#dom-XRJointPose-views + fn GetRadius(&self) -> Option<Finite<f32>> { + self.radius.map(Finite::wrap) + } +} diff --git a/components/script/dom/xrjointspace.rs b/components/script/dom/xrjointspace.rs new file mode 100644 index 00000000000..4b31b0bc6a2 --- /dev/null +++ b/components/script/dom/xrjointspace.rs @@ -0,0 +1,60 @@ +/* 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 crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrsession::{ApiPose, XRSession}; +use crate::dom::xrspace::XRSpace; +use dom_struct::dom_struct; +use euclid::RigidTransform3D; +use webxr_api::{BaseSpace, Frame, InputId, Joint, JointFrame, Space}; + +#[dom_struct] +pub struct XRJointSpace { + xrspace: XRSpace, + #[ignore_malloc_size_of = "defined in rust-webxr"] + input: InputId, + #[ignore_malloc_size_of = "defined in rust-webxr"] + joint: Joint, +} + +impl XRJointSpace { + pub fn new_inherited(session: &XRSession, input: InputId, joint: Joint) -> XRJointSpace { + XRJointSpace { + xrspace: XRSpace::new_inherited(session), + input, + joint, + } + } + + #[allow(unused)] + pub fn new( + global: &GlobalScope, + session: &XRSession, + input: InputId, + joint: Joint, + ) -> DomRoot<XRJointSpace> { + reflect_dom_object(Box::new(Self::new_inherited(session, input, joint)), global) + } + + pub fn space(&self) -> Space { + let base = BaseSpace::Joint(self.input, self.joint); + let offset = RigidTransform3D::identity(); + Space { base, offset } + } + + pub fn frame<'a>(&self, frame: &'a Frame) -> Option<&'a JointFrame> { + frame + .inputs + .iter() + .find(|i| i.id == self.input) + .and_then(|i| i.hand.as_ref()) + .and_then(|h| h.get(self.joint)) + } + + pub fn get_pose(&self, frame: &Frame) -> Option<ApiPose> { + self.frame(frame).map(|f| f.pose).map(|t| t.cast_unit()) + } +} diff --git a/components/script/dom/xrlayer.rs b/components/script/dom/xrlayer.rs new file mode 100644 index 00000000000..7328609d89f --- /dev/null +++ b/components/script/dom/xrlayer.rs @@ -0,0 +1,75 @@ +/* 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 crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::Dom; +use crate::dom::eventtarget::EventTarget; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::dom::xrframe::XRFrame; +use crate::dom::xrsession::XRSession; +use crate::dom::xrwebgllayer::XRWebGLLayer; +use canvas_traits::webgl::WebGLContextId; +use dom_struct::dom_struct; +use webxr_api::LayerId; + +#[dom_struct] +pub struct XRLayer { + event_target: EventTarget, + session: Dom<XRSession>, + context: Dom<WebGLRenderingContext>, + /// If none, the session is inline (the composition disabled flag is true) + /// and this is a XRWebGLLayer. + #[ignore_malloc_size_of = "Layer ids don't heap-allocate"] + layer_id: Option<LayerId>, +} + +impl XRLayer { + #[allow(dead_code)] + pub fn new_inherited( + session: &XRSession, + context: &WebGLRenderingContext, + layer_id: Option<LayerId>, + ) -> XRLayer { + XRLayer { + event_target: EventTarget::new_inherited(), + session: Dom::from_ref(session), + context: Dom::from_ref(context), + layer_id, + } + } + + pub(crate) fn layer_id(&self) -> Option<LayerId> { + self.layer_id + } + + pub(crate) fn context_id(&self) -> WebGLContextId { + self.context.context_id() + } + + pub(crate) fn context(&self) -> &WebGLRenderingContext { + &self.context + } + + pub(crate) fn session(&self) -> &XRSession { + &self.session + } + + pub fn begin_frame(&self, frame: &XRFrame) -> Option<()> { + // TODO: Implement this for other layer types + if let Some(this) = self.downcast::<XRWebGLLayer>() { + this.begin_frame(frame) + } else { + unimplemented!() + } + } + + pub fn end_frame(&self, frame: &XRFrame) -> Option<()> { + // TODO: Implement this for other layer types + if let Some(this) = self.downcast::<XRWebGLLayer>() { + this.end_frame(frame) + } else { + unimplemented!() + } + } +} diff --git a/components/script/dom/xrlayerevent.rs b/components/script/dom/xrlayerevent.rs new file mode 100644 index 00000000000..d3485fa1c51 --- /dev/null +++ b/components/script/dom/xrlayerevent.rs @@ -0,0 +1,62 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::XRLayerEventBinding::XRLayerEventInit; +use crate::dom::bindings::codegen::Bindings::XRLayerEventBinding::XRLayerEventMethods; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::window::Window; +use crate::dom::xrlayer::XRLayer; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +// https://w3c.github.io/uievents/#interface-uievent +#[dom_struct] +pub struct XRLayerEvent { + event: Event, + layer: Dom<XRLayer>, +} + +impl XRLayerEvent { + pub fn new_inherited(layer: &XRLayer) -> XRLayerEvent { + XRLayerEvent { + event: Event::new_inherited(), + layer: Dom::from_ref(layer), + } + } + + pub fn new(window: &Window, layer: &XRLayer) -> DomRoot<XRLayerEvent> { + reflect_dom_object(Box::new(XRLayerEvent::new_inherited(layer)), window) + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &XRLayerEventInit, + ) -> DomRoot<XRLayerEvent> { + let event = XRLayerEvent::new(window, &init.layer); + let type_ = Atom::from(type_); + let bubbles = init.parent.bubbles; + let cancelable = init.parent.cancelable; + event.event.init_event(type_, bubbles, cancelable); + event + } +} + +impl XRLayerEventMethods for XRLayerEvent { + // https://immersive-web.github.io/layers/#dom-xrlayerevent-layer + fn Layer(&self) -> DomRoot<XRLayer> { + DomRoot::from_ref(&self.layer) + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/xrmediabinding.rs b/components/script/dom/xrmediabinding.rs new file mode 100644 index 00000000000..5e1dfd3abeb --- /dev/null +++ b/components/script/dom/xrmediabinding.rs @@ -0,0 +1,86 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRMediaBindingBinding::XRMediaBindingBinding::XRMediaBindingMethods; +use crate::dom::bindings::codegen::Bindings::XRMediaBindingBinding::XRMediaLayerInit; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::DomRoot; +use crate::dom::htmlvideoelement::HTMLVideoElement; +use crate::dom::window::Window; +use crate::dom::xrcylinderlayer::XRCylinderLayer; +use crate::dom::xrequirectlayer::XREquirectLayer; +use crate::dom::xrquadlayer::XRQuadLayer; +use crate::dom::xrsession::XRSession; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRMediaBinding { + reflector: Reflector, + session: Dom<XRSession>, +} + +impl XRMediaBinding { + pub fn new_inherited(session: &XRSession) -> XRMediaBinding { + XRMediaBinding { + reflector: Reflector::new(), + session: Dom::from_ref(session), + } + } + + pub fn new(global: &Window, session: &XRSession) -> DomRoot<XRMediaBinding> { + reflect_dom_object(Box::new(XRMediaBinding::new_inherited(session)), global) + } + + #[allow(non_snake_case)] + pub fn Constructor(global: &Window, session: &XRSession) -> Fallible<DomRoot<XRMediaBinding>> { + // Step 1. + if session.is_ended() { + return Err(Error::InvalidState); + } + + // Step 2. + if !session.is_immersive() { + return Err(Error::InvalidState); + } + + // Steps 3-5. + Ok(XRMediaBinding::new(global, session)) + } +} + +impl XRMediaBindingMethods for XRMediaBinding { + /// https://immersive-web.github.io/layers/#dom-xrmediabinding-createquadlayer + fn CreateQuadLayer( + &self, + _: &HTMLVideoElement, + _: &XRMediaLayerInit, + ) -> Fallible<DomRoot<XRQuadLayer>> { + // https://github.com/servo/servo/issues/27493 + Err(Error::NotSupported) + } + + /// https://immersive-web.github.io/layers/#dom-xrmediabinding-createcylinderlayer + fn CreateCylinderLayer( + &self, + _: &HTMLVideoElement, + _: &XRMediaLayerInit, + ) -> Fallible<DomRoot<XRCylinderLayer>> { + // https://github.com/servo/servo/issues/27493 + Err(Error::NotSupported) + } + + /// https://immersive-web.github.io/layers/#dom-xrmediabinding-createequirectlayer + fn CreateEquirectLayer( + &self, + _: &HTMLVideoElement, + _: &XRMediaLayerInit, + ) -> Fallible<DomRoot<XREquirectLayer>> { + // https://github.com/servo/servo/issues/27493 + Err(Error::NotSupported) + } +} diff --git a/components/script/dom/xrpose.rs b/components/script/dom/xrpose.rs new file mode 100644 index 00000000000..bc9fb3a4be5 --- /dev/null +++ b/components/script/dom/xrpose.rs @@ -0,0 +1,39 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRPoseBinding::XRPoseMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrrigidtransform::XRRigidTransform; +use crate::dom::xrsession::ApiRigidTransform; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRPose { + reflector_: Reflector, + transform: Dom<XRRigidTransform>, +} + +impl XRPose { + pub fn new_inherited(transform: &XRRigidTransform) -> XRPose { + XRPose { + reflector_: Reflector::new(), + transform: Dom::from_ref(transform), + } + } + + #[allow(unused)] + pub fn new(global: &GlobalScope, transform: ApiRigidTransform) -> DomRoot<XRPose> { + let transform = XRRigidTransform::new(global, transform); + reflect_dom_object(Box::new(XRPose::new_inherited(&transform)), global) + } +} + +impl XRPoseMethods for XRPose { + /// https://immersive-web.github.io/webxr/#dom-xrpose-transform + fn Transform(&self) -> DomRoot<XRRigidTransform> { + DomRoot::from_ref(&self.transform) + } +} diff --git a/components/script/dom/xrprojectionlayer.rs b/components/script/dom/xrprojectionlayer.rs new file mode 100644 index 00000000000..f5d6f68de67 --- /dev/null +++ b/components/script/dom/xrprojectionlayer.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::xrcompositionlayer::XRCompositionLayer; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRProjectionLayer { + composition_layer: XRCompositionLayer, +} diff --git a/components/script/dom/xrquadlayer.rs b/components/script/dom/xrquadlayer.rs new file mode 100644 index 00000000000..86358a7d7b5 --- /dev/null +++ b/components/script/dom/xrquadlayer.rs @@ -0,0 +1,11 @@ +/* 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 crate::dom::xrcompositionlayer::XRCompositionLayer; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRQuadLayer { + composition_layer: XRCompositionLayer, +} diff --git a/components/script/dom/xrray.rs b/components/script/dom/xrray.rs new file mode 100644 index 00000000000..37262732c3a --- /dev/null +++ b/components/script/dom/xrray.rs @@ -0,0 +1,147 @@ +/* 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 crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit; +use crate::dom::bindings::codegen::Bindings::XRRayBinding::{XRRayDirectionInit, XRRayMethods}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::utils::create_typed_array; +use crate::dom::dompointreadonly::DOMPointReadOnly; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use crate::dom::xrrigidtransform::XRRigidTransform; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use euclid::{Angle, RigidTransform3D, Rotation3D, Vector3D}; +use js::jsapi::{Heap, JSObject}; +use std::ptr::NonNull; +use webxr_api::{ApiSpace, Ray}; + +#[dom_struct] +pub struct XRRay { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webxr"] + ray: Ray<ApiSpace>, + #[ignore_malloc_size_of = "defined in mozjs"] + matrix: Heap<*mut JSObject>, +} + +impl XRRay { + fn new_inherited(ray: Ray<ApiSpace>) -> XRRay { + XRRay { + reflector_: Reflector::new(), + ray, + matrix: Heap::default(), + } + } + + pub fn new(global: &GlobalScope, ray: Ray<ApiSpace>) -> DomRoot<XRRay> { + reflect_dom_object(Box::new(XRRay::new_inherited(ray)), global) + } + + #[allow(non_snake_case)] + /// https://immersive-web.github.io/hit-test/#dom-xrray-xrray + pub fn Constructor( + window: &Window, + origin: &DOMPointInit, + direction: &XRRayDirectionInit, + ) -> Fallible<DomRoot<Self>> { + if origin.w != 1.0 { + return Err(Error::Type("Origin w coordinate must be 1".into())); + } + if *direction.w != 0.0 { + return Err(Error::Type("Direction w coordinate must be 0".into())); + } + if *direction.x == 0.0 && *direction.y == 0.0 && *direction.z == 0.0 { + return Err(Error::Type( + "Direction vector cannot have zero length".into(), + )); + } + + let origin = Vector3D::new(origin.x as f32, origin.y as f32, origin.z as f32); + let direction = Vector3D::new( + *direction.x as f32, + *direction.y as f32, + *direction.z as f32, + ) + .normalize(); + + Ok(Self::new(&window.global(), Ray { origin, direction })) + } + + #[allow(non_snake_case)] + /// https://immersive-web.github.io/hit-test/#dom-xrray-xrray-transform + pub fn Constructor_(window: &Window, transform: &XRRigidTransform) -> Fallible<DomRoot<Self>> { + let transform = transform.transform(); + let origin = transform.translation; + let direction = transform + .rotation + .transform_vector3d(Vector3D::new(0., 0., -1.)); + + Ok(Self::new(&window.global(), Ray { origin, direction })) + } + + pub fn ray(&self) -> Ray<ApiSpace> { + self.ray + } +} + +impl XRRayMethods for XRRay { + /// https://immersive-web.github.io/hit-test/#dom-xrray-origin + fn Origin(&self) -> DomRoot<DOMPointReadOnly> { + DOMPointReadOnly::new( + &self.global(), + self.ray.origin.x as f64, + self.ray.origin.y as f64, + self.ray.origin.z as f64, + 1., + ) + } + + /// https://immersive-web.github.io/hit-test/#dom-xrray-direction + fn Direction(&self) -> DomRoot<DOMPointReadOnly> { + DOMPointReadOnly::new( + &self.global(), + self.ray.direction.x as f64, + self.ray.direction.y as f64, + self.ray.direction.z as f64, + 0., + ) + } + + /// https://immersive-web.github.io/hit-test/#dom-xrray-matrix + fn Matrix(&self, _cx: JSContext) -> NonNull<JSObject> { + // https://immersive-web.github.io/hit-test/#xrray-obtain-the-matrix + // Step 1 + if self.matrix.get().is_null() { + let cx = self.global().get_cx(); + // Step 2 + let z = Vector3D::new(0., 0., -1.); + // Step 3 + let axis = z.cross(self.ray.direction); + // Step 4 + let cos_angle = z.dot(self.ray.direction); + // Step 5 + let rotation = if cos_angle > -1. && cos_angle < 1. { + Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos())) + } else if cos_angle == -1. { + let axis = Vector3D::new(1., 0., 0.); + Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos())) + } else { + Rotation3D::identity() + }; + // Step 6 + let translation = self.ray.origin; + // Step 7 + // According to the spec all matrices are column-major, + // however euclid uses row vectors so we use .to_row_major_array() + let arr = RigidTransform3D::new(rotation, translation) + .to_transform() + .to_row_major_array(); + create_typed_array(cx, &arr, &self.matrix); + } + NonNull::new(self.matrix.get()).unwrap() + } +} diff --git a/components/script/dom/xrreferencespace.rs b/components/script/dom/xrreferencespace.rs new file mode 100644 index 00000000000..fb4f890e766 --- /dev/null +++ b/components/script/dom/xrreferencespace.rs @@ -0,0 +1,135 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceMethods; +use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrrigidtransform::XRRigidTransform; +use crate::dom::xrsession::{cast_transform, ApiPose, BaseTransform, XRSession}; +use crate::dom::xrspace::XRSpace; +use dom_struct::dom_struct; +use euclid::RigidTransform3D; +use webxr_api::{self, Frame, Space}; + +#[dom_struct] +pub struct XRReferenceSpace { + xrspace: XRSpace, + offset: Dom<XRRigidTransform>, + ty: XRReferenceSpaceType, +} + +impl XRReferenceSpace { + pub fn new_inherited( + session: &XRSession, + offset: &XRRigidTransform, + ty: XRReferenceSpaceType, + ) -> XRReferenceSpace { + XRReferenceSpace { + xrspace: XRSpace::new_inherited(session), + offset: Dom::from_ref(offset), + ty, + } + } + + #[allow(unused)] + pub fn new( + global: &GlobalScope, + session: &XRSession, + ty: XRReferenceSpaceType, + ) -> DomRoot<XRReferenceSpace> { + let offset = XRRigidTransform::identity(global); + Self::new_offset(global, session, ty, &offset) + } + + #[allow(unused)] + pub fn new_offset( + global: &GlobalScope, + session: &XRSession, + ty: XRReferenceSpaceType, + offset: &XRRigidTransform, + ) -> DomRoot<XRReferenceSpace> { + reflect_dom_object( + Box::new(XRReferenceSpace::new_inherited(session, &offset, ty)), + global, + ) + } + + pub fn space(&self) -> Space { + let base = match self.ty { + XRReferenceSpaceType::Local => webxr_api::BaseSpace::Local, + XRReferenceSpaceType::Viewer => webxr_api::BaseSpace::Viewer, + XRReferenceSpaceType::Local_floor => webxr_api::BaseSpace::Floor, + _ => panic!("unsupported reference space found"), + }; + let offset = self.offset.transform(); + Space { base, offset } + } +} + +impl XRReferenceSpaceMethods for XRReferenceSpace { + /// https://immersive-web.github.io/webxr/#dom-xrreferencespace-getoffsetreferencespace + fn GetOffsetReferenceSpace(&self, new: &XRRigidTransform) -> DomRoot<Self> { + let offset = self.offset.transform().pre_transform(&new.transform()); + let offset = XRRigidTransform::new(&self.global(), offset); + Self::new_offset( + &self.global(), + self.upcast::<XRSpace>().session(), + self.ty, + &offset, + ) + } +} + +impl XRReferenceSpace { + /// Get a transform that can be used to locate the base space + /// + /// This is equivalent to `get_pose(self).inverse()` (in column vector notation), + /// but with better types + pub fn get_base_transform(&self, base_pose: &Frame) -> Option<BaseTransform> { + let pose = self.get_pose(base_pose)?; + Some(pose.inverse().cast_unit()) + } + + /// Gets pose represented by this space + /// + /// The reference origin used is common between all + /// get_pose calls for spaces from the same device, so this can be used to compare + /// with other spaces + pub fn get_pose(&self, base_pose: &Frame) -> Option<ApiPose> { + let pose = self.get_unoffset_pose(base_pose)?; + let offset = self.offset.transform(); + // pose is a transform from the unoffset space to native space, + // offset is a transform from offset space to unoffset space, + // we want a transform from unoffset space to native space, + // which is pose * offset in column vector notation + Some(pose.pre_transform(&offset)) + } + + /// Gets pose represented by this space + /// + /// Does not apply originOffset, use get_viewer_pose instead if you need it + pub fn get_unoffset_pose(&self, base_pose: &Frame) -> Option<ApiPose> { + match self.ty { + XRReferenceSpaceType::Local => { + // The eye-level pose is basically whatever the headset pose was at t=0, which + // for most devices is (0, 0, 0) + Some(RigidTransform3D::identity()) + }, + XRReferenceSpaceType::Local_floor => { + let native_to_floor = self + .upcast::<XRSpace>() + .session() + .with_session(|s| s.floor_transform())?; + Some(cast_transform(native_to_floor.inverse())) + }, + XRReferenceSpaceType::Viewer => { + Some(cast_transform(base_pose.pose.as_ref()?.transform)) + }, + _ => unimplemented!(), + } + } +} diff --git a/components/script/dom/xrrenderstate.rs b/components/script/dom/xrrenderstate.rs new file mode 100644 index 00000000000..71e6d873814 --- /dev/null +++ b/components/script/dom/xrrenderstate.rs @@ -0,0 +1,162 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateMethods; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::utils::to_frozen_array; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrlayer::XRLayer; +use crate::dom::xrwebgllayer::XRWebGLLayer; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use js::jsval::JSVal; +use std::cell::Cell; +use webxr_api::SubImages; + +#[dom_struct] +pub struct XRRenderState { + reflector_: Reflector, + depth_near: Cell<f64>, + depth_far: Cell<f64>, + inline_vertical_fov: Cell<Option<f64>>, + base_layer: MutNullableDom<XRWebGLLayer>, + layers: DomRefCell<Vec<Dom<XRLayer>>>, +} + +impl XRRenderState { + pub fn new_inherited( + depth_near: f64, + depth_far: f64, + inline_vertical_fov: Option<f64>, + layer: Option<&XRWebGLLayer>, + layers: Vec<&XRLayer>, + ) -> XRRenderState { + debug_assert!(layer.is_none() || layers.is_empty()); + XRRenderState { + reflector_: Reflector::new(), + depth_near: Cell::new(depth_near), + depth_far: Cell::new(depth_far), + inline_vertical_fov: Cell::new(inline_vertical_fov), + base_layer: MutNullableDom::new(layer), + layers: DomRefCell::new( + layers + .into_iter() + .map(|layer| Dom::from_ref(layer)) + .collect(), + ), + } + } + + pub fn new( + global: &GlobalScope, + depth_near: f64, + depth_far: f64, + inline_vertical_fov: Option<f64>, + layer: Option<&XRWebGLLayer>, + layers: Vec<&XRLayer>, + ) -> DomRoot<XRRenderState> { + reflect_dom_object( + Box::new(XRRenderState::new_inherited( + depth_near, + depth_far, + inline_vertical_fov, + layer, + layers, + )), + global, + ) + } + + pub fn clone_object(&self) -> DomRoot<Self> { + XRRenderState::new( + &self.global(), + self.depth_near.get(), + self.depth_far.get(), + self.inline_vertical_fov.get(), + self.base_layer.get().as_ref().map(|x| &**x), + self.layers.borrow().iter().map(|x| &**x).collect(), + ) + } + + pub fn set_depth_near(&self, depth: f64) { + self.depth_near.set(depth) + } + pub fn set_depth_far(&self, depth: f64) { + self.depth_far.set(depth) + } + pub fn set_inline_vertical_fov(&self, fov: f64) { + debug_assert!(self.inline_vertical_fov.get().is_some()); + self.inline_vertical_fov.set(Some(fov)) + } + pub fn set_base_layer(&self, layer: Option<&XRWebGLLayer>) { + self.base_layer.set(layer) + } + pub fn set_layers(&self, layers: Vec<&XRLayer>) { + *self.layers.borrow_mut() = layers + .into_iter() + .map(|layer| Dom::from_ref(layer)) + .collect(); + } + pub fn with_layers<F, R>(&self, f: F) -> R + where + F: FnOnce(&[Dom<XRLayer>]) -> R, + { + let layers = self.layers.borrow(); + f(&*layers) + } + pub fn has_sub_images(&self, sub_images: &[SubImages]) -> bool { + if let Some(base_layer) = self.base_layer.get() { + match sub_images.len() { + // For inline sessions, there may be a base layer, but it won't have a framebuffer + 0 => base_layer.layer_id() == None, + // For immersive sessions, the base layer will have a framebuffer, + // so we make sure the layer id's match up + 1 => base_layer.layer_id() == Some(sub_images[0].layer_id), + _ => false, + } + } else { + // The layers API is only for immersive sessions + let layers = self.layers.borrow(); + sub_images.len() == layers.len() && + sub_images + .iter() + .zip(layers.iter()) + .all(|(sub_image, layer)| Some(sub_image.layer_id) == layer.layer_id()) + } + } +} + +impl XRRenderStateMethods for XRRenderState { + /// https://immersive-web.github.io/webxr/#dom-xrrenderstate-depthnear + fn DepthNear(&self) -> Finite<f64> { + Finite::wrap(self.depth_near.get()) + } + + /// https://immersive-web.github.io/webxr/#dom-xrrenderstate-depthfar + fn DepthFar(&self) -> Finite<f64> { + Finite::wrap(self.depth_far.get()) + } + + /// https://immersive-web.github.io/webxr/#dom-xrrenderstate-inlineverticalfieldofview + fn GetInlineVerticalFieldOfView(&self) -> Option<Finite<f64>> { + self.inline_vertical_fov.get().map(Finite::wrap) + } + + /// https://immersive-web.github.io/webxr/#dom-xrrenderstate-baselayer + fn GetBaseLayer(&self) -> Option<DomRoot<XRWebGLLayer>> { + self.base_layer.get() + } + + /// https://immersive-web.github.io/layers/#dom-xrrenderstate-layers + fn Layers(&self, cx: JSContext) -> JSVal { + // TODO: cache this array? + let layers = self.layers.borrow(); + let layers: Vec<&XRLayer> = layers.iter().map(|x| &**x).collect(); + to_frozen_array(&layers[..], cx) + } +} diff --git a/components/script/dom/xrrigidtransform.rs b/components/script/dom/xrrigidtransform.rs new file mode 100644 index 00000000000..ac752d5a3dd --- /dev/null +++ b/components/script/dom/xrrigidtransform.rs @@ -0,0 +1,135 @@ +/* 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 crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit; +use crate::dom::bindings::codegen::Bindings::XRRigidTransformBinding::XRRigidTransformMethods; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::utils::create_typed_array; +use crate::dom::dompointreadonly::DOMPointReadOnly; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use crate::dom::xrsession::ApiRigidTransform; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use euclid::{RigidTransform3D, Rotation3D, Vector3D}; +use js::jsapi::{Heap, JSObject}; +use std::ptr::NonNull; + +#[dom_struct] +pub struct XRRigidTransform { + reflector_: Reflector, + position: MutNullableDom<DOMPointReadOnly>, + orientation: MutNullableDom<DOMPointReadOnly>, + #[ignore_malloc_size_of = "defined in euclid"] + transform: ApiRigidTransform, + inverse: MutNullableDom<XRRigidTransform>, + #[ignore_malloc_size_of = "defined in mozjs"] + matrix: Heap<*mut JSObject>, +} + +impl XRRigidTransform { + fn new_inherited(transform: ApiRigidTransform) -> XRRigidTransform { + XRRigidTransform { + reflector_: Reflector::new(), + position: MutNullableDom::default(), + orientation: MutNullableDom::default(), + transform, + inverse: MutNullableDom::default(), + matrix: Heap::default(), + } + } + + pub fn new(global: &GlobalScope, transform: ApiRigidTransform) -> DomRoot<XRRigidTransform> { + reflect_dom_object(Box::new(XRRigidTransform::new_inherited(transform)), global) + } + + pub fn identity(window: &GlobalScope) -> DomRoot<XRRigidTransform> { + let transform = RigidTransform3D::identity(); + XRRigidTransform::new(window, transform) + } + + // https://immersive-web.github.io/webxr/#dom-xrrigidtransform-xrrigidtransform + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + position: &DOMPointInit, + orientation: &DOMPointInit, + ) -> Fallible<DomRoot<Self>> { + if position.w != 1.0 { + return Err(Error::Type(format!( + "XRRigidTransform must be constructed with a position that has a w value of of 1.0, not {}", + position.w + ))); + } + + let translate = Vector3D::new(position.x as f32, position.y as f32, position.z as f32); + let rotate = Rotation3D::unit_quaternion( + orientation.x as f32, + orientation.y as f32, + orientation.z as f32, + orientation.w as f32, + ); + + if !rotate.i.is_finite() { + // if quaternion has zero norm, we'll get an infinite or NaN + // value for each element. This is preferable to checking for zero. + return Err(Error::InvalidState); + } + let transform = RigidTransform3D::new(rotate, translate); + Ok(XRRigidTransform::new(&window.global(), transform)) + } +} + +impl XRRigidTransformMethods for XRRigidTransform { + // https://immersive-web.github.io/webxr/#dom-xrrigidtransform-position + fn Position(&self) -> DomRoot<DOMPointReadOnly> { + self.position.or_init(|| { + let t = &self.transform.translation; + DOMPointReadOnly::new(&self.global(), t.x.into(), t.y.into(), t.z.into(), 1.0) + }) + } + // https://immersive-web.github.io/webxr/#dom-xrrigidtransform-orientation + fn Orientation(&self) -> DomRoot<DOMPointReadOnly> { + self.orientation.or_init(|| { + let r = &self.transform.rotation; + DOMPointReadOnly::new( + &self.global(), + r.i.into(), + r.j.into(), + r.k.into(), + r.r.into(), + ) + }) + } + // https://immersive-web.github.io/webxr/#dom-xrrigidtransform-inverse + fn Inverse(&self) -> DomRoot<XRRigidTransform> { + self.inverse.or_init(|| { + let transform = XRRigidTransform::new(&self.global(), self.transform.inverse()); + transform.inverse.set(Some(self)); + transform + }) + } + // https://immersive-web.github.io/webxr/#dom-xrrigidtransform-matrix + fn Matrix(&self, _cx: JSContext) -> NonNull<JSObject> { + if self.matrix.get().is_null() { + let cx = self.global().get_cx(); + // According to the spec all matrices are column-major, + // however euclid uses row vectors so we use .to_row_major_array() + let arr = self.transform.to_transform().to_row_major_array(); + create_typed_array(cx, &arr, &self.matrix); + } + NonNull::new(self.matrix.get()).unwrap() + } +} + +impl XRRigidTransform { + /// https://immersive-web.github.io/webxr/#dom-xrpose-transform + pub fn transform(&self) -> ApiRigidTransform { + self.transform + } +} diff --git a/components/script/dom/xrsession.rs b/components/script/dom/xrsession.rs new file mode 100644 index 00000000000..01b21379ce3 --- /dev/null +++ b/components/script/dom/xrsession.rs @@ -0,0 +1,911 @@ +/* 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 crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::XRHitTestOptionsInit; +use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::XRHitTestTrackableType; +use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType; +use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateInit; +use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateMethods; +use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XREnvironmentBlendMode; +use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRFrameRequestCallback; +use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRSessionMethods; +use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRVisibilityState; +use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionMode; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutDom, MutNullableDom}; +use crate::dom::event::Event; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::performance::reduce_timing_resolution; +use crate::dom::promise::Promise; +use crate::dom::xrframe::XRFrame; +use crate::dom::xrhittestsource::XRHitTestSource; +use crate::dom::xrinputsourcearray::XRInputSourceArray; +use crate::dom::xrinputsourceevent::XRInputSourceEvent; +use crate::dom::xrreferencespace::XRReferenceSpace; +use crate::dom::xrrenderstate::XRRenderState; +use crate::dom::xrsessionevent::XRSessionEvent; +use crate::dom::xrspace::XRSpace; +use crate::realms::InRealm; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use euclid::{RigidTransform3D, Transform3D, Vector3D}; +use ipc_channel::ipc::IpcReceiver; +use ipc_channel::router::ROUTER; +use metrics::ToMs; +use profile_traits::ipc; +use std::cell::Cell; +use std::collections::HashMap; +use std::f64::consts::{FRAC_PI_2, PI}; +use std::mem; +use std::rc::Rc; +use webxr_api::ContextId as WebXRContextId; +use webxr_api::{ + self, util, ApiSpace, Display, EntityTypes, EnvironmentBlendMode, Event as XREvent, Frame, + FrameUpdateEvent, HitTestId, HitTestSource, Ray, SelectEvent, SelectKind, Session, SessionId, + View, Viewer, Visibility, +}; + +#[dom_struct] +pub struct XRSession { + eventtarget: EventTarget, + blend_mode: XREnvironmentBlendMode, + mode: XRSessionMode, + visibility_state: Cell<XRVisibilityState>, + viewer_space: MutNullableDom<XRSpace>, + #[ignore_malloc_size_of = "defined in webxr"] + session: DomRefCell<Session>, + frame_requested: Cell<bool>, + pending_render_state: MutNullableDom<XRRenderState>, + active_render_state: MutDom<XRRenderState>, + /// Cached projection matrix for inline sessions + inline_projection_matrix: DomRefCell<Transform3D<f32, Viewer, Display>>, + + next_raf_id: Cell<i32>, + #[ignore_malloc_size_of = "closures are hard"] + raf_callback_list: DomRefCell<Vec<(i32, Option<Rc<XRFrameRequestCallback>>)>>, + #[ignore_malloc_size_of = "closures are hard"] + current_raf_callback_list: DomRefCell<Vec<(i32, Option<Rc<XRFrameRequestCallback>>)>>, + input_sources: Dom<XRInputSourceArray>, + // Any promises from calling end() + #[ignore_malloc_size_of = "promises are hard"] + end_promises: DomRefCell<Vec<Rc<Promise>>>, + /// https://immersive-web.github.io/webxr/#ended + ended: Cell<bool>, + #[ignore_malloc_size_of = "defined in webxr"] + next_hit_test_id: Cell<HitTestId>, + #[ignore_malloc_size_of = "defined in webxr"] + pending_hit_test_promises: DomRefCell<HashMap<HitTestId, Rc<Promise>>>, + /// Opaque framebuffers need to know the session is "outside of a requestAnimationFrame" + /// https://immersive-web.github.io/webxr/#opaque-framebuffer + outside_raf: Cell<bool>, +} + +impl XRSession { + fn new_inherited( + session: Session, + render_state: &XRRenderState, + input_sources: &XRInputSourceArray, + mode: XRSessionMode, + ) -> XRSession { + XRSession { + eventtarget: EventTarget::new_inherited(), + blend_mode: session.environment_blend_mode().into(), + mode, + visibility_state: Cell::new(XRVisibilityState::Visible), + viewer_space: Default::default(), + session: DomRefCell::new(session), + frame_requested: Cell::new(false), + pending_render_state: MutNullableDom::new(None), + active_render_state: MutDom::new(render_state), + inline_projection_matrix: Default::default(), + + next_raf_id: Cell::new(0), + raf_callback_list: DomRefCell::new(vec![]), + current_raf_callback_list: DomRefCell::new(vec![]), + input_sources: Dom::from_ref(input_sources), + end_promises: DomRefCell::new(vec![]), + ended: Cell::new(false), + next_hit_test_id: Cell::new(HitTestId(0)), + pending_hit_test_promises: DomRefCell::new(HashMap::new()), + outside_raf: Cell::new(true), + } + } + + pub fn new( + global: &GlobalScope, + session: Session, + mode: XRSessionMode, + frame_receiver: IpcReceiver<Frame>, + ) -> DomRoot<XRSession> { + let ivfov = if mode == XRSessionMode::Inline { + Some(FRAC_PI_2) + } else { + None + }; + let render_state = XRRenderState::new(global, 0.1, 1000.0, ivfov, None, Vec::new()); + let input_sources = XRInputSourceArray::new(global); + let ret = reflect_dom_object( + Box::new(XRSession::new_inherited( + session, + &render_state, + &input_sources, + mode, + )), + global, + ); + ret.attach_event_handler(); + ret.setup_raf_loop(frame_receiver); + ret + } + + pub fn with_session<R, F: FnOnce(&Session) -> R>(&self, with: F) -> R { + let session = self.session.borrow(); + with(&session) + } + + pub fn is_ended(&self) -> bool { + self.ended.get() + } + + pub fn is_immersive(&self) -> bool { + self.mode != XRSessionMode::Inline + } + + // https://immersive-web.github.io/layers/#feature-descriptor-layers + pub fn has_layers_feature(&self) -> bool { + // We do not support creating layers other than projection layers + // https://github.com/servo/servo/issues/27493 + false + } + + fn setup_raf_loop(&self, frame_receiver: IpcReceiver<Frame>) { + let this = Trusted::new(self); + let global = self.global(); + let window = global.as_window(); + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + ROUTER.add_route( + frame_receiver.to_opaque(), + Box::new(move |message| { + #[allow(unused)] + let mut frame: Frame = message.to().unwrap(); + #[cfg(feature = "xr-profile")] + { + let received = time::precise_time_ns(); + println!( + "WEBXR PROFILING [raf receive]:\t{}ms", + (received - frame.sent_time) as f64 / 1_000_000. + ); + frame.sent_time = received; + } + let this = this.clone(); + let _ = task_source.queue_with_canceller( + task!(xr_raf_callback: move || { + this.root().raf_callback(frame); + }), + &canceller, + ); + }), + ); + + self.session.borrow_mut().start_render_loop(); + } + + pub fn is_outside_raf(&self) -> bool { + self.outside_raf.get() + } + + fn attach_event_handler(&self) { + let this = Trusted::new(self); + let global = self.global(); + let window = global.as_window(); + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap(); + + ROUTER.add_route( + receiver.to_opaque(), + Box::new(move |message| { + let this = this.clone(); + let _ = task_source.queue_with_canceller( + task!(xr_event_callback: move || { + this.root().event_callback(message.to().unwrap()); + }), + &canceller, + ); + }), + ); + + // request animation frame + self.session.borrow_mut().set_event_dest(sender); + } + + // Must be called after the promise for session creation is resolved + // https://github.com/immersive-web/webxr/issues/961 + // + // This enables content that assumes all input sources are accompanied + // by an inputsourceschange event to work properly. Without + pub fn setup_initial_inputs(&self) { + let initial_inputs = self.session.borrow().initial_inputs().to_owned(); + + if initial_inputs.is_empty() { + // do not fire an empty event + return; + } + + let global = self.global(); + let window = global.as_window(); + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let this = Trusted::new(self); + // Queue a task so that it runs after resolve()'s microtasks complete + // so that content has a chance to attach a listener for inputsourceschange + let _ = task_source.queue_with_canceller( + task!(session_initial_inputs: move || { + let this = this.root(); + this.input_sources.add_input_sources(&this, &initial_inputs); + }), + &canceller, + ); + } + + fn event_callback(&self, event: XREvent) { + match event { + XREvent::SessionEnd => { + // https://immersive-web.github.io/webxr/#shut-down-the-session + // Step 2 + self.ended.set(true); + // Step 3-4 + self.global().as_window().Navigator().Xr().end_session(self); + // Step 5: We currently do not have any such promises + // Step 6 is happening n the XR session + // https://immersive-web.github.io/webxr/#dom-xrsession-end step 3 + for promise in self.end_promises.borrow_mut().drain(..) { + promise.resolve_native(&()); + } + // Step 7 + let event = XRSessionEvent::new(&self.global(), atom!("end"), false, false, self); + event.upcast::<Event>().fire(self.upcast()); + }, + XREvent::Select(input, kind, ty, frame) => { + use servo_atoms::Atom; + const START_ATOMS: [Atom; 2] = [atom!("selectstart"), atom!("squeezestart")]; + const EVENT_ATOMS: [Atom; 2] = [atom!("select"), atom!("squeeze")]; + const END_ATOMS: [Atom; 2] = [atom!("selectend"), atom!("squeezeend")]; + + // https://immersive-web.github.io/webxr/#primary-action + let source = self.input_sources.find(input); + let atom_index = if kind == SelectKind::Squeeze { 1 } else { 0 }; + if let Some(source) = source { + let frame = XRFrame::new(&self.global(), self, frame); + frame.set_active(true); + if ty == SelectEvent::Start { + let event = XRInputSourceEvent::new( + &self.global(), + START_ATOMS[atom_index].clone(), + false, + false, + &frame, + &source, + ); + event.upcast::<Event>().fire(self.upcast()); + } else { + if ty == SelectEvent::Select { + let event = XRInputSourceEvent::new( + &self.global(), + EVENT_ATOMS[atom_index].clone(), + false, + false, + &frame, + &source, + ); + event.upcast::<Event>().fire(self.upcast()); + } + let event = XRInputSourceEvent::new( + &self.global(), + END_ATOMS[atom_index].clone(), + false, + false, + &frame, + &source, + ); + event.upcast::<Event>().fire(self.upcast()); + } + frame.set_active(false); + } + }, + XREvent::VisibilityChange(v) => { + let v = match v { + Visibility::Visible => XRVisibilityState::Visible, + Visibility::VisibleBlurred => XRVisibilityState::Visible_blurred, + Visibility::Hidden => XRVisibilityState::Hidden, + }; + self.visibility_state.set(v); + let event = XRSessionEvent::new( + &self.global(), + atom!("visibilitychange"), + false, + false, + self, + ); + event.upcast::<Event>().fire(self.upcast()); + // The page may be visible again, dirty the layers + // This also wakes up the event loop if necessary + self.dirty_layers(); + }, + XREvent::AddInput(info) => { + self.input_sources.add_input_sources(self, &[info]); + }, + XREvent::RemoveInput(id) => { + self.input_sources.remove_input_source(self, id); + }, + XREvent::UpdateInput(id, source) => { + self.input_sources.add_remove_input_source(self, id, source); + }, + } + } + + /// https://immersive-web.github.io/webxr/#xr-animation-frame + fn raf_callback(&self, mut frame: Frame) { + debug!("WebXR RAF callback {:?}", frame); + #[cfg(feature = "xr-profile")] + let raf_start = time::precise_time_ns(); + #[cfg(feature = "xr-profile")] + println!( + "WEBXR PROFILING [raf queued]:\t{}ms", + (raf_start - frame.sent_time) as f64 / 1_000_000. + ); + + // Step 1-2 happen in the xebxr device thread + + // Step 3 + if let Some(pending) = self.pending_render_state.take() { + // https://immersive-web.github.io/webxr/#apply-the-pending-render-state + // (Steps 1-4 are implicit) + // Step 5 + self.active_render_state.set(&pending); + // Step 6-7: XXXManishearth handle inlineVerticalFieldOfView + + if !self.is_immersive() { + self.update_inline_projection_matrix() + } + } + + // TODO: how does this fit the webxr spec? + for event in frame.events.drain(..) { + self.handle_frame_event(event); + } + + // Step 4 + // TODO: what should this check be? + // This is checking that the new render state has the same + // layers as the frame. + // Related to https://github.com/immersive-web/webxr/issues/1051 + if !self + .active_render_state + .get() + .has_sub_images(&frame.sub_images[..]) + { + // If the frame has different layers than the render state, + // we just return early, drawing a blank frame. + // This can result in flickering when the render state is changed. + // TODO: it would be better to not render anything until the next frame. + warn!("Rendering blank XR frame"); + self.session.borrow_mut().render_animation_frame(); + return; + } + + // Step 5: XXXManishearth handle inline session + + // Step 6-7 + { + let mut current = self.current_raf_callback_list.borrow_mut(); + assert!(current.is_empty()); + mem::swap(&mut *self.raf_callback_list.borrow_mut(), &mut current); + } + let start = self.global().as_window().get_navigation_start(); + let time = reduce_timing_resolution((frame.time_ns - start).to_ms()); + + let frame = XRFrame::new(&self.global(), self, frame); + // Step 8-9 + frame.set_active(true); + frame.set_animation_frame(true); + + // Step 10 + self.apply_frame_updates(&*frame); + + // TODO: how does this fit with the webxr and xr layers specs? + self.layers_begin_frame(&*frame); + + // Step 11-12 + self.outside_raf.set(false); + let len = self.current_raf_callback_list.borrow().len(); + for i in 0..len { + let callback = self.current_raf_callback_list.borrow()[i] + .1 + .as_ref() + .map(|callback| Rc::clone(callback)); + if let Some(callback) = callback { + let _ = callback.Call__(time, &frame, ExceptionHandling::Report); + } + } + self.outside_raf.set(true); + *self.current_raf_callback_list.borrow_mut() = vec![]; + + // TODO: how does this fit with the webxr and xr layers specs? + self.layers_end_frame(&*frame); + + // Step 13 + frame.set_active(false); + + // TODO: how does this fit the webxr spec? + self.session.borrow_mut().render_animation_frame(); + + #[cfg(feature = "xr-profile")] + println!( + "WEBXR PROFILING [raf execute]:\t{}ms", + (time::precise_time_ns() - raf_start) as f64 / 1_000_000. + ); + } + + fn update_inline_projection_matrix(&self) { + debug_assert!(!self.is_immersive()); + let render_state = self.active_render_state.get(); + let size = if let Some(base) = render_state.GetBaseLayer() { + base.size() + } else { + return; + }; + let mut clip_planes = util::ClipPlanes::default(); + let near = *render_state.DepthNear() as f32; + let far = *render_state.DepthFar() as f32; + clip_planes.update(near, far); + let top = *render_state + .GetInlineVerticalFieldOfView() + .expect("IVFOV should be non null for inline sessions") / + 2.; + let top = near * top.tan() as f32; + let bottom = top; + let left = top * size.width as f32 / size.height as f32; + let right = left; + let matrix = util::frustum_to_projection_matrix(left, right, top, bottom, clip_planes); + *self.inline_projection_matrix.borrow_mut() = matrix; + } + + /// Constructs a View suitable for inline sessions using the inlineVerticalFieldOfView and canvas size + pub fn inline_view(&self) -> View<Viewer> { + debug_assert!(!self.is_immersive()); + View { + // Inline views have no offset + transform: RigidTransform3D::identity(), + projection: *self.inline_projection_matrix.borrow(), + } + } + + pub fn session_id(&self) -> SessionId { + self.session.borrow().id() + } + + pub fn dirty_layers(&self) { + if let Some(layer) = self.RenderState().GetBaseLayer() { + layer.context().mark_as_dirty(); + } + } + + // TODO: how does this align with the layers spec? + fn layers_begin_frame(&self, frame: &XRFrame) { + if let Some(layer) = self.active_render_state.get().GetBaseLayer() { + layer.begin_frame(frame); + } + self.active_render_state.get().with_layers(|layers| { + for layer in layers { + layer.begin_frame(frame); + } + }); + } + + // TODO: how does this align with the layers spec? + fn layers_end_frame(&self, frame: &XRFrame) { + if let Some(layer) = self.active_render_state.get().GetBaseLayer() { + layer.end_frame(frame); + } + self.active_render_state.get().with_layers(|layers| { + for layer in layers { + layer.end_frame(frame); + } + }); + } + + /// https://immersive-web.github.io/webxr/#xrframe-apply-frame-updates + fn apply_frame_updates(&self, _frame: &XRFrame) { + // TODO: add a comment about why this is empty right now! + } + + fn handle_frame_event(&self, event: FrameUpdateEvent) { + match event { + FrameUpdateEvent::HitTestSourceAdded(id) => { + if let Some(promise) = self.pending_hit_test_promises.borrow_mut().remove(&id) { + promise.resolve_native(&XRHitTestSource::new(&self.global(), id, &self)); + } else { + warn!( + "received hit test add request for unknown hit test {:?}", + id + ) + } + }, + _ => self.session.borrow_mut().apply_event(event), + } + } +} + +impl XRSessionMethods for XRSession { + // https://immersive-web.github.io/webxr/#eventdef-xrsession-end + event_handler!(end, GetOnend, SetOnend); + + // https://immersive-web.github.io/webxr/#eventdef-xrsession-select + event_handler!(select, GetOnselect, SetOnselect); + + // https://immersive-web.github.io/webxr/#eventdef-xrsession-selectstart + event_handler!(selectstart, GetOnselectstart, SetOnselectstart); + + // https://immersive-web.github.io/webxr/#eventdef-xrsession-selectend + event_handler!(selectend, GetOnselectend, SetOnselectend); + + // https://immersive-web.github.io/webxr/#eventdef-xrsession-squeeze + event_handler!(squeeze, GetOnsqueeze, SetOnsqueeze); + + // https://immersive-web.github.io/webxr/#eventdef-xrsession-squeezestart + event_handler!(squeezestart, GetOnsqueezestart, SetOnsqueezestart); + + // https://immersive-web.github.io/webxr/#eventdef-xrsession-squeezeend + event_handler!(squeezeend, GetOnsqueezeend, SetOnsqueezeend); + + // https://immersive-web.github.io/webxr/#eventdef-xrsession-visibilitychange + event_handler!( + visibilitychange, + GetOnvisibilitychange, + SetOnvisibilitychange + ); + + // https://immersive-web.github.io/webxr/#eventdef-xrsession-inputsourceschange + event_handler!( + inputsourceschange, + GetOninputsourceschange, + SetOninputsourceschange + ); + + // https://immersive-web.github.io/webxr/#dom-xrsession-renderstate + fn RenderState(&self) -> DomRoot<XRRenderState> { + self.active_render_state.get() + } + + /// https://immersive-web.github.io/webxr/#dom-xrsession-updaterenderstate + fn UpdateRenderState(&self, init: &XRRenderStateInit, _: InRealm) -> ErrorResult { + // Step 2 + if self.ended.get() { + return Err(Error::InvalidState); + } + // Step 3: + if let Some(Some(ref layer)) = init.baseLayer { + if Dom::from_ref(layer.session()) != Dom::from_ref(self) { + return Err(Error::InvalidState); + } + } + + // Step 4: + if init.inlineVerticalFieldOfView.is_some() && self.is_immersive() { + return Err(Error::InvalidState); + } + + // https://immersive-web.github.io/layers/#updaterenderstatechanges + // Step 1. + if init.baseLayer.is_some() { + if self.has_layers_feature() { + return Err(Error::NotSupported); + } + // https://github.com/immersive-web/layers/issues/189 + if init.layers.is_some() { + return Err(Error::Type(String::from( + "Cannot set WebXR layers and baseLayer", + ))); + } + } + + if let Some(Some(ref layers)) = init.layers { + // Step 2 + for layer in layers { + let count = layers + .iter() + .filter(|other| other.layer_id() == layer.layer_id()) + .count(); + if count > 1 { + return Err(Error::Type(String::from("Duplicate entry in WebXR layers"))); + } + } + + // Step 3 + for layer in layers { + if layer.session() != self { + return Err(Error::Type(String::from( + "Layer from different session in WebXR layers", + ))); + } + } + } + + // Step 4-5 + let pending = self + .pending_render_state + .or_init(|| self.active_render_state.get().clone_object()); + + // Step 6 + if let Some(ref layers) = init.layers { + let layers = layers.as_deref().unwrap_or_default(); + pending.set_base_layer(None); + pending.set_layers(layers.iter().map(|x| &**x).collect()); + let layers = layers + .iter() + .filter_map(|layer| { + let context_id = WebXRContextId::from(layer.context_id()); + let layer_id = layer.layer_id()?; + Some((context_id, layer_id)) + }) + .collect(); + self.session.borrow_mut().set_layers(layers); + } + + // End of https://immersive-web.github.io/layers/#updaterenderstatechanges + + if let Some(near) = init.depthNear { + let mut near = *near; + // Step 8 from #apply-the-pending-render-state + // this may need to be changed if backends wish to impose + // further constraints + if near < 0. { + near = 0.; + } + pending.set_depth_near(near); + } + if let Some(far) = init.depthFar { + // Step 9 from #apply-the-pending-render-state + // this may need to be changed if backends wish to impose + // further constraints + // currently the maximum is infinity, so we do nothing + pending.set_depth_far(*far); + } + if let Some(fov) = init.inlineVerticalFieldOfView { + let mut fov = *fov; + // Step 10 from #apply-the-pending-render-state + // this may need to be changed if backends wish to impose + // further constraints + if fov < 0. { + fov = 0.0001; + } else if fov > PI { + fov = PI - 0.0001; + } + pending.set_inline_vertical_fov(fov); + } + if let Some(ref layer) = init.baseLayer { + pending.set_base_layer(layer.as_deref()); + pending.set_layers(Vec::new()); + let layers = layer + .iter() + .filter_map(|layer| { + let context_id = WebXRContextId::from(layer.context_id()); + let layer_id = layer.layer_id()?; + Some((context_id, layer_id)) + }) + .collect(); + self.session.borrow_mut().set_layers(layers); + } + + if init.depthFar.is_some() || init.depthNear.is_some() { + self.session + .borrow_mut() + .update_clip_planes(*pending.DepthNear() as f32, *pending.DepthFar() as f32); + } + + Ok(()) + } + + /// https://immersive-web.github.io/webxr/#dom-xrsession-requestanimationframe + fn RequestAnimationFrame(&self, callback: Rc<XRFrameRequestCallback>) -> i32 { + // queue up RAF callback, obtain ID + let raf_id = self.next_raf_id.get(); + self.next_raf_id.set(raf_id + 1); + self.raf_callback_list + .borrow_mut() + .push((raf_id, Some(callback))); + + raf_id + } + + /// https://immersive-web.github.io/webxr/#dom-xrsession-cancelanimationframe + fn CancelAnimationFrame(&self, frame: i32) { + let mut list = self.raf_callback_list.borrow_mut(); + if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) { + pair.1 = None; + } + + let mut list = self.current_raf_callback_list.borrow_mut(); + if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) { + pair.1 = None; + } + } + + /// https://immersive-web.github.io/webxr/#dom-xrsession-environmentblendmode + fn EnvironmentBlendMode(&self) -> XREnvironmentBlendMode { + self.blend_mode + } + + /// https://immersive-web.github.io/webxr/#dom-xrsession-visibilitystate + fn VisibilityState(&self) -> XRVisibilityState { + self.visibility_state.get() + } + + /// https://immersive-web.github.io/webxr/#dom-xrsession-requestreferencespace + fn RequestReferenceSpace(&self, ty: XRReferenceSpaceType, comp: InRealm) -> Rc<Promise> { + let p = Promise::new_in_current_realm(&self.global(), comp); + + // https://immersive-web.github.io/webxr/#create-a-reference-space + + // XXXManishearth reject based on session type + // https://github.com/immersive-web/webxr/blob/master/spatial-tracking-explainer.md#practical-usage-guidelines + + match ty { + XRReferenceSpaceType::Bounded_floor | XRReferenceSpaceType::Unbounded => { + // XXXManishearth eventually support these + p.reject_error(Error::NotSupported) + }, + ty => { + if ty != XRReferenceSpaceType::Viewer && + (!self.is_immersive() || ty != XRReferenceSpaceType::Local) + { + let s = ty.as_str(); + if self + .session + .borrow() + .granted_features() + .iter() + .find(|f| &**f == s) + .is_none() + { + p.reject_error(Error::NotSupported); + return p; + } + } + p.resolve_native(&XRReferenceSpace::new(&self.global(), self, ty)); + }, + } + p + } + + /// https://immersive-web.github.io/webxr/#dom-xrsession-inputsources + fn InputSources(&self) -> DomRoot<XRInputSourceArray> { + DomRoot::from_ref(&*self.input_sources) + } + + /// https://immersive-web.github.io/webxr/#dom-xrsession-end + fn End(&self) -> Rc<Promise> { + let global = self.global(); + let p = Promise::new(&global); + if self.ended.get() && self.end_promises.borrow().is_empty() { + // If the session has completely ended and all end promises have been resolved, + // don't queue up more end promises + // + // We need to check for end_promises being empty because `ended` is set + // before everything has been completely shut down, and we do not want to + // prematurely resolve the promise then + // + // However, if end_promises is empty, then all end() promises have already resolved, + // so the session has completely shut down and we should not queue up more promises + p.resolve_native(&()); + return p; + } + self.end_promises.borrow_mut().push(p.clone()); + // This is duplicated in event_callback since this should + // happen ASAP for end() but can happen later if the device + // shuts itself down + self.ended.set(true); + global.as_window().Navigator().Xr().end_session(self); + self.session.borrow_mut().end_session(); + p + } + + // https://immersive-web.github.io/hit-test/#dom-xrsession-requesthittestsource + fn RequestHitTestSource(&self, options: &XRHitTestOptionsInit) -> Rc<Promise> { + let p = Promise::new(&self.global()); + + if self + .session + .borrow() + .granted_features() + .iter() + .find(|f| &**f == "hit-test") + .is_none() + { + p.reject_error(Error::NotSupported); + return p; + } + + let id = self.next_hit_test_id.get(); + self.next_hit_test_id.set(HitTestId(id.0 + 1)); + + let space = options.space.space(); + let ray = if let Some(ref ray) = options.offsetRay { + ray.ray() + } else { + Ray { + origin: Vector3D::new(0., 0., 0.), + direction: Vector3D::new(0., 0., -1.), + } + }; + + let mut types = EntityTypes::default(); + + if let Some(ref tys) = options.entityTypes { + for ty in tys { + match ty { + XRHitTestTrackableType::Point => types.point = true, + XRHitTestTrackableType::Plane => types.plane = true, + XRHitTestTrackableType::Mesh => types.mesh = true, + } + } + } else { + types.plane = true; + } + + let source = HitTestSource { + id, + space, + ray, + types, + }; + self.pending_hit_test_promises + .borrow_mut() + .insert(id, p.clone()); + + self.session.borrow().request_hit_test(source); + + p + } +} + +// The pose of an object in native-space. Should never be exposed. +pub type ApiPose = RigidTransform3D<f32, ApiSpace, webxr_api::Native>; +// A transform between objects in some API-space +pub type ApiRigidTransform = RigidTransform3D<f32, ApiSpace, ApiSpace>; + +#[derive(Clone, Copy)] +pub struct BaseSpace; + +pub type BaseTransform = RigidTransform3D<f32, webxr_api::Native, BaseSpace>; + +#[allow(unsafe_code)] +pub fn cast_transform<T, U, V, W>( + transform: RigidTransform3D<f32, T, U>, +) -> RigidTransform3D<f32, V, W> { + unsafe { mem::transmute(transform) } +} + +impl From<EnvironmentBlendMode> for XREnvironmentBlendMode { + fn from(x: EnvironmentBlendMode) -> Self { + match x { + EnvironmentBlendMode::Opaque => XREnvironmentBlendMode::Opaque, + EnvironmentBlendMode::AlphaBlend => XREnvironmentBlendMode::Alpha_blend, + EnvironmentBlendMode::Additive => XREnvironmentBlendMode::Additive, + } + } +} diff --git a/components/script/dom/xrsessionevent.rs b/components/script/dom/xrsessionevent.rs new file mode 100644 index 00000000000..95005d4e397 --- /dev/null +++ b/components/script/dom/xrsessionevent.rs @@ -0,0 +1,76 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::XRSessionEventBinding::{self, XRSessionEventMethods}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; +use crate::dom::xrsession::XRSession; +use dom_struct::dom_struct; +use servo_atoms::Atom; + +#[dom_struct] +pub struct XRSessionEvent { + event: Event, + session: Dom<XRSession>, +} + +impl XRSessionEvent { + #[allow(unrooted_must_root)] + fn new_inherited(session: &XRSession) -> XRSessionEvent { + XRSessionEvent { + event: Event::new_inherited(), + session: Dom::from_ref(session), + } + } + + pub fn new( + global: &GlobalScope, + type_: Atom, + bubbles: bool, + cancelable: bool, + session: &XRSession, + ) -> DomRoot<XRSessionEvent> { + let trackevent = + reflect_dom_object(Box::new(XRSessionEvent::new_inherited(&session)), global); + { + let event = trackevent.upcast::<Event>(); + event.init_event(type_, bubbles, cancelable); + } + trackevent + } + + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + type_: DOMString, + init: &XRSessionEventBinding::XRSessionEventInit, + ) -> Fallible<DomRoot<XRSessionEvent>> { + Ok(XRSessionEvent::new( + &window.global(), + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + &init.session, + )) + } +} + +impl XRSessionEventMethods for XRSessionEvent { + // https://immersive-web.github.io/webxr/#dom-xrsessioneventinit-session + fn Session(&self) -> DomRoot<XRSession> { + DomRoot::from_ref(&*self.session) + } + + // https://dom.spec.whatwg.org/#dom-event-istrusted + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/xrspace.rs b/components/script/dom/xrspace.rs new file mode 100644 index 00000000000..66dd5b44e70 --- /dev/null +++ b/components/script/dom/xrspace.rs @@ -0,0 +1,119 @@ +/* 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 crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrinputsource::XRInputSource; +use crate::dom::xrjointspace::XRJointSpace; +use crate::dom::xrreferencespace::XRReferenceSpace; +use crate::dom::xrsession::{cast_transform, ApiPose, XRSession}; +use dom_struct::dom_struct; +use euclid::RigidTransform3D; +use webxr_api::{BaseSpace, Frame, Space}; + +#[dom_struct] +pub struct XRSpace { + eventtarget: EventTarget, + session: Dom<XRSession>, + input_source: MutNullableDom<XRInputSource>, + /// If we're an input space, are we an aim space or a grip space? + is_grip_space: bool, +} + +impl XRSpace { + pub fn new_inherited(session: &XRSession) -> XRSpace { + XRSpace { + eventtarget: EventTarget::new_inherited(), + session: Dom::from_ref(session), + input_source: Default::default(), + is_grip_space: false, + } + } + + fn new_inputspace_inner( + session: &XRSession, + input: &XRInputSource, + is_grip_space: bool, + ) -> XRSpace { + XRSpace { + eventtarget: EventTarget::new_inherited(), + session: Dom::from_ref(session), + input_source: MutNullableDom::new(Some(input)), + is_grip_space, + } + } + + pub fn new_inputspace( + global: &GlobalScope, + session: &XRSession, + input: &XRInputSource, + is_grip_space: bool, + ) -> DomRoot<XRSpace> { + reflect_dom_object( + Box::new(XRSpace::new_inputspace_inner(session, input, is_grip_space)), + global, + ) + } + + pub fn space(&self) -> Space { + if let Some(rs) = self.downcast::<XRReferenceSpace>() { + rs.space() + } else if let Some(j) = self.downcast::<XRJointSpace>() { + j.space() + } else if let Some(source) = self.input_source.get() { + let base = if self.is_grip_space { + BaseSpace::Grip(source.id()) + } else { + BaseSpace::TargetRay(source.id()) + }; + Space { + base, + offset: RigidTransform3D::identity(), + } + } else { + panic!("invalid space found") + } + } +} + +impl XRSpace { + /// Gets pose represented by this space + /// + /// The reference origin used is common between all + /// get_pose calls for spaces from the same device, so this can be used to compare + /// with other spaces + pub fn get_pose(&self, base_pose: &Frame) -> Option<ApiPose> { + if let Some(reference) = self.downcast::<XRReferenceSpace>() { + reference.get_pose(base_pose) + } else if let Some(joint) = self.downcast::<XRJointSpace>() { + joint.get_pose(base_pose) + } else if let Some(source) = self.input_source.get() { + // XXXManishearth we should be able to request frame information + // for inputs when necessary instead of always loading it + // + // Also, the below code is quadratic, so this API may need an overhaul anyway + let id = source.id(); + // XXXManishearth once we have dynamic inputs we'll need to handle this better + let frame = base_pose + .inputs + .iter() + .find(|i| i.id == id) + .expect("no input found"); + if self.is_grip_space { + frame.grip_origin.map(cast_transform) + } else { + frame.target_ray_origin.map(cast_transform) + } + } else { + unreachable!() + } + } + + pub fn session(&self) -> &XRSession { + &self.session + } +} diff --git a/components/script/dom/xrsubimage.rs b/components/script/dom/xrsubimage.rs new file mode 100644 index 00000000000..2be59d48d3b --- /dev/null +++ b/components/script/dom/xrsubimage.rs @@ -0,0 +1,23 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRSubImageBinding::XRSubImageBinding::XRSubImageMethods; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::DomRoot; +use crate::dom::xrviewport::XRViewport; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRSubImage { + reflector: Reflector, + viewport: Dom<XRViewport>, +} + +impl XRSubImageMethods for XRSubImage { + /// https://immersive-web.github.io/layers/#dom-xrsubimage-viewport + fn Viewport(&self) -> DomRoot<XRViewport> { + DomRoot::from_ref(&self.viewport) + } +} diff --git a/components/script/dom/xrsystem.rs b/components/script/dom/xrsystem.rs new file mode 100644 index 00000000000..23235c1d95a --- /dev/null +++ b/components/script/dom/xrsystem.rs @@ -0,0 +1,321 @@ +/* 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 crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionInit; +use crate::dom::bindings::codegen::Bindings::XRSystemBinding::{XRSessionMode, XRSystemMethods}; +use crate::dom::bindings::conversions::{ConversionResult, FromJSValConvertible}; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::eventtarget::EventTarget; +use crate::dom::gamepad::Gamepad; +use crate::dom::promise::Promise; +use crate::dom::window::Window; +use crate::dom::xrsession::XRSession; +use crate::dom::xrtest::XRTest; +use crate::realms::InRealm; +use crate::script_thread::ScriptThread; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use ipc_channel::ipc::{self as ipc_crate, IpcReceiver}; +use ipc_channel::router::ROUTER; +use msg::constellation_msg::PipelineId; +use profile_traits::ipc; +use servo_config::pref; +use std::cell::Cell; +use std::rc::Rc; +use webxr_api::{Error as XRError, Frame, Session, SessionInit, SessionMode}; + +#[dom_struct] +pub struct XRSystem { + eventtarget: EventTarget, + gamepads: DomRefCell<Vec<Dom<Gamepad>>>, + pending_immersive_session: Cell<bool>, + active_immersive_session: MutNullableDom<XRSession>, + active_inline_sessions: DomRefCell<Vec<Dom<XRSession>>>, + test: MutNullableDom<XRTest>, + pipeline: PipelineId, +} + +impl XRSystem { + fn new_inherited(pipeline: PipelineId) -> XRSystem { + XRSystem { + eventtarget: EventTarget::new_inherited(), + gamepads: DomRefCell::new(Vec::new()), + pending_immersive_session: Cell::new(false), + active_immersive_session: Default::default(), + active_inline_sessions: DomRefCell::new(Vec::new()), + test: Default::default(), + pipeline, + } + } + + pub fn new(window: &Window) -> DomRoot<XRSystem> { + reflect_dom_object( + Box::new(XRSystem::new_inherited(window.pipeline_id())), + window, + ) + } + + pub fn pending_or_active_session(&self) -> bool { + self.pending_immersive_session.get() || self.active_immersive_session.get().is_some() + } + + pub fn set_pending(&self) { + self.pending_immersive_session.set(true) + } + + pub fn set_active_immersive_session(&self, session: &XRSession) { + // XXXManishearth when we support non-immersive (inline) sessions we should + // ensure they never reach these codepaths + self.pending_immersive_session.set(false); + self.active_immersive_session.set(Some(session)) + } + + /// https://immersive-web.github.io/webxr/#ref-for-eventdef-xrsession-end + pub fn end_session(&self, session: &XRSession) { + // Step 3 + if let Some(active) = self.active_immersive_session.get() { + if Dom::from_ref(&*active) == Dom::from_ref(session) { + self.active_immersive_session.set(None); + // Dirty the canvas, since it has been skipping this step whilst in immersive + // mode + session.dirty_layers(); + } + } + self.active_inline_sessions + .borrow_mut() + .retain(|sess| Dom::from_ref(&**sess) != Dom::from_ref(session)); + } +} + +impl Into<SessionMode> for XRSessionMode { + fn into(self) -> SessionMode { + match self { + XRSessionMode::Immersive_vr => SessionMode::ImmersiveVR, + XRSessionMode::Immersive_ar => SessionMode::ImmersiveAR, + XRSessionMode::Inline => SessionMode::Inline, + } + } +} + +impl XRSystemMethods for XRSystem { + /// https://immersive-web.github.io/webxr/#dom-xr-issessionsupported + fn IsSessionSupported(&self, mode: XRSessionMode) -> Rc<Promise> { + // XXXManishearth this should select an XR device first + let promise = Promise::new(&self.global()); + let mut trusted = Some(TrustedPromise::new(promise.clone())); + let global = self.global(); + let window = global.as_window(); + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap(); + ROUTER.add_route( + receiver.to_opaque(), + Box::new(move |message| { + // router doesn't know this is only called once + let trusted = if let Some(trusted) = trusted.take() { + trusted + } else { + error!("supportsSession callback called twice!"); + return; + }; + let message: Result<(), webxr_api::Error> = if let Ok(message) = message.to() { + message + } else { + error!("supportsSession callback given incorrect payload"); + return; + }; + if let Ok(()) = message { + let _ = + task_source.queue_with_canceller(trusted.resolve_task(true), &canceller); + } else { + let _ = + task_source.queue_with_canceller(trusted.resolve_task(false), &canceller); + }; + }), + ); + window + .webxr_registry() + .supports_session(mode.into(), sender); + + promise + } + + /// https://immersive-web.github.io/webxr/#dom-xr-requestsession + #[allow(unsafe_code)] + fn RequestSession( + &self, + mode: XRSessionMode, + init: RootedTraceableBox<XRSessionInit>, + comp: InRealm, + ) -> Rc<Promise> { + let global = self.global(); + let window = global.as_window(); + let promise = Promise::new_in_current_realm(&global, comp); + + if mode != XRSessionMode::Inline { + if !ScriptThread::is_user_interacting() { + if pref!(dom.webxr.unsafe_assume_user_intent) { + warn!("The dom.webxr.unsafe-assume-user-intent preference assumes user intent to enter WebXR."); + } else { + promise.reject_error(Error::Security); + return promise; + } + } + + if self.pending_or_active_session() { + promise.reject_error(Error::InvalidState); + return promise; + } + + self.set_pending(); + } + + let mut required_features = vec![]; + let mut optional_features = vec![]; + let cx = global.get_cx(); + + // We are supposed to include "viewer" and on immersive devices "local" + // by default here, but this is handled directly in requestReferenceSpace() + if let Some(ref r) = init.requiredFeatures { + for feature in r { + unsafe { + if let Ok(ConversionResult::Success(s)) = + String::from_jsval(*cx, feature.handle(), ()) + { + required_features.push(s) + } else { + warn!("Unable to convert required feature to string"); + if mode != XRSessionMode::Inline { + self.pending_immersive_session.set(false); + } + promise.reject_error(Error::NotSupported); + return promise; + } + } + } + } + + if let Some(ref o) = init.optionalFeatures { + for feature in o { + unsafe { + if let Ok(ConversionResult::Success(s)) = + String::from_jsval(*cx, feature.handle(), ()) + { + optional_features.push(s) + } else { + warn!("Unable to convert optional feature to string"); + } + } + } + } + + let init = SessionInit { + required_features, + optional_features, + first_person_observer_view: pref!(dom.webxr.first_person_observer_view), + }; + + let mut trusted = Some(TrustedPromise::new(promise.clone())); + let this = Trusted::new(self); + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap(); + let (frame_sender, frame_receiver) = ipc_crate::channel().unwrap(); + let mut frame_receiver = Some(frame_receiver); + ROUTER.add_route( + receiver.to_opaque(), + Box::new(move |message| { + // router doesn't know this is only called once + let trusted = trusted.take().unwrap(); + let this = this.clone(); + let frame_receiver = frame_receiver.take().unwrap(); + let message: Result<Session, webxr_api::Error> = if let Ok(message) = message.to() { + message + } else { + error!("requestSession callback given incorrect payload"); + return; + }; + let _ = task_source.queue_with_canceller( + task!(request_session: move || { + this.root().session_obtained(message, trusted.root(), mode, frame_receiver); + }), + &canceller, + ); + }), + ); + window + .webxr_registry() + .request_session(mode.into(), init, sender, frame_sender); + promise + } + + // https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md + fn Test(&self) -> DomRoot<XRTest> { + self.test.or_init(|| XRTest::new(&self.global())) + } +} + +impl XRSystem { + fn session_obtained( + &self, + response: Result<Session, XRError>, + promise: Rc<Promise>, + mode: XRSessionMode, + frame_receiver: IpcReceiver<Frame>, + ) { + let session = match response { + Ok(session) => session, + Err(e) => { + warn!("Error requesting XR session: {:?}", e); + if mode != XRSessionMode::Inline { + self.pending_immersive_session.set(false); + } + promise.reject_error(Error::NotSupported); + return; + }, + }; + let session = XRSession::new(&self.global(), session, mode, frame_receiver); + if mode == XRSessionMode::Inline { + self.active_inline_sessions + .borrow_mut() + .push(Dom::from_ref(&*session)); + } else { + self.set_active_immersive_session(&session); + } + promise.resolve_native(&session); + // https://github.com/immersive-web/webxr/issues/961 + // This must be called _after_ the promise is resolved + session.setup_initial_inputs(); + } + + // https://github.com/immersive-web/navigation/issues/10 + pub fn dispatch_sessionavailable(&self) { + let xr = Trusted::new(self); + let global = self.global(); + let window = global.as_window(); + window + .task_manager() + .dom_manipulation_task_source() + .queue( + task!(fire_sessionavailable_event: move || { + // The sessionavailable event indicates user intent to enter an XR session + let xr = xr.root(); + let interacting = ScriptThread::is_user_interacting(); + ScriptThread::set_user_interacting(true); + xr.upcast::<EventTarget>().fire_bubbling_event(atom!("sessionavailable")); + ScriptThread::set_user_interacting(interacting); + }), + window.upcast(), + ) + .unwrap(); + } +} diff --git a/components/script/dom/xrtest.rs b/components/script/dom/xrtest.rs new file mode 100644 index 00000000000..37707c3ce8e --- /dev/null +++ b/components/script/dom/xrtest.rs @@ -0,0 +1,230 @@ +/* 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/. */ + +/* 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 crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; +use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionMode; +use crate::dom::bindings::codegen::Bindings::XRTestBinding::{FakeXRDeviceInit, XRTestMethods}; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::fakexrdevice::{get_origin, get_views, get_world, FakeXRDevice}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::script_thread::ScriptThread; +use crate::task_source::TaskSource; +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSender; +use ipc_channel::router::ROUTER; +use profile_traits::ipc; +use std::rc::Rc; +use webxr_api::{self, Error as XRError, MockDeviceInit, MockDeviceMsg}; + +#[dom_struct] +pub struct XRTest { + reflector: Reflector, + devices_connected: DomRefCell<Vec<Dom<FakeXRDevice>>>, +} + +impl XRTest { + pub fn new_inherited() -> XRTest { + XRTest { + reflector: Reflector::new(), + devices_connected: DomRefCell::new(vec![]), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<XRTest> { + reflect_dom_object(Box::new(XRTest::new_inherited()), global) + } + + fn device_obtained( + &self, + response: Result<IpcSender<MockDeviceMsg>, XRError>, + trusted: TrustedPromise, + ) { + let promise = trusted.root(); + if let Ok(sender) = response { + let device = FakeXRDevice::new(&self.global(), sender); + self.devices_connected + .borrow_mut() + .push(Dom::from_ref(&device)); + promise.resolve_native(&device); + } else { + promise.reject_native(&()); + } + } +} + +impl XRTestMethods for XRTest { + /// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md + #[allow(unsafe_code)] + fn SimulateDeviceConnection(&self, init: &FakeXRDeviceInit) -> Rc<Promise> { + let global = self.global(); + let p = Promise::new(&global); + + let origin = if let Some(ref o) = init.viewerOrigin { + match get_origin(&o) { + Ok(origin) => Some(origin), + Err(e) => { + p.reject_error(e); + return p; + }, + } + } else { + None + }; + + let floor_origin = if let Some(ref o) = init.floorOrigin { + match get_origin(&o) { + Ok(origin) => Some(origin), + Err(e) => { + p.reject_error(e); + return p; + }, + } + } else { + None + }; + + let views = match get_views(&init.views) { + Ok(views) => views, + Err(e) => { + p.reject_error(e); + return p; + }, + }; + + let supported_features = if let Some(ref s) = init.supportedFeatures { + s.iter().cloned().map(String::from).collect() + } else { + vec![] + }; + + let world = if let Some(ref w) = init.world { + let w = match get_world(w) { + Ok(w) => w, + Err(e) => { + p.reject_error(e); + return p; + }, + }; + Some(w) + } else { + None + }; + + let (mut supports_inline, mut supports_vr, mut supports_ar) = (false, false, false); + + if let Some(ref modes) = init.supportedModes { + for mode in modes { + match mode { + XRSessionMode::Immersive_vr => supports_vr = true, + XRSessionMode::Immersive_ar => supports_ar = true, + XRSessionMode::Inline => supports_inline = true, + } + } + } + + let init = MockDeviceInit { + viewer_origin: origin, + views, + supports_inline, + supports_vr, + supports_ar, + floor_origin, + supported_features, + world, + }; + + let global = self.global(); + let window = global.as_window(); + let this = Trusted::new(self); + let mut trusted = Some(TrustedPromise::new(p.clone())); + + let (task_source, canceller) = window + .task_manager() + .dom_manipulation_task_source_with_canceller(); + let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap(); + ROUTER.add_route( + receiver.to_opaque(), + Box::new(move |message| { + let trusted = trusted + .take() + .expect("SimulateDeviceConnection callback called twice"); + let this = this.clone(); + let message = message + .to() + .expect("SimulateDeviceConnection callback given incorrect payload"); + + let _ = task_source.queue_with_canceller( + task!(request_session: move || { + this.root().device_obtained(message, trusted); + }), + &canceller, + ); + }), + ); + window + .webxr_registry() + .simulate_device_connection(init, sender); + + p + } + + /// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md + fn SimulateUserActivation(&self, f: Rc<Function>) { + ScriptThread::set_user_interacting(true); + let _ = f.Call__(vec![], ExceptionHandling::Rethrow); + ScriptThread::set_user_interacting(false); + } + + /// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md + fn DisconnectAllDevices(&self) -> Rc<Promise> { + // XXXManishearth implement device disconnection and session ending + let global = self.global(); + let p = Promise::new(&global); + let mut devices = self.devices_connected.borrow_mut(); + if devices.is_empty() { + p.resolve_native(&()); + } else { + let mut len = devices.len(); + + let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap(); + let mut rooted_devices: Vec<_> = + devices.iter().map(|x| DomRoot::from_ref(&**x)).collect(); + devices.clear(); + + let mut trusted = Some(TrustedPromise::new(p.clone())); + let (task_source, canceller) = global + .as_window() + .task_manager() + .dom_manipulation_task_source_with_canceller(); + + ROUTER.add_route( + receiver.to_opaque(), + Box::new(move |_| { + len -= 1; + if len == 0 { + let trusted = trusted + .take() + .expect("DisconnectAllDevices disconnected more devices than expected"); + let _ = + task_source.queue_with_canceller(trusted.resolve_task(()), &canceller); + } + }), + ); + + for device in rooted_devices.drain(..) { + device.disconnect(sender.clone()); + } + }; + p + } +} diff --git a/components/script/dom/xrview.rs b/components/script/dom/xrview.rs new file mode 100644 index 00000000000..55cac600f6b --- /dev/null +++ b/components/script/dom/xrview.rs @@ -0,0 +1,105 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRViewBinding::{XREye, XRViewMethods}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::utils::create_typed_array; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrrigidtransform::XRRigidTransform; +use crate::dom::xrsession::{cast_transform, BaseSpace, BaseTransform, XRSession}; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use euclid::RigidTransform3D; +use js::jsapi::{Heap, JSObject}; +use std::ptr::NonNull; +use webxr_api::{ApiSpace, View}; + +#[dom_struct] +pub struct XRView { + reflector_: Reflector, + session: Dom<XRSession>, + eye: XREye, + viewport_index: usize, + #[ignore_malloc_size_of = "mozjs"] + proj: Heap<*mut JSObject>, + #[ignore_malloc_size_of = "defined in rust-webxr"] + view: View<ApiSpace>, + transform: Dom<XRRigidTransform>, +} + +impl XRView { + fn new_inherited( + session: &XRSession, + transform: &XRRigidTransform, + eye: XREye, + viewport_index: usize, + view: View<ApiSpace>, + ) -> XRView { + XRView { + reflector_: Reflector::new(), + session: Dom::from_ref(session), + eye, + viewport_index, + proj: Heap::default(), + view, + transform: Dom::from_ref(transform), + } + } + + pub fn new<V: Copy>( + global: &GlobalScope, + session: &XRSession, + view: &View<V>, + eye: XREye, + viewport_index: usize, + to_base: &BaseTransform, + ) -> DomRoot<XRView> { + let transform: RigidTransform3D<f32, V, BaseSpace> = to_base.pre_transform(&view.transform); + let transform = XRRigidTransform::new(global, cast_transform(transform)); + + reflect_dom_object( + Box::new(XRView::new_inherited( + session, + &transform, + eye, + viewport_index, + view.cast_unit(), + )), + global, + ) + } + + pub fn session(&self) -> &XRSession { + &self.session + } + + pub fn viewport_index(&self) -> usize { + self.viewport_index + } +} + +impl XRViewMethods for XRView { + /// https://immersive-web.github.io/webxr/#dom-xrview-eye + fn Eye(&self) -> XREye { + self.eye + } + + /// https://immersive-web.github.io/webxr/#dom-xrview-projectionmatrix + fn ProjectionMatrix(&self, _cx: JSContext) -> NonNull<JSObject> { + if self.proj.get().is_null() { + let cx = self.global().get_cx(); + // row_major since euclid uses row vectors + let proj = self.view.projection.to_row_major_array(); + create_typed_array(cx, &proj, &self.proj); + } + NonNull::new(self.proj.get()).unwrap() + } + + /// https://immersive-web.github.io/webxr/#dom-xrview-transform + fn Transform(&self) -> DomRoot<XRRigidTransform> { + DomRoot::from_ref(&self.transform) + } +} diff --git a/components/script/dom/xrviewerpose.rs b/components/script/dom/xrviewerpose.rs new file mode 100644 index 00000000000..3d11185c809 --- /dev/null +++ b/components/script/dom/xrviewerpose.rs @@ -0,0 +1,173 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye; +use crate::dom::bindings::codegen::Bindings::XRViewerPoseBinding::XRViewerPoseMethods; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::dom::xrpose::XRPose; +use crate::dom::xrrigidtransform::XRRigidTransform; +use crate::dom::xrsession::{cast_transform, BaseSpace, BaseTransform, XRSession}; +use crate::dom::xrview::XRView; +use crate::realms::enter_realm; +use crate::script_runtime::JSContext; +use dom_struct::dom_struct; +use euclid::RigidTransform3D; +use js::conversions::ToJSValConvertible; +use js::jsapi::Heap; +use js::jsval::{JSVal, UndefinedValue}; +use webxr_api::{Viewer, ViewerPose, Views}; + +#[dom_struct] +pub struct XRViewerPose { + pose: XRPose, + #[ignore_malloc_size_of = "mozjs"] + views: Heap<JSVal>, +} + +impl XRViewerPose { + fn new_inherited(transform: &XRRigidTransform) -> XRViewerPose { + XRViewerPose { + pose: XRPose::new_inherited(transform), + views: Heap::default(), + } + } + + #[allow(unsafe_code)] + pub fn new( + global: &GlobalScope, + session: &XRSession, + to_base: BaseTransform, + viewer_pose: &ViewerPose, + ) -> DomRoot<XRViewerPose> { + let _ac = enter_realm(&*global); + rooted_vec!(let mut views); + match &viewer_pose.views { + Views::Inline => views.push(XRView::new( + global, + session, + &session.inline_view(), + XREye::None, + 0, + &to_base, + )), + Views::Mono(view) => views.push(XRView::new( + global, + session, + &view, + XREye::None, + 0, + &to_base, + )), + Views::Stereo(left, right) => { + views.push(XRView::new( + global, + session, + &left, + XREye::Left, + 0, + &to_base, + )); + views.push(XRView::new( + global, + session, + &right, + XREye::Right, + 1, + &to_base, + )); + }, + Views::StereoCapture(left, right, third_eye) => { + views.push(XRView::new( + global, + session, + &left, + XREye::Left, + 0, + &to_base, + )); + views.push(XRView::new( + global, + session, + &right, + XREye::Right, + 1, + &to_base, + )); + views.push(XRView::new( + global, + session, + &third_eye, + XREye::None, + 2, + &to_base, + )); + }, + Views::Cubemap(front, left, right, top, bottom, back) => { + views.push(XRView::new( + global, + session, + &front, + XREye::None, + 0, + &to_base, + )); + views.push(XRView::new( + global, + session, + &left, + XREye::None, + 1, + &to_base, + )); + views.push(XRView::new( + global, + session, + &right, + XREye::None, + 2, + &to_base, + )); + views.push(XRView::new(global, session, &top, XREye::None, 3, &to_base)); + views.push(XRView::new( + global, + session, + &bottom, + XREye::None, + 4, + &to_base, + )); + views.push(XRView::new( + global, + session, + &back, + XREye::None, + 5, + &to_base, + )); + }, + }; + let transform: RigidTransform3D<f32, Viewer, BaseSpace> = + to_base.pre_transform(&viewer_pose.transform); + let transform = XRRigidTransform::new(global, cast_transform(transform)); + let pose = reflect_dom_object(Box::new(XRViewerPose::new_inherited(&transform)), global); + + let cx = global.get_cx(); + unsafe { + rooted!(in(*cx) let mut jsval = UndefinedValue()); + views.to_jsval(*cx, jsval.handle_mut()); + pose.views.set(jsval.get()); + } + + pose + } +} + +impl XRViewerPoseMethods for XRViewerPose { + /// https://immersive-web.github.io/webxr/#dom-xrviewerpose-views + fn Views(&self, _cx: JSContext) -> JSVal { + self.views.get() + } +} diff --git a/components/script/dom/xrviewport.rs b/components/script/dom/xrviewport.rs new file mode 100644 index 00000000000..30872bae0c4 --- /dev/null +++ b/components/script/dom/xrviewport.rs @@ -0,0 +1,52 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRViewportBinding::XRViewportMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use dom_struct::dom_struct; +use euclid::Rect; +use webxr_api::Viewport; + +#[dom_struct] +pub struct XRViewport { + reflector_: Reflector, + viewport: Rect<i32, Viewport>, +} + +impl XRViewport { + fn new_inherited(viewport: Rect<i32, Viewport>) -> XRViewport { + XRViewport { + reflector_: Reflector::new(), + viewport, + } + } + + pub fn new(global: &GlobalScope, viewport: Rect<i32, Viewport>) -> DomRoot<XRViewport> { + reflect_dom_object(Box::new(XRViewport::new_inherited(viewport)), global) + } +} + +impl XRViewportMethods for XRViewport { + /// https://immersive-web.github.io/webxr/#dom-xrviewport-x + fn X(&self) -> i32 { + self.viewport.origin.x + } + + /// https://immersive-web.github.io/webxr/#dom-xrviewport-y + fn Y(&self) -> i32 { + self.viewport.origin.y + } + + /// https://immersive-web.github.io/webxr/#dom-xrviewport-width + fn Width(&self) -> i32 { + self.viewport.size.width + } + + /// https://immersive-web.github.io/webxr/#dom-xrviewport-height + fn Height(&self) -> i32 { + self.viewport.size.height + } +} diff --git a/components/script/dom/xrwebglbinding.rs b/components/script/dom/xrwebglbinding.rs new file mode 100644 index 00000000000..64ea31e0bce --- /dev/null +++ b/components/script/dom/xrwebglbinding.rs @@ -0,0 +1,144 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye; +use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::XRCubeLayerInit; +use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::XRCylinderLayerInit; +use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::XREquirectLayerInit; +use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::XRProjectionLayerInit; +use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::XRQuadLayerInit; +use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::XRTextureType; +use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::XRWebGLBindingBinding::XRWebGLBindingMethods; +use crate::dom::bindings::codegen::UnionTypes::WebGLRenderingContextOrWebGL2RenderingContext; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::dom::window::Window; +use crate::dom::xrcompositionlayer::XRCompositionLayer; +use crate::dom::xrcubelayer::XRCubeLayer; +use crate::dom::xrcylinderlayer::XRCylinderLayer; +use crate::dom::xrequirectlayer::XREquirectLayer; +use crate::dom::xrframe::XRFrame; +use crate::dom::xrprojectionlayer::XRProjectionLayer; +use crate::dom::xrquadlayer::XRQuadLayer; +use crate::dom::xrsession::XRSession; +use crate::dom::xrview::XRView; +use crate::dom::xrwebglsubimage::XRWebGLSubImage; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct XRWebGLBinding { + reflector: Reflector, + session: Dom<XRSession>, + context: Dom<WebGLRenderingContext>, +} + +impl XRWebGLBinding { + pub fn new_inherited(session: &XRSession, context: &WebGLRenderingContext) -> XRWebGLBinding { + XRWebGLBinding { + reflector: Reflector::new(), + session: Dom::from_ref(session), + context: Dom::from_ref(context), + } + } + + pub fn new( + global: &Window, + session: &XRSession, + context: &WebGLRenderingContext, + ) -> DomRoot<XRWebGLBinding> { + reflect_dom_object( + Box::new(XRWebGLBinding::new_inherited(session, context)), + global, + ) + } + + #[allow(non_snake_case)] + pub fn Constructor( + global: &Window, + session: &XRSession, + context: WebGLRenderingContextOrWebGL2RenderingContext, + ) -> DomRoot<XRWebGLBinding> { + let context = match context { + WebGLRenderingContextOrWebGL2RenderingContext::WebGLRenderingContext(ctx) => ctx, + WebGLRenderingContextOrWebGL2RenderingContext::WebGL2RenderingContext(ctx) => { + ctx.base_context() + }, + }; + XRWebGLBinding::new(global, session, &context) + } +} + +impl XRWebGLBindingMethods for XRWebGLBinding { + /// https://immersive-web.github.io/layers/#dom-xrwebglbinding-createprojectionlayer + fn CreateProjectionLayer( + &self, + _: XRTextureType, + _: &XRProjectionLayerInit, + ) -> Fallible<DomRoot<XRProjectionLayer>> { + // https://github.com/servo/servo/issues/27468 + Err(Error::NotSupported) + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglbinding-createquadlayer + fn CreateQuadLayer( + &self, + _: XRTextureType, + _: &Option<XRQuadLayerInit>, + ) -> Fallible<DomRoot<XRQuadLayer>> { + // https://github.com/servo/servo/issues/27493 + Err(Error::NotSupported) + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglbinding-createcylinderlayer + fn CreateCylinderLayer( + &self, + _: XRTextureType, + _: &Option<XRCylinderLayerInit>, + ) -> Fallible<DomRoot<XRCylinderLayer>> { + // https://github.com/servo/servo/issues/27493 + Err(Error::NotSupported) + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglbinding-createequirectlayer + fn CreateEquirectLayer( + &self, + _: XRTextureType, + _: &Option<XREquirectLayerInit>, + ) -> Fallible<DomRoot<XREquirectLayer>> { + // https://github.com/servo/servo/issues/27493 + Err(Error::NotSupported) + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglbinding-createcubelayer + fn CreateCubeLayer(&self, _: &Option<XRCubeLayerInit>) -> Fallible<DomRoot<XRCubeLayer>> { + // https://github.com/servo/servo/issues/27493 + Err(Error::NotSupported) + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglbinding-getsubimage + fn GetSubImage( + &self, + _: &XRCompositionLayer, + _: &XRFrame, + _: XREye, + ) -> Fallible<DomRoot<XRWebGLSubImage>> { + // https://github.com/servo/servo/issues/27468 + Err(Error::NotSupported) + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglbinding-getviewsubimage + fn GetViewSubImage( + &self, + _: &XRProjectionLayer, + _: &XRView, + ) -> Fallible<DomRoot<XRWebGLSubImage>> { + // https://github.com/servo/servo/issues/27468 + Err(Error::NotSupported) + } +} diff --git a/components/script/dom/xrwebgllayer.rs b/components/script/dom/xrwebgllayer.rs new file mode 100644 index 00000000000..dd1e4eff57f --- /dev/null +++ b/components/script/dom/xrwebgllayer.rs @@ -0,0 +1,328 @@ +/* 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 crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods; +use crate::dom::bindings::codegen::Bindings::XRWebGLLayerBinding::XRWebGLLayerInit; +use crate::dom::bindings::codegen::Bindings::XRWebGLLayerBinding::XRWebGLLayerMethods; +use crate::dom::bindings::codegen::Bindings::XRWebGLLayerBinding::XRWebGLRenderingContext; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::webglframebuffer::WebGLFramebuffer; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::WebGLRenderingContext; +use crate::dom::webgltexture::WebGLTexture; +use crate::dom::window::Window; +use crate::dom::xrframe::XRFrame; +use crate::dom::xrlayer::XRLayer; +use crate::dom::xrsession::XRSession; +use crate::dom::xrview::XRView; +use crate::dom::xrviewport::XRViewport; +use canvas_traits::webgl::WebGLCommand; +use canvas_traits::webgl::WebGLContextId; +use canvas_traits::webgl::WebGLTextureId; +use dom_struct::dom_struct; +use euclid::{Rect, Size2D}; +use std::convert::TryInto; +use webxr_api::ContextId as WebXRContextId; +use webxr_api::LayerId; +use webxr_api::LayerInit; +use webxr_api::Viewport; + +impl<'a> From<&'a XRWebGLLayerInit> for LayerInit { + fn from(init: &'a XRWebGLLayerInit) -> LayerInit { + LayerInit::WebGLLayer { + alpha: init.alpha, + antialias: init.antialias, + depth: init.depth, + stencil: init.stencil, + framebuffer_scale_factor: *init.framebufferScaleFactor as f32, + ignore_depth_values: init.ignoreDepthValues, + } + } +} + +#[dom_struct] +pub struct XRWebGLLayer { + xr_layer: XRLayer, + antialias: bool, + depth: bool, + stencil: bool, + alpha: bool, + ignore_depth_values: bool, + /// If none, this is an inline session (the composition disabled flag is true) + framebuffer: Option<Dom<WebGLFramebuffer>>, +} + +impl XRWebGLLayer { + pub fn new_inherited( + session: &XRSession, + context: &WebGLRenderingContext, + init: &XRWebGLLayerInit, + framebuffer: Option<&WebGLFramebuffer>, + layer_id: Option<LayerId>, + ) -> XRWebGLLayer { + XRWebGLLayer { + xr_layer: XRLayer::new_inherited(session, context, layer_id), + antialias: init.antialias, + depth: init.depth, + stencil: init.stencil, + alpha: init.alpha, + ignore_depth_values: init.ignoreDepthValues, + framebuffer: framebuffer.map(Dom::from_ref), + } + } + + pub fn new( + global: &GlobalScope, + session: &XRSession, + context: &WebGLRenderingContext, + init: &XRWebGLLayerInit, + framebuffer: Option<&WebGLFramebuffer>, + layer_id: Option<LayerId>, + ) -> DomRoot<XRWebGLLayer> { + reflect_dom_object( + Box::new(XRWebGLLayer::new_inherited( + session, + context, + init, + framebuffer, + layer_id, + )), + global, + ) + } + + /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-xrwebgllayer + #[allow(non_snake_case)] + pub fn Constructor( + global: &Window, + session: &XRSession, + context: XRWebGLRenderingContext, + init: &XRWebGLLayerInit, + ) -> Fallible<DomRoot<Self>> { + let context = match context { + XRWebGLRenderingContext::WebGLRenderingContext(ctx) => ctx, + XRWebGLRenderingContext::WebGL2RenderingContext(ctx) => ctx.base_context(), + }; + + // Step 2 + if session.is_ended() { + return Err(Error::InvalidState); + } + // XXXManishearth step 3: throw error if context is lost + // XXXManishearth step 4: check XR compat flag for immersive sessions + + let (framebuffer, layer_id) = if session.is_immersive() { + // Step 9.2. "Initialize layer’s framebuffer to a new opaque framebuffer created with context." + let size = session + .with_session(|session| session.recommended_framebuffer_resolution()) + .ok_or(Error::Operation)?; + let framebuffer = WebGLFramebuffer::maybe_new_webxr(session, &context, size) + .ok_or(Error::Operation)?; + + // Step 9.3. "Allocate and initialize resources compatible with session’s XR device, + // including GPU accessible memory buffers, as required to support the compositing of layer." + let context_id = WebXRContextId::from(context.context_id()); + let layer_init = LayerInit::from(init); + let layer_id = session + .with_session(|session| session.create_layer(context_id, layer_init)) + .map_err(|_| Error::Operation)?; + + // Step 9.4: "If layer’s resources were unable to be created for any reason, + // throw an OperationError and abort these steps." + (Some(framebuffer), Some(layer_id)) + } else { + (None, None) + }; + + // Ensure that we finish setting up this layer before continuing. + context.Finish(); + + // Step 10. "Return layer." + Ok(XRWebGLLayer::new( + &global.global(), + session, + &context, + init, + framebuffer.as_deref(), + layer_id, + )) + } + + pub fn layer_id(&self) -> Option<LayerId> { + self.xr_layer.layer_id() + } + + pub fn context_id(&self) -> WebGLContextId { + self.xr_layer.context_id() + } + + pub fn session(&self) -> &XRSession { + &self.xr_layer.session() + } + + pub fn size(&self) -> Size2D<u32, Viewport> { + if let Some(framebuffer) = self.framebuffer.as_ref() { + let size = framebuffer.size().unwrap_or((0, 0)); + Size2D::new( + size.0.try_into().unwrap_or(0), + size.1.try_into().unwrap_or(0), + ) + } else { + let size = self.context().Canvas().get_size(); + Size2D::from_untyped(size) + } + } + + fn texture_target(&self) -> u32 { + if cfg!(target_os = "macos") { + sparkle::gl::TEXTURE_RECTANGLE + } else { + sparkle::gl::TEXTURE_2D + } + } + + pub fn begin_frame(&self, frame: &XRFrame) -> Option<()> { + debug!("XRWebGLLayer begin frame"); + let framebuffer = self.framebuffer.as_ref()?; + let context = framebuffer.upcast::<WebGLObject>().context(); + let sub_images = frame.get_sub_images(self.layer_id()?)?; + let session = self.session(); + // TODO: Cache this texture + let color_texture_id = + WebGLTextureId::maybe_new(sub_images.sub_image.as_ref()?.color_texture)?; + let color_texture = WebGLTexture::new_webxr(context, color_texture_id, session); + let target = self.texture_target(); + + // Save the current bindings + let saved_framebuffer = context.get_draw_framebuffer_slot().get(); + let saved_framebuffer_target = framebuffer.target(); + let saved_texture_id = context + .textures() + .active_texture_slot(target, context.webgl_version()) + .ok() + .and_then(|slot| slot.get().map(|texture| texture.id())); + + // We have to pick a framebuffer target. + // If there is a draw framebuffer, we use its target, + // otherwise we just use DRAW_FRAMEBUFFER. + let framebuffer_target = saved_framebuffer + .as_ref() + .and_then(|fb| fb.target()) + .unwrap_or(constants::DRAW_FRAMEBUFFER); + + // Update the attachments + context.send_command(WebGLCommand::BindTexture(target, Some(color_texture_id))); + framebuffer.bind(framebuffer_target); + framebuffer + .texture2d_even_if_opaque( + constants::COLOR_ATTACHMENT0, + self.texture_target(), + Some(&color_texture), + 0, + ) + .ok()?; + if let Some(id) = sub_images.sub_image.as_ref()?.depth_stencil_texture { + // TODO: Cache this texture + let depth_stencil_texture_id = WebGLTextureId::maybe_new(id)?; + let depth_stencil_texture = + WebGLTexture::new_webxr(context, depth_stencil_texture_id, session); + framebuffer + .texture2d_even_if_opaque( + constants::DEPTH_STENCIL_ATTACHMENT, + constants::TEXTURE_2D, + Some(&depth_stencil_texture), + 0, + ) + .ok()?; + } + + // Restore the old bindings + context.send_command(WebGLCommand::BindTexture(target, saved_texture_id)); + if let Some(framebuffer_target) = saved_framebuffer_target { + framebuffer.bind(framebuffer_target); + } + if let Some(framebuffer) = saved_framebuffer { + framebuffer.bind(framebuffer_target); + } + Some(()) + } + + pub fn end_frame(&self, _frame: &XRFrame) -> Option<()> { + debug!("XRWebGLLayer end frame"); + // TODO: invalidate the old texture + let framebuffer = self.framebuffer.as_ref()?; + // TODO: rebind the current bindings + framebuffer.bind(constants::FRAMEBUFFER); + framebuffer + .texture2d_even_if_opaque(constants::COLOR_ATTACHMENT0, self.texture_target(), None, 0) + .ok()?; + framebuffer + .texture2d_even_if_opaque( + constants::DEPTH_STENCIL_ATTACHMENT, + constants::DEPTH_STENCIL_ATTACHMENT, + None, + 0, + ) + .ok()?; + framebuffer.upcast::<WebGLObject>().context().Flush(); + Some(()) + } + + pub(crate) fn context(&self) -> &WebGLRenderingContext { + self.xr_layer.context() + } +} + +impl XRWebGLLayerMethods for XRWebGLLayer { + /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-antialias + fn Antialias(&self) -> bool { + self.antialias + } + + /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-ignoredepthvalues + fn IgnoreDepthValues(&self) -> bool { + self.ignore_depth_values + } + + /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebuffer + fn GetFramebuffer(&self) -> Option<DomRoot<WebGLFramebuffer>> { + self.framebuffer.as_ref().map(|x| DomRoot::from_ref(&**x)) + } + + /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebufferwidth + fn FramebufferWidth(&self) -> u32 { + self.size().width + } + + /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebufferheight + fn FramebufferHeight(&self) -> u32 { + self.size().height + } + + /// https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport + fn GetViewport(&self, view: &XRView) -> Option<DomRoot<XRViewport>> { + if self.session() != view.session() { + return None; + } + + let index = view.viewport_index(); + + let viewport = self.session().with_session(|s| { + // Inline sssions + if s.viewports().is_empty() { + Rect::from_size(self.size().to_i32()) + } else { + s.viewports()[index] + } + }); + + Some(XRViewport::new(&self.global(), viewport)) + } +} diff --git a/components/script/dom/xrwebglsubimage.rs b/components/script/dom/xrwebglsubimage.rs new file mode 100644 index 00000000000..0813bd85d9e --- /dev/null +++ b/components/script/dom/xrwebglsubimage.rs @@ -0,0 +1,48 @@ +/* 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 crate::dom::bindings::codegen::Bindings::XRWebGLSubImageBinding::XRWebGLSubImageBinding::XRWebGLSubImageMethods; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::DomRoot; +use crate::dom::webgltexture::WebGLTexture; +use crate::dom::xrsubimage::XRSubImage; +use dom_struct::dom_struct; +use euclid::Size2D; +use webxr_api::Viewport; + +#[dom_struct] +pub struct XRWebGLSubImage { + xr_sub_image: XRSubImage, + color_texture: Dom<WebGLTexture>, + depth_stencil_texture: Option<Dom<WebGLTexture>>, + image_index: Option<u32>, + size: Size2D<u32, Viewport>, +} + +impl XRWebGLSubImageMethods for XRWebGLSubImage { + /// https://immersive-web.github.io/layers/#dom-xrwebglsubimage-colortexture + fn ColorTexture(&self) -> DomRoot<WebGLTexture> { + DomRoot::from_ref(&self.color_texture) + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglsubimage-depthstenciltexture + fn GetDepthStencilTexture(&self) -> Option<DomRoot<WebGLTexture>> { + self.depth_stencil_texture.as_deref().map(DomRoot::from_ref) + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglsubimage-imageindex + fn GetImageIndex(&self) -> Option<u32> { + self.image_index + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglsubimage-texturewidth + fn TextureWidth(&self) -> u32 { + self.size.width + } + + /// https://immersive-web.github.io/layers/#dom-xrwebglsubimage-textureheight + fn TextureHeight(&self) -> u32 { + self.size.height + } +} |